314 lines
11 KiB
Markdown
314 lines
11 KiB
Markdown
# Android application profiling
|
|
|
|
This section shows how to profile an Android application.
|
|
Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/README.md).
|
|
|
|
Profiling an Android application involves three steps:
|
|
1. Prepare an Android application.
|
|
2. Record profiling data.
|
|
3. Report profiling data.
|
|
|
|
[TOC]
|
|
|
|
## Prepare an Android application
|
|
|
|
Based on the profiling situation, we may need to customize the build script to generate an apk file
|
|
specifically for profiling. Below are some suggestions.
|
|
|
|
1. If you want to profile a debug build of an application:
|
|
|
|
For the debug build type, Android studio sets android::debuggable="true" in AndroidManifest.xml,
|
|
enables JNI checks and may not optimize C/C++ code. It can be profiled by simpleperf without any
|
|
change.
|
|
|
|
2. If you want to profile a release build of an application:
|
|
|
|
For the release build type, Android studio sets android::debuggable="false" in AndroidManifest.xml,
|
|
disables JNI checks and optimizes C/C++ code. However, security restrictions mean that only apps
|
|
with android::debuggable set to true can be profiled. So simpleperf can only profile a release
|
|
build under these three circumstances:
|
|
If you are on a rooted device, you can profile any app.
|
|
|
|
If you are on Android >= Q, you can add profileableFromShell flag in AndroidManifest.xml, this makes
|
|
a released app profileable by preinstalled profiling tools. In this case, simpleperf downloaded by
|
|
adb will invoke simpleperf preinstalled in system image to profile the app.
|
|
|
|
```
|
|
<manifest ...>
|
|
<application ...>
|
|
<profileable android:shell="true" />
|
|
</application>
|
|
</manifest>
|
|
```
|
|
|
|
If you are on Android >= O, we can use [wrap.sh](https://developer.android.com/ndk/guides/wrap-script.html)
|
|
to profile a release build:
|
|
Step 1: Add android::debuggable="true" in AndroidManifest.xml to enable profiling.
|
|
```
|
|
<manifest ...>
|
|
<application android::debuggable="true" ...>
|
|
```
|
|
|
|
Step 2: Add wrap.sh in lib/`arch` directories. wrap.sh runs the app without passing any debug flags
|
|
to ART, so the app runs as a release app. wrap.sh can be done by adding the script below in
|
|
app/build.gradle.
|
|
```
|
|
android {
|
|
buildTypes {
|
|
release {
|
|
sourceSets {
|
|
release {
|
|
resources {
|
|
srcDir {
|
|
"wrap_sh_lib_dir"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
task createWrapShLibDir
|
|
for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
|
|
def dir = new File("app/wrap_sh_lib_dir/lib/" + abi)
|
|
dir.mkdirs()
|
|
def wrapFile = new File(dir, "wrap.sh")
|
|
wrapFile.withWriter { writer ->
|
|
writer.write('#!/system/bin/sh\n\$@\n')
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
3. If you want to profile C/C++ code:
|
|
|
|
Android studio strips symbol table and debug info of native libraries in the apk. So the profiling
|
|
results may contain unknown symbols or broken callgraphs. To fix this, we can pass app_profiler.py
|
|
a directory containing unstripped native libraries via the -lib option. Usually the directory can
|
|
be the path of your Android Studio project.
|
|
|
|
|
|
4. If you want to profile Java code:
|
|
|
|
On Android >= P, simpleperf supports profiling Java code, no matter whether it is executed by
|
|
the interpreter, or JITed, or compiled into native instructions. So you don't need to do anything.
|
|
|
|
On Android O, simpleperf supports profiling Java code which is compiled into native instructions,
|
|
and it also needs wrap.sh to use the compiled Java code. To compile Java code, we can pass
|
|
app_profiler.py the --compile_java_code option.
|
|
|
|
On Android N, simpleperf supports profiling Java code that is compiled into native instructions.
|
|
To compile java code, we can pass app_profiler.py the --compile_java_code option.
|
|
|
|
On Android <= M, simpleperf doesn't support profiling Java code.
|
|
|
|
|
|
Below I use application [SimpleperfExampleCpp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExampleCpp).
|
|
It builds an app-debug.apk for profiling.
|
|
|
|
```sh
|
|
$ git clone https://android.googlesource.com/platform/system/extras
|
|
$ cd extras/simpleperf/demo
|
|
# Open SimpleperfExampleCpp project with Android studio, and build this project
|
|
# successfully, otherwise the `./gradlew` command below will fail.
|
|
$ cd SimpleperfExampleCpp
|
|
|
|
# On windows, use "gradlew" instead.
|
|
$ ./gradlew clean assemble
|
|
$ adb install -r app/build/outputs/apk/debug/app-debug.apk
|
|
```
|
|
|
|
## Record and report profiling data
|
|
|
|
We can use [app-profiler.py](scripts_reference.md#app_profilerpy) to profile Android applications.
|
|
|
|
```sh
|
|
# Cd to the directory of simpleperf scripts. Record perf.data.
|
|
# -p option selects the profiled app using its package name.
|
|
# --compile_java_code option compiles Java code into native instructions, which isn't needed on
|
|
# Android >= P.
|
|
# -a option selects the Activity to profile.
|
|
# -lib option gives the directory to find debug native libraries.
|
|
$ ./app_profiler.py -p simpleperf.example.cpp -a .MixActivity -lib path_of_SimpleperfExampleCpp
|
|
```
|
|
|
|
This will collect profiling data in perf.data in the current directory, and related native
|
|
binaries in binary_cache/.
|
|
|
|
Normally we need to use the app when profiling, otherwise we may record no samples. But in this
|
|
case, the MixActivity starts a busy thread. So we don't need to use the app while profiling.
|
|
|
|
```sh
|
|
# Report perf.data in stdio interface.
|
|
$ ./report.py
|
|
Cmdline: /data/data/simpleperf.example.cpp/simpleperf record ...
|
|
Arch: arm64
|
|
Event: task-clock:u (type 1, config 1)
|
|
Samples: 10023
|
|
Event count: 10023000000
|
|
|
|
Overhead Command Pid Tid Shared Object Symbol
|
|
27.04% BusyThread 5703 5729 /system/lib64/libart.so art::JniMethodStart(art::Thread*)
|
|
25.87% BusyThread 5703 5729 /system/lib64/libc.so long StrToI<long, ...
|
|
...
|
|
```
|
|
|
|
[report.py](scripts_reference.md#reportpy) reports profiling data in stdio interface. If there
|
|
are a lot of unknown symbols in the report, check [here](README.md#how-to-solve-missing-symbols-in-report).
|
|
|
|
```sh
|
|
# Report perf.data in html interface.
|
|
$ ./report_html.py
|
|
|
|
# Add source code and disassembly. Change the path of source_dirs if it not correct.
|
|
$ ./report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleCpp \
|
|
--add_disassembly
|
|
```
|
|
|
|
[report_html.py](scripts_reference.md#report_htmlpy) generates report in report.html, and pops up
|
|
a browser tab to show it.
|
|
|
|
## Record and report call graph
|
|
|
|
We can record and report [call graphs](executable_commands_reference.md#record-call-graphs) as below.
|
|
|
|
```sh
|
|
# Record dwarf based call graphs: add "-g" in the -r option.
|
|
$ ./app_profiler.py -p simpleperf.example.cpp \
|
|
-r "-e task-clock:u -f 1000 --duration 10 -g" -lib path_of_SimpleperfExampleCpp
|
|
|
|
# Record stack frame based call graphs: add "--call-graph fp" in the -r option.
|
|
$ ./app_profiler.py -p simpleperf.example.cpp \
|
|
-r "-e task-clock:u -f 1000 --duration 10 --call-graph fp" \
|
|
-lib path_of_SimpleperfExampleCpp
|
|
|
|
# Report call graphs in stdio interface.
|
|
$ ./report.py -g
|
|
|
|
# Report call graphs in python Tk interface.
|
|
$ ./report.py -g --gui
|
|
|
|
# Report call graphs in html interface.
|
|
$ ./report_html.py
|
|
|
|
# Report call graphs in flamegraphs.
|
|
# On Windows, use inferno.bat instead of ./inferno.sh.
|
|
$ ./inferno.sh -sc
|
|
```
|
|
|
|
## Report in html interface
|
|
|
|
We can use [report_html.py](scripts_reference.md#report_htmlpy) to show profiling results in a web browser.
|
|
report_html.py integrates chart statistics, sample table, flamegraphs, source code annotation
|
|
and disassembly annotation. It is the recommended way to show reports.
|
|
|
|
```sh
|
|
$ ./report_html.py
|
|
```
|
|
|
|
## Show flamegraph
|
|
|
|
To show flamegraphs, we need to first record call graphs. Flamegraphs are shown by
|
|
report_html.py in the "Flamegraph" tab.
|
|
We can also use [inferno](scripts_reference.md#inferno) to show flamegraphs directly.
|
|
|
|
```sh
|
|
# On Windows, use inferno.bat instead of ./inferno.sh.
|
|
$ ./inferno.sh -sc
|
|
```
|
|
|
|
We can also build flamegraphs using https://github.com/brendangregg/FlameGraph.
|
|
Please make sure you have perl installed.
|
|
|
|
```sh
|
|
$ git clone https://github.com/brendangregg/FlameGraph.git
|
|
$ ./report_sample.py --symfs binary_cache >out.perf
|
|
$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
|
|
$ FlameGraph/flamegraph.pl out.folded >a.svg
|
|
```
|
|
|
|
## Report in Android Studio
|
|
|
|
simpleperf report-sample command can convert perf.data into protobuf format accepted by
|
|
Android Studio cpu profiler. The conversion can be done either on device or on host. If you have
|
|
more symbol info on host, then prefer do it on host with --symdir option.
|
|
|
|
```sh
|
|
$ simpleperf report-sample --protobuf --show-callchain -i perf.data -o perf.trace
|
|
# Then open perf.trace in Android Studio to show it.
|
|
```
|
|
|
|
## Deobfuscate Java symbols
|
|
|
|
Java symbols may be obfuscated by ProGuard. To restore the original symbols in a report, we can
|
|
pass a Proguard mapping file to the report scripts or report-sample command via
|
|
`--proguard-mapping-file`.
|
|
|
|
```sh
|
|
$ ./report_html.py --proguard-mapping-file proguard_mapping_file.txt
|
|
```
|
|
|
|
## Record both on CPU time and off CPU time
|
|
|
|
We can [record both on CPU time and off CPU time](executable_commands_reference.md#record-both-on-cpu-time-and-off-cpu-time).
|
|
|
|
First check if trace-offcpu feature is supported on the device.
|
|
|
|
```sh
|
|
$ ./run_simpleperf_on_device.py list --show-features
|
|
dwarf-based-call-graph
|
|
trace-offcpu
|
|
```
|
|
|
|
If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
|
|
|
|
```sh
|
|
$ ./app_profiler.py -p simpleperf.example.cpp -a .SleepActivity \
|
|
-r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu" \
|
|
-lib path_of_SimpleperfExampleCpp
|
|
$ ./report_html.py --add_disassembly --add_source_code \
|
|
--source_dirs path_of_SimpleperfExampleCpp
|
|
```
|
|
|
|
## Profile from launch
|
|
|
|
We can [profile from launch of an application](scripts_reference.md#profile-from-launch-of-an-application).
|
|
|
|
```sh
|
|
# Start simpleperf recording, then start the Activity to profile.
|
|
$ ./app_profiler.py -p simpleperf.example.cpp -a .MainActivity
|
|
|
|
# We can also start the Activity on the device manually.
|
|
# 1. Make sure the application isn't running or one of the recent apps.
|
|
# 2. Start simpleperf recording.
|
|
$ ./app_profiler.py -p simpleperf.example.cpp
|
|
# 3. Start the app manually on the device.
|
|
```
|
|
|
|
## Control recording in application code
|
|
|
|
Simpleperf supports controlling recording from application code. Below is the workflow:
|
|
|
|
1. Run `api_profiler.py prepare -p <package_name>` to allow an app recording itself using
|
|
simpleperf. By default, the permission is reset after device reboot. So we need to run the
|
|
script every time the device reboots. But on Android >= 13, we can use `--days` options to
|
|
set how long we want the permission to last.
|
|
|
|
2. Link simpleperf app_api code in the application. The app needs to be debuggable or
|
|
profileableFromShell as described [here](#prepare-an-android-application). Then the app can
|
|
use the api to start/pause/resume/stop recording. To start recording, the app_api forks a child
|
|
process running simpleperf, and uses pipe files to send commands to the child process. After
|
|
recording, a profiling data file is generated.
|
|
|
|
3. Run `api_profiler.py collect -p <package_name>` to collect profiling data files to host.
|
|
|
|
Examples are CppApi and JavaApi in [demo](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo).
|
|
|
|
|
|
## Parse profiling data manually
|
|
|
|
We can also write python scripts to parse profiling data manually, by using
|
|
[simpleperf_report_lib.py](scripts_reference.md#simpleperf_report_libpy). Examples are report_sample.py,
|
|
report_html.py.
|