Illustration by Virginia Poltrack

JankStats Goes Alpha

A library for chasing jank in the real world

Chet Haase
Published in
6 min readFeb 9, 2022

--

Jank (noun): Bad application performance which can result in missed frames, discontinuous UI motion, and bad user experience. See “unhappy users.”

Debugging performance issues is… hard. Often, it’s not clear where to start, what tools to use, what problems users are having, or how those problems manifest on real world devices.

The Android team has spent the past several years providing more tools to debug different parts of the problem, from analyzing startup performance to testing specific code paths to testing and optimizing particular use cases to visual profilers in the IDE. All of these are meant for development-time testing to help you debug and fix the problems that you see locally.

Meanwhile, both Google Play’s Android Vitals and Firebase offer dashboards where developers can see how their apps are doing on user devices in the field.

But still, it’s hard to know how to find the problems that your app might have in real situations, especially problems that occur on user devices, and not just the situations you see on that convenient development machine that you use from the comfort of your own chair. The performance dashboards help, but they don’t necessarily provide the level of detail you need to know what was happening when your users were experiencing problems.

Enter JankStats: the first AndroidX library built specifically to instrument and report performance problems with your app on user devices.

JankStats is a relatively small API with essentially three goals: capturing per-frame performance information, running on your app on user (not just development) devices, and enabling instrumentation and reporting about what’s happening in your app when it has performance problems.

Per-Frame Performance

The Android platform already offers ways to get frame performance data. For example, you can use FrameMetrics starting with API 24, and we have been adding to it in more recent releases to provide even more information. If you are running on earlier releases, there are various approaches to get less accurate, but still useful timing information.

So if you want to get your own frame-duration logic working across all releases, you’ll need to implement these different mechanisms across the API versions. Or you could use the single JankStats API, which does it for you… in addition to providing more features on top (keep reading!).

JankStats simplifies this by providing a single API to report per-frame durations, and delegates to appropriate mechanisms internally (FrameMetrics on API 24+, etc). You don’t have to worry about where that data came from, you can just ask JankStats to tell you how long things took, and you get callbacks with that information.

Creating and listening for JankStats data is essentially that easy: you create it and then you sit back (okay, your code sits back) and listen. Here’s an example of these steps from the JankStats sample, JankLoggingActivity:

val jankFrameListener = JankStats.OnFrameListener { frameData ->
// real app would do something more interesting than log this...
Log.v("JankStatsSample", frameData.toString())
}
jankStats = JankStats.createAndTrack(
window,
Dispatchers.Default.asExecutor(),
jankFrameListener,
)

That Log.v() call is… not what you should do in your app, it’s just there for example purposes. Instead, you should probably aggregate/store/upload data for later analysis instead of just spitting it out to the log. In any case, here’s a sample of what it produces while running on an API 30 emulator (some logcat noise deleted and blank lines added for clarity):

JankStats.OnFrameListener: FrameData(frameStartNanos=827233150542009, frameDurationUiNanos=27779985, frameDurationCpuNanos=31296985, isJank=false, states=[Activity: JankLoggingActivity])JankStats.OnFrameListener: FrameData(frameStartNanos=827314067288736, frameDurationUiNanos=89903592, frameDurationCpuNanos=94582592, isJank=true, states=[RecyclerView: Dragging, Activity: JankLoggingActivity])JankStats.OnFrameListener: FrameData(frameStartNanos=827314167288732, frameDurationUiNanos=88641926, frameDurationCpuNanos=91526926, isJank=true, states=[RecyclerView: Settling, RecyclerView: Dragging, Activity: JankLoggingActivity])JankStats.OnFrameListener: FrameData(frameStartNanos=827314183945923, frameDurationUiNanos=4731405, frameDurationCpuNanos=8283405, isJank=false, states=[RecyclerView: Settling, Activity: JankLoggingActivity])

You can see a couple of interesting pieces in the logged frameData:

  • There are some frames with isJank=true. The log is taken from a run of the sample app JankLoggingActivity, which is in the sample linked below. That app forces some long frames (thanks, Thread.sleep()!), which result in this jank determination by JankStats.
  • The frame duration information has both UI and CPU data. Prior to API 24 (when FrameMetrics was introduced), there is only UI duration.
  • The log is taken from the point in the app when I started flinging the RecyclerView. We can see information about the UI state from before that started (with only the Activity state listed), as the RecyclerView starts to move (“Dragging”), and as the RecyclerView is scrolling (“Settling”). More about providing this kind of UI state below.

Real World Data

Unlike the recent benchmarking libraries, JankStats was created to provide results from user devices. It’s great to debug problems on your development machine, but that doesn’t help for those situations where your app is being used by real people in the real world, on very different devices, under far different constraints.

JankStats offers an API to instrument your application to provide the performance data you need and a reporting mechanism so that you can get that data for uploading and analyzing offline.

State of Mind

Finally (listen up: this is the really new thing with this library), JankStats provides a way to understand what was actually happening in your app when performance problems occurred. A complaint we’ve heard often is that the existing tools, dashboards, and approaches don’t give you enough context for the performance problems that your users might be seeing.

For example, the FrameMetrics API (introduced in API 24, and used internally in JankStats) can tell you how long frames are taking to draw, from which you can derive jank information. But it can’t tell you what was happening in your app at the time. It leaves that problem up to you to figure out, as you try to instrument your code and integrate it with FrameMetrics or other performance measurement tools. But everyone has enough to do without having to build this kind of infrastructure internally, so jank typically goes unmeasured and performance problems continue.

Similarly, the Android Vitals dashboard can tell you that your app is having performance problems, but it can’t tell you what your app was doing when those problems occurred. So it’s difficult to know what to do with that information.

JankStats introduces the PerformanceMetricsState API, a simple set of methods that allow you to tell the system what is happening in your application at any time, via pairs of Strings. For example, you might want to note when a particular Activity or Fragment are active, or when a RecyclerView is being scrolled.

For example, here is the code from the JankStats sample, showing how to instrument a RecyclerView to provide this information to JankStats:

val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int)
{
val metricsState = metricsStateHolder?.state ?: return
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
metricsState.addState("RecyclerView", "Dragging")
}
RecyclerView.SCROLL_STATE_SETTLING -> {
metricsState.addState("RecyclerView", "Settling")
}
else -> {
metricsState.removeState("RecyclerView")
}
}
}
}

This state can be injected from anywhere in your application (or even from another library), and it will be picked up by JankStats when it reports results. That way, when you get reports from JankStats, you find out not only how long things took on each frame, but what the user was doing during that frame that might be a contributing factor.

Resources

Here are some resources for you to learn more about JankStats:

AndroidX project: JankStats is in the androidx.metrics library in AndroidX.

Docs: Our developer website has a new developer guide that describes how to use JankStats.

Sample code: This project has samples that show how to instantiate and listen to JankStats objects, along with how to instrument your app with important UI state information:

Bugs, bugs, bugs: If you have any problems with the library, or have API requests, please file a bug.

Alpha -> 1.0

JankStats has just launched its first alpha release, which means “We think this is the API and functionality that makes sense for the 1.0 release, but please try it out and let us know.”

There are other things we’d like to do with JankStats in the future, including adding some kind of aggregation mechanism, or even sync’ing with existing upload services. But we wanted to get this first version out with the basic plumbing to see how you use it and what else you would like to see. We hope that it will be useful in its current, basic state; just the ability to easily instrument and then log UI state information should be better than… not having that capability.

So get it, play with it, and let us know if you have any problems. And most importantly; find and fix those performance problems! Your users are waiting on you — don’t make them wait too long!

--

--

Chet Haase
Android Developers

Past: Android development Present: Student, comedy writer Future: ???