System tracing (ATrace) your Android apps for better performance
Using systrace and OpenTrace on release-style builds
System tracing involves recording device information about an app or system component execution. The main purposes of system tracing are
- understanding what tasks are running during a specific workflow (e.g., app startup)
- measure how long those tasks take to execute
The workflow for system tracing is:
- identify a scenario to trace (e.g., app startup, or performing a time-sensitive scenario for the app)
- add any trace calls if the scenario is not well traced
- reset the device to the “before-the-scenario” state (e.g., for startup, the process is killed)
- start recording the system trace
- perform the identified scenario in (1)
- stop recording the system trace
- analyze the system trace
- fix any bottlenecks, go back to (2)
Both the Android SDK and NDKs have APIs to feed into a system trace:
- From the Android SDK (Java/Kotlin/JVM):
android.os.Trace
is the API. We will only useTrace#beginSection
andTrace#endSection
. - From the Android NDK (C/C++):
ATrace_*
fromtrace.h
.ATrace_beginSection
andATrace_endSection
are the corresponding functions.
Tracing setup for non-debug builds
If you start adding calls to Trace.{begin,end}section
in your codebase and then record a trace, you’ll be disappointed because the trace won’t contain the sections you added.
Trace System Calls (aka systrace, perfetto): Low impact on runtime, great for understanding how the app interacts with the system and CPUs, but not the Java method calls that happen inside the app VM. ― Py’s post: Android Vitals — Profiling App Startup
There are multiple ways to record system traces for your own custom sections in the app’s VM for non-debug builds.
- (API 29 / Android 10 and above) Add the
profileableFromShell
element to your AndroidManifest. Typically you’ll want to capture system traces from older devices, so this isn’t a great solution. - (API 27 / Android 8.1 and above) Use the “wrap hack”, which involves marking the “release” (or better a new one called “perf”) build type as debuggable in the manifest, and then using a script (usually called
wrap.sh
) to run the app without the debug flags in ART. - (Recommended) (API 18 / Android 4.3.x and above) Use reflection to enable tracing. The function
Trace#setAppTracingAllowed
controls whether the sections in your application can be traced. This function is indeed called withtrue
iff the application is marked as debuggable or profileable. This can be done with reflection. Don’t be scared, you can configure that code to only run on a special “perf” build type and won’t make it to production.
OpenTrace
OpenTrace is a library I created that packages solution (3) above and comes with a sample app to show how to configure a “perf” build type to enable tracing.
Add OpenTrace
implementation 'com.qflair.opentrace:2.0.0'
Identify the scenario to trace
In this case, we’ll trace the scenario that is applicable to all apps: startup.
Start recording the system trace
There’s a couple of ways to record the trace for app startup. One is from Android Studio (API 26 / Android 8.0 and above)
The other one is to record the start the trace from the command line (while the process is gone) and start the app while recording:
$ python $ANDROID_HOME/platform-tools/systrace/systrace.py -a com.example.opentrace.perf
If we only want to include our app’s traces, we’d add the app
option at the end of the above command. When we stop recording the trace, we are provided with an HTML file that contains all the default categories for a system trace and our own custom sections!
There is a lot of information on the Android documentation on how to read and interpret these reports.
We hope you found this useful. Happy hacking!