9.
Profile CPU Activity
Written by Zac Lippard
Efficiently using the CPU is critical when it comes to your Android app. It determines the difference between jittery and smooth UI and significantly impacts a device’s battery life.
It’s important to keep in mind your app’s impact on resources. Your app should run smoothly and feel fluid throughout. A choppy app won’t bring delight to your end-user.
Your app should also be efficient and not have unfettered access to system resources. Extensive use of system resources causes other applications to delay their operations. This also drains the battery and could lower the app’s overall performance.
This chapter covers the Android Studio CPU Profiler and how you can use this tool to track CPU usage.
You’ll learn:
- All about CPU traces including system traces and method/function traces.
- Recording traces.
- Import and export traces.
- Inspect traces using the many different charting formats available in the CPU Profiler.
- Use the third-party tool Perfetto to inspect system traces.
The CPU Profiler
Android Studio provides the CPU Profiler as a tool to measure CPU usage in real-time as well as to monitor active threads of an Android process. If you’re noticing a performance issue with your app, the Profiler is there to help!
To start, open the Podplay starter project in Android Studio and run the application. Once the app is running, you can connect the Profiler to your app’s process. To do so, click View ▸ Tool Windows ▸ Profiler, or simply click the Profiler tab in the bottom toolbar.
In the Profiler window, you’ll notice there’s not really much happening there yet. You now need to connect the Profiler to your running Podplay app process.
- Click the + icon in the top-left of the Profiler window in the Sessions toolbar.
- Select your device.
- Choose the Podplay app process to connect to.
You’ll notice a new entry on the left in the Sessions column. On the right, the Profiler has started collecting data. The session will remain until you close Android Studio. There are sections for CPU, Memory, Networking, and Energy. For the purposes of this chapter, you’ll focus on the CPU section.
Double-click the CPU section, and the Profiler will update to show the CPU panel.
To the left of the CPU panel is a section to create trace recordings and configurations.
On the right-hand side, there’s a series of timelines.
Each timeline section provides unique details about how your app is interacting with the CPU:
- Event timeline: UI rendering and interaction data shows up here. Activity and fragment lifecycle calls will appear in this timeline. Any user interactions such as button taps or text input will also display in this section.
- CPU timeline: This timeline displays the CPU utilization as a percentage. The timeline will also show CPU utilization belonging to other processes to compare your app against.
- Thread activity timeline: Active threads that are running as part of the Podplay application process. A green section indicates the thread is active and running, or at least in a runnable state. Yellow represents that the thread is active but waiting for an IO operation. Gray shows threads that are sleeping and not consuming any CPU.
Standalone Profiler
Don’t want to run the entire Android Studio IDE just to launch the Profiler? No worries! There’s a way to launch just the Profiler as a standalone tool.
First, ensure Android Studio isn’t running the profiler, or close Android Studio altogether. Next, open a terminal window to the bin folder under the Android Studio installation:
-
Windows/Linux:
<studio-installation-folder>/bin -
macOS:
<studio-installation-folder>/Contents/bin
Then, depending on your OS, run profiler.exe or profiler.sh.
A new Android Studio profiler dialog will appear where you can start new sessions, same as listed above via the Profiler window.
One of the critical components of the CPU Profiler is the ability to record traces. However, before diving into recording and configuring traces, you’ll learn what traces are.
Traces
A trace is a set of data that details how a process and its threads interact with the CPU and other resources. Typically the trace data is extremely thorough and covers method/function calls, rendering data, lifecycle calls on the Activity or Fragment and user input. All of these combined helps to paint a picture of how your app performs and what resources it’s using.
There are two types of traces:
- System Traces: Details how your app as a whole interacts with resources on the given device.
- Method/Function Traces: Provides information about your app’s Java methods and C/C++ functions. CPU utilization allocated to the method is also recorded in the trace. These types of traces also show a chain of caller and callee methods, very similar to a stack trace.
You can use trace data to see where your app may be utilizing more resources than necessary.
Now that you know what traces are, it’s time to record one!
Recording Traces
Run the Podplay project in Android Studio. Start a new CPU Profiler session by clicking on the Profiler window tab at the bottom of Android Studio. Click the + icon and select the device and Podplay process to launch the new session. Now, click the CPU timeline to open the CPU Panel.
Recording Configurations
As mentioned earlier in the chapter, the left column shows a number of report configurations.
The configurations will capture different data depending on your need:
-
Callstack Sample Recording: This will capture Java/Kotlin method and C/C++ function call stack information using
simpleperf. For native code sampling, you must be recording against a device running Android 8.0+ (API 26+).simpleperfis a command-line tool that you can use to run trace reports outside of Android Studio and the CPU Profiler itself. The tool has several options you can configure to this recording configuration. For more details, read the Android documentation. -
System Trace Recording: Provides fine-grained details about the system resources, like CPU core utilization and memory usage. You’ll need an Android 7.0+ (API 24+) device to record with this configuration.
-
Java/Kotlin Method Trace Recording: This configuration adds instrumentation to your app at runtime. The instrumentation adds timestamps to the beginning and end of each method in the call stack. These timestamps are then used and aggregated to calculate app timing as well as CPU utilization. Note that this configuration adds significant overhead due to the required instrumentation. For short-lived methods, this configuration could result in inaccurate time data. If your app makes a large number of method calls throughout the recording, it could cause the Profiler to exceed its maximum file size limit.
-
Java/Kotlin Method Sample Recording (legacy): Similar to the Callstack Sample Recording option but uses the legacy
systracetool. This works on devices using Android 4.3+ (API 18+).
Recording a Trace
To record a trace from the CPU Panel, click Record.
You’ll notice that once the recording starts, the CPU timeline background changes color, and there’s an “In progress” tooltip in the timeline at the start of the recording.
Note: Depending on your workstation, trace recordings can be resource intensive, especially if you’re using an emulator or the method trace recording configuration. If you notice latency while recording, try switching over to an actual device to reduce some of the overhead on your workstation.
Now that you’ve collected some data in the recording, click Stop.
Notice that the active session adds the new recording:
The recording entry includes the configuration type and a timestamp of when the recording occurred during the profiler session.
Recording at App Startup
You can create trace recordings during the app startup as well. This is useful for monitoring resources and code during the launch of the Application and initial Activity classes where you might not be quick enough to attach the Profiler manually.
First, click Select Run/Debug Configuration at the top of Android Studio. Click Edit Configurations….
Click the Profiling tab. Here you can check the Start this recording on startup option. Select the CPU activity option and choose one of the record configurations.
Click OK to save the run configuration and close the dialog. Then, in Android Studio, choose the Run ▸ Profile… menu option, or click on the Profile ‘app’ icon:
Notice that the Profiler immediately starts when the app launches. A new session will appear in the left column along with the recorded trace during the app’s run.
Multiple recordings all appear under the associated session:
Exporting a Recording
Now that you’ve acquired a number of recorded traces, it’s time to export them. Hover over one of the recordings, and you’ll notice a save icon that you can click.
Click the save icon, then in the new dialog, click Save. Your trace recording is now saved in a .trace file.
Importing a Recording
You can also import recordings to Android Studio. This is useful if you want to share and access a recording from another workstation or with other developers.
To import a recording, click the Profiler tab to open the profiler window.
Next, click the + icon and choose Load from file….
Select the recording that you exported in the previous section. A new session will appear with the imported trace recording:
The trace filename and the associated recording configuration type appear in the session entry.
Congratulations on successfully importing a recording! Now that you’ve gathered the trace data, it’s time to inspect it.
Inspecting Method Traces
Within the CPU Profiler, there are a number of ways to be able to inspect the trace data. You’ll learn about the different charting approaches in this section and the benefits of using each one.
To start inspecting a trace, you’ll need to record a new one.
Begin by running Podplay in Android Studio. Once the recording has started, tap Search. Search for “android”. Once the podcasts load, it’s time to start the profiler session.
Start a new profiler session and record a new trace using the Java/Kotlin Method Trace Recording configuration. Once the recording starts, go back to the app and tap on a podcast. When the podcast details page loads, go back to the profiler window in Android Studio and click Stop to end the recording.
Now that you have a trace, it’s time to use the different charts to inspect it!
Call Chart
The first charting option is the Call Chart. It appears under the Threads section of the trace. This chart provides a visual aid of the method and function calls made during the timeframe of the recording.
- The CPU Usage timeline shows an overall CPU utilization as a timeline. You can drag the vertical blue start and end lines to focus on a specific portion of the timeline. Doing so adjusts the associated data and will update the details in the other places like the Threads section.
- Select the main thread and expand it by clicking the arrow. Notice that the call chart updates and reflects all the methods and native functions called by the app during the time window you selected. There are three different colors used to represent the different method calls in the chart:
- Orange indicates calls by the app to system APIs.
- Green are the app’s own methods.
- Blue are third-party API calls.
- The method calls in the Threads section display from top to bottom, where the topmost method is the first call, and the subsequent method below is the next call in the list. In this example, there are system calls at the top that launch a coroutine that eventually fires
PodcastActivity$onShowDetails$1.invokeSuspend(). This method then calls the next method in the listPodcastViewModel.getPodcast(). The call list continues down from there. - Notice how the method lengths get shorter the lower down the list you go? You can think of methods below a certain method in the list as “children”. Each child runs as long or as short as the parent. In the example above, the child method
getEndIdx()runs for a fraction of the time it’s parent callerFormatter.parse()ran. These methods are all descendants ofPodcastViewModel.getPodcast().
The call chart in the Threads section is extremely valuable for determining what methods are taking longer than expected. You can drill down deeper to see if there are unnecessary calls to other methods or if there’s a child method that’s an underlying culprit to poor performance in the app.
Looking at Top Down Charts
The Top Down Chart provides similar details to the Call Chart mentioned earlier but with more fine-grained details about each method. To use this chart, switch to the Analysis section on the right-hand side.
Here’s the breakdown:
- Notice that the
mainthread is still selected. - Choose the Top Down tab to view the methods.
- The call stack will display somewhat expanded. You can continue to expand them by clicking the arrows on each method to see its children. In the screenshot,
loadPodcast()expands along with it’s childexecute(). The nestedexecute()then has a series of methods it calls. In the top down chart, child methods appear below their parents. - The nested
execute()has a series of child methods within it. Notice thatexecute(), along with its ancestor methods, took 48,244 microseconds (~48 milliseconds) to run. The Total (μs) column represents the total amount of time the method took to execute code within itself and all the time its children took. The childinvokeOnCancellation()took a total of 3,373 microseconds. - Regarding
invokeOnCancellation()again, you’ll notice in the Self (μs) column that 198 microseconds were allocated to the method itself for execution. - The remaining 3,175 microseconds were allocated to the children of
invokeOnCancellation()via the Children (μs) column.
The granular breakdown of this chart provides you with a view of how long each method and child are taking. You can use this data to better understand how long methods are taking and begin to pinpoint why they run as long as they do.
Looking at Flame Charts
The Flame Chart option is an inverted Call Chart. This way, ancestor methods are at the bottom, and the descendants appear above them. This causes the visualization of the chart to appear like flames, hence the name.
Follow these steps:
- Click the Flame Chart tab at the top.
- Note that the methods towards the top get smaller as these represent the child methods that will inevitably take less time to run than their parents, creating the appearance of flames burning across the screen.
- You can hover your mouse over a method in the chart to get details about the method, such as the full method name, beginning and ending timestamps and the overall time taken in milliseconds. In this example, you can see that
PodcastViewModel.getPodcast()took a total of 48 milliseconds to run.
The colors for the chart are slightly different compared to the Call Chart, but the same method types are represented here:
- Dark orange represents the third part API method calls.
- Light orange shows methods that belong to the app.
- Red displays system API calls.
Looking at Bottom Up Charts
As you may have guessed, the Bottom Up Chart is very similar to the Top Down Chart but inverted, in a way.
In this chart, you’ll see all the methods called over the period of the recording at the top level. The difference though is that ancestor methods will have less associated with them.
- Click the Bottom Up tab to view this chart type.
- Search for
invokeOnCancellation()in the search box at the top. - Notice that
main()can’t expand. It’s the root method and has no other parent tied to it. - Because of the search criteria, only the methods associated with
invokeOnCancellation()will remain. That method itself will also be bold. You can expand this method to viewexecute()as its parent. You can keep expanding each ancestor method all the way back up tomain(). Every method eventually routes back tomain().
The same columns are here in this chart as in the Top Down Chart to represent total/self/child time and percentage.
Note: With any charts under the Analysis section, you can use the search box to bold methods that fit the search criteria. This is useful when you have many methods that you’re trying to look through.
Events
The Events tab is useful to see all the calls for the thread selected in the Threads timeline.
Click an event to update the Call Chart in the Threads timeline to reflect the associated methods tied to the event.
You’ve taken a deeper dive into method and function tracing, but now it’s time to see the power behind system traces and what insights they provide into system resources.
Inspecting System Traces
As mentioned earlier, system traces provide fine-grained details on the system resources allocated to the app on a given device. This provides a different approach to understanding how performant your app is and how efficient it runs within the Android ecosystem.
Firstly, record a trace, but this time with the System Trace Recording configuration. You can follow the same steps as you did earlier by running the app and searching for “android” podcasts. Once the list of podcasts displays, stop the recording.
You’ll now see the recording displayed in the profiler window.
UI Interaction
The Interaction timeline shows you what the user did during the time frame captured.
Action data recorded here are things like taps and text input. Lifecycle-related information for activities and fragments is also displayed here. You can hover over a specific interaction to get more details about the event’s length.
The Display timeline shows information about drawn frames and what is set to draw next from the buffer.
The attributes for this timeline are:
-
Frames: A frame is a UI rendering created by Android and displayed on the screen. The frame drawing measurement happens here. Long draw times display in red.
-
SurfaceFlinger: This system process is responsible for sending buffer data to display.
-
VSYNC: The signal indicates when to synchronize the display. This is a binary value of either 0 or 1.
-
BufferQueue: Determines how many buffers are in the queue. These buffers then get processed by the SurfaceFlinger.
You can hover over the data in the timeline to see the values for each of these.
The Frame Lifecycle timeline shows the lifecycle of a particular “frame” that displays to the user. Activity and Fragment instances build the screen associated with a given frame. In this example, the frame in the timeline associates with PodcastActivity.
The attributes measured here are as follows:
- Application: The duration of the app drawing the frame to the screen.
- Wait for GPU: The time required for the GPU to complete the drawing.
- Composition: The composition time taken by the SurfaceFlinger.
- Frames on display: How long the frame displays on the screen.
UI Jank
Jank refers to an unpleasant or unstable experience within an app. Specific to the user experience, UI jank is often referring to jittery UI that feels unnatural or jarring to the end-user.
Aside from visually inspecting the running app, the most common way to determine jank is to look for skipped frames in Logcat.
For devices running Android 12+, there is also a way to detect these janky frames from a system trace:
-
When there is jitter in the UI, the Janky frames section will appear in the Display timeline of a system trace.
-
Click a frame in the section to view details about the jank type as well as expected and actual durations it took for the frame to render.
CPU Cores
The CPU Cores timeline gives insight into how the work your app requires allocates to the device’s CPU. This section lists each CPU core and frequency.
You can hover over a particular thread on one of the cores to get more information. Some of these details include how long each thread is taking and the associated process.
Process Memory (RSS)
The Process Memory timeline provides details about the app’s memory footprint. This footprint, generally known as the Resident Set Size or RSS, describes how much memory the app uses in RAM.
This timeline breaks down the memory usage between shared and non-shared memory requirements. Non-shared memory is specifically allocated to the app for normal and file mapping purposes.
System Trace Formats
There are two primary formats for system traces on Android: Perfetto and Systrace.
In Android 10 and higher, system trace files save in the Perfetto format. This format includes more system data sources than Systrace does. You can record much longer traces with the Perfetto format. Along with the format, Perfetto is also the platform-wide tracing tool. Android, Linux and even Chrome all use this format. You can also use this tool via the command line with perfetto.
Systrace is a legacy system trace tool and format used in Android 4.3 and higher. Systrace files save as compressed text files. Thus, they cannot contain as much data as their Perfetto counterpart. While Systrace is available on Android 10+ devices, Perfetto is the recommended format.
Inspection With Perfetto UI
A situation may arise where you have a system trace file but don’t have Android Studio installed. Fear not, for there is a third-party alternative that you can use!
Perfetto UI is an online tool that you can use to parse and inspect Perfetto and Systrace formatted system traces.
To begin, take one of the system traces you recorded earlier in this chapter and go to Perfetto UI.
In the left column, click Open trace file and open either your Perfetto or Systrace formatted trace file.
Once you’ve selected your file, Perfetto UI will update and display the timeline.
- Overall CPU activity at the top.
- CPU core processing details.
- Process breakdown by CPU activity.
- Process and thread details.
Challenge
Remember the topic about UI jank? Well, it turns out there’s some jankiness in this chapter’s starter and final Podplay app as well. Open the Logcat window and run the Podplay app. Tap Search and search for “android”. When the list of podcasts appears, tap any. Then, tap on a podcast episode to launch the player. You’ll notice a delay before the loading spinner appears. During this time, you’ll see logs stating Skipped X frames!.
Using the knowledge you gained from this chapter, record and inspect method or system traces to fix the UI jank and make it run smoothly!
Note: The jank specific to this challenge is only in this chapter’s starter and final Podplay projects. It isn’t present in any of the other chapter’s starter and final projects.
Key Points
- System traces measure performance on system resources and the UI.
- Method/function traces measure performance of targeted Java/Kotlin methods and C/C++ functions.
- Traces can be imported and exported with Android Studio.
- The CPU Profiler provides inspection on traces.
- You can analyze traces with a number of charting options provided by the CPU Profiler.
- Perfetto UI is an alternative to the CPU Profiler to analyze system traces.
Where to Go From Here?
You covered a lot of ground regarding CPU profiling. There’s still so much more to explore regarding this topic!
Check out the Android developer CPU Profiler doc for more details on what the Android Studio CPU Profiler has to offer. Interested in learning more about the types of system tracing tools at your disposal? Check out this system tracing Android developer doc.
Now that you’ve learned about the CPU aspect of the Profiler, it’s time to take a look at how you can use the Profiler for memory usage.