1# Android application profiling 2 3This section shows how to profile an Android application. 4Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/README.md). 5 6Profiling an Android application involves three steps: 71. Prepare an Android application. 82. Record profiling data. 93. Report profiling data. 10 11[TOC] 12 13## Prepare an Android application 14 15Based on the profiling situation, we may need to customize the build script to generate an apk file 16specifically for profiling. Below are some suggestions. 17 181. If you want to profile a debug build of an application: 19 20For the debug build type, Android studio sets android::debuggable="true" in AndroidManifest.xml, 21enables JNI checks and may not optimize C/C++ code. It can be profiled by simpleperf without any 22change. 23 242. If you want to profile a release build of an application: 25 26For the release build type, Android studio sets android::debuggable="false" in AndroidManifest.xml, 27disables JNI checks and optimizes C/C++ code. However, security restrictions mean that only apps 28with android::debuggable set to true can be profiled. So simpleperf can only profile a release 29build under these three circumstances: 30If you are on a rooted device, you can profile any app. 31 32If you are on Android >= Q, you can add profileableFromShell flag in AndroidManifest.xml, this makes 33a released app profileable by preinstalled profiling tools. In this case, simpleperf downloaded by 34adb will invoke simpleperf preinstalled in system image to profile the app. 35 36``` 37<manifest ...> 38 <application ...> 39 <profileable android:shell="true" /> 40 </application> 41</manifest> 42``` 43 44If you are on Android >= O, we can use [wrap.sh](https://developer.android.com/ndk/guides/wrap-script.html) 45to profile a release build: 46Step 1: Add android::debuggable="true" in AndroidManifest.xml to enable profiling. 47``` 48<manifest ...> 49 <application android::debuggable="true" ...> 50``` 51 52Step 2: Add wrap.sh in lib/`arch` directories. wrap.sh runs the app without passing any debug flags 53to ART, so the app runs as a release app. wrap.sh can be done by adding the script below in 54app/build.gradle. 55``` 56android { 57 buildTypes { 58 release { 59 sourceSets { 60 release { 61 resources { 62 srcDir { 63 "wrap_sh_lib_dir" 64 } 65 } 66 } 67 } 68 } 69 } 70} 71 72task createWrapShLibDir 73 for (String abi : ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) { 74 def dir = new File("app/wrap_sh_lib_dir/lib/" + abi) 75 dir.mkdirs() 76 def wrapFile = new File(dir, "wrap.sh") 77 wrapFile.withWriter { writer -> 78 writer.write('#!/system/bin/sh\n\$@\n') 79 } 80 } 81} 82``` 83 843. If you want to profile C/C++ code: 85 86Android studio strips symbol table and debug info of native libraries in the apk. So the profiling 87results may contain unknown symbols or broken callgraphs. To fix this, we can pass app_profiler.py 88a directory containing unstripped native libraries via the -lib option. Usually the directory can 89be the path of your Android Studio project. 90 91 924. If you want to profile Java code: 93 94On Android >= P, simpleperf supports profiling Java code, no matter whether it is executed by 95the interpreter, or JITed, or compiled into native instructions. So you don't need to do anything. 96 97On Android O, simpleperf supports profiling Java code which is compiled into native instructions, 98and it also needs wrap.sh to use the compiled Java code. To compile Java code, we can pass 99app_profiler.py the --compile_java_code option. 100 101On Android N, simpleperf supports profiling Java code that is compiled into native instructions. 102To compile java code, we can pass app_profiler.py the --compile_java_code option. 103 104On Android <= M, simpleperf doesn't support profiling Java code. 105 106 107Below I use application [SimpleperfExampleCpp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/SimpleperfExampleCpp). 108It builds an app-debug.apk for profiling. 109 110```sh 111$ git clone https://android.googlesource.com/platform/system/extras 112$ cd extras/simpleperf/demo 113# Open SimpleperfExampleCpp project with Android studio, and build this project 114# successfully, otherwise the `./gradlew` command below will fail. 115$ cd SimpleperfExampleCpp 116 117# On windows, use "gradlew" instead. 118$ ./gradlew clean assemble 119$ adb install -r app/build/outputs/apk/debug/app-debug.apk 120``` 121 122## Record and report profiling data 123 124We can use [app-profiler.py](scripts_reference.md#app_profilerpy) to profile Android applications. 125 126```sh 127# Cd to the directory of simpleperf scripts. Record perf.data. 128# -p option selects the profiled app using its package name. 129# --compile_java_code option compiles Java code into native instructions, which isn't needed on 130# Android >= P. 131# -a option selects the Activity to profile. 132# -lib option gives the directory to find debug native libraries. 133$ ./app_profiler.py -p simpleperf.example.cpp -a .MixActivity -lib path_of_SimpleperfExampleCpp 134``` 135 136This will collect profiling data in perf.data in the current directory, and related native 137binaries in binary_cache/. 138 139Normally we need to use the app when profiling, otherwise we may record no samples. But in this 140case, the MixActivity starts a busy thread. So we don't need to use the app while profiling. 141 142```sh 143# Report perf.data in stdio interface. 144$ ./report.py 145Cmdline: /data/data/simpleperf.example.cpp/simpleperf record ... 146Arch: arm64 147Event: task-clock:u (type 1, config 1) 148Samples: 10023 149Event count: 10023000000 150 151Overhead Command Pid Tid Shared Object Symbol 15227.04% BusyThread 5703 5729 /system/lib64/libart.so art::JniMethodStart(art::Thread*) 15325.87% BusyThread 5703 5729 /system/lib64/libc.so long StrToI<long, ... 154... 155``` 156 157[report.py](scripts_reference.md#reportpy) reports profiling data in stdio interface. If there 158are a lot of unknown symbols in the report, check [here](README.md#how-to-solve-missing-symbols-in-report). 159 160```sh 161# Report perf.data in html interface. 162$ ./report_html.py 163 164# Add source code and disassembly. Change the path of source_dirs if it not correct. 165$ ./report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleCpp \ 166 --add_disassembly 167``` 168 169[report_html.py](scripts_reference.md#report_htmlpy) generates report in report.html, and pops up 170a browser tab to show it. 171 172## Record and report call graph 173 174We can record and report [call graphs](executable_commands_reference.md#record-call-graphs) as below. 175 176```sh 177# Record dwarf based call graphs: add "-g" in the -r option. 178$ ./app_profiler.py -p simpleperf.example.cpp \ 179 -r "-e task-clock:u -f 1000 --duration 10 -g" -lib path_of_SimpleperfExampleCpp 180 181# Record stack frame based call graphs: add "--call-graph fp" in the -r option. 182$ ./app_profiler.py -p simpleperf.example.cpp \ 183 -r "-e task-clock:u -f 1000 --duration 10 --call-graph fp" \ 184 -lib path_of_SimpleperfExampleCpp 185 186# Report call graphs in stdio interface. 187$ ./report.py -g 188 189# Report call graphs in python Tk interface. 190$ ./report.py -g --gui 191 192# Report call graphs in html interface. 193$ ./report_html.py 194 195# Report call graphs in flamegraphs. 196# On Windows, use inferno.bat instead of ./inferno.sh. 197$ ./inferno.sh -sc 198``` 199 200## Report in html interface 201 202We can use [report_html.py](scripts_reference.md#report_htmlpy) to show profiling results in a web browser. 203report_html.py integrates chart statistics, sample table, flamegraphs, source code annotation 204and disassembly annotation. It is the recommended way to show reports. 205 206```sh 207$ ./report_html.py 208``` 209 210## Show flamegraph 211 212To show flamegraphs, we need to first record call graphs. Flamegraphs are shown by 213report_html.py in the "Flamegraph" tab. 214We can also use [inferno](scripts_reference.md#inferno) to show flamegraphs directly. 215 216```sh 217# On Windows, use inferno.bat instead of ./inferno.sh. 218$ ./inferno.sh -sc 219``` 220 221We can also build flamegraphs using https://github.com/brendangregg/FlameGraph. 222Please make sure you have perl installed. 223 224```sh 225$ git clone https://github.com/brendangregg/FlameGraph.git 226$ ./report_sample.py --symfs binary_cache >out.perf 227$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded 228$ FlameGraph/flamegraph.pl out.folded >a.svg 229``` 230 231## Report in Android Studio 232 233simpleperf report-sample command can convert perf.data into protobuf format accepted by 234Android Studio cpu profiler. The conversion can be done either on device or on host. If you have 235more symbol info on host, then prefer do it on host with --symdir option. 236 237```sh 238$ simpleperf report-sample --protobuf --show-callchain -i perf.data -o perf.trace 239# Then open perf.trace in Android Studio to show it. 240``` 241 242## Deobfuscate Java symbols 243 244Java symbols may be obfuscated by ProGuard. To restore the original symbols in a report, we can 245pass a Proguard mapping file to the report scripts or report-sample command via 246`--proguard-mapping-file`. 247 248```sh 249$ ./report_html.py --proguard-mapping-file proguard_mapping_file.txt 250``` 251 252## Record both on CPU time and off CPU time 253 254We can [record both on CPU time and off CPU time](executable_commands_reference.md#record-both-on-cpu-time-and-off-cpu-time). 255 256First check if trace-offcpu feature is supported on the device. 257 258```sh 259$ ./run_simpleperf_on_device.py list --show-features 260dwarf-based-call-graph 261trace-offcpu 262``` 263 264If trace-offcpu is supported, it will be shown in the feature list. Then we can try it. 265 266```sh 267$ ./app_profiler.py -p simpleperf.example.cpp -a .SleepActivity \ 268 -r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu" \ 269 -lib path_of_SimpleperfExampleCpp 270$ ./report_html.py --add_disassembly --add_source_code \ 271 --source_dirs path_of_SimpleperfExampleCpp 272``` 273 274## Profile from launch 275 276We can [profile from launch of an application](scripts_reference.md#profile-from-launch-of-an-application). 277 278```sh 279# Start simpleperf recording, then start the Activity to profile. 280$ ./app_profiler.py -p simpleperf.example.cpp -a .MainActivity 281 282# We can also start the Activity on the device manually. 283# 1. Make sure the application isn't running or one of the recent apps. 284# 2. Start simpleperf recording. 285$ ./app_profiler.py -p simpleperf.example.cpp 286# 3. Start the app manually on the device. 287``` 288 289## Control recording in application code 290 291Simpleperf supports controlling recording from application code. Below is the workflow: 292 2931. Run `api_profiler.py prepare -p <package_name>` to allow an app recording itself using 294 simpleperf. By default, the permission is reset after device reboot. So we need to run the 295 script every time the device reboots. But on Android >= 13, we can use `--days` options to 296 set how long we want the permission to last. 297 2982. Link simpleperf app_api code in the application. The app needs to be debuggable or 299 profileableFromShell as described [here](#prepare-an-android-application). Then the app can 300 use the api to start/pause/resume/stop recording. To start recording, the app_api forks a child 301 process running simpleperf, and uses pipe files to send commands to the child process. After 302 recording, a profiling data file is generated. 303 3043. Run `api_profiler.py collect -p <package_name>` to collect profiling data files to host. 305 306Examples are CppApi and JavaApi in [demo](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo). 307 308 309## Parse profiling data manually 310 311We can also write python scripts to parse profiling data manually, by using 312[simpleperf_report_lib.py](scripts_reference.md#simpleperf_report_libpy). Examples are report_sample.py, 313report_html.py. 314