谈到Android上的性能剖析,很多人会想到TraceView,SysTrace。TraceView擅长Method Tracing,与IDE集成,使用方便,不过有时候它的误差让人无法忍受;SysTrace的牛逼之处在于它可以给出整个系统的一些关键模块的性能信息,因此用途广泛;虽然它也支持分析自定义模块,但是使用起来稍显复杂。
事实上,在AOSP中有另外一个性能剖析工具不为人知,这个安静的小可爱就是今天要介绍的主角—— simpleperf。我们先来看看它的介绍:
Simpleperf is a native profiling tool for Android. Its command-line interface supports broadly the same options as the linux-tools perf, but also supports various Android-specific improvements.
native profiling?是不是很心动?
至于simpleperf的原理,简单来说,现代CPU一般都带有一个叫做性能监视单元(PMU)的组件,这个硬件能够记录诸如cpu周期数、执行的指令数、缓存失效次数等等关键信息;Linux内核对这个硬件做了一层封装,通过 `perf_event_open` 系统调用把接口暴露给用户空间;这就是simpleperf工具的由来。
简介和原理都表明这个工具貌似很牛逼,接下来我们就试一试。
首先把AOSP上的源码下载下来,点我。
simpleperf是一个命令行工具,与systrace不同的是,它的工具集包涵client端和host端;client端运行在Android系统上,负责收集性能数据;host端则运行在你的开发机上,负责对数据进行分析和可视化。(这些可执行文件在下载后的bin文件夹的android和win/mac/linux下)整个工具链使用起来相对复杂,所幸simpleperf的创造者提供了一个炒鸡傻瓜的使用脚本,使用起来毫不费力,非常方便!这个脚本就是 `app_profile.py`。
我们把 simpleperf 下载下来之后,首先修改 `app_profile.config` 文件中的配置,主要是以下几个字段:
# 待分析的app的包名,需要是debuggable的
app_package_name = ""
# 分析的命令,体验的话使用默认即可;更详细的用途看文档。
record_options = "-e cpu-cycles:u -f 4000 -g --dump-symbols --duration 10"
# 待分析app的主界面,app_profile.py会通过am start 帮助开启app
main_activity = ''
然后在你的设备上安装待分析的App,要注意的是这个App必须是debuggable的,在AndroidManifest中把debuggable设置为true即可。接下来,就可以直接通过:
python app_profile.py
进行分析了,这个脚本帮你完成了simpleperf下载,权限设置,打开app,导出数据等等一系列过程;开始分析之后,就可以对App进行操作;在设置好的时间之后就会停止分析,跟SysTrace一样。分析完毕之后,脚本会自动把分析数据从设备上pull下来并存放在当前目录下,叫 perf.data。
接下来就是分析数据了;原始的数据是一个文本文件,长这样:
0.00% 0.00% ORDERED_THREAD_ 17046 18210 /system/lib/libart.so art::Mutex::ExclusiveLock(art::Thread*)
|
-- art::Mutex::ExclusiveLock(art::Thread*)
0.00% 0.00% AsyncTaskExecut 17046 18216 /system/lib/libart.so art::Monitor::Lock(art::Thread*)
|
-- art::Monitor::Lock(art::Thread*)
|
|--52.01%-- art::Mutex::ExclusiveLock(art::Thread*)
|
--0.54%-- art::Mutex::ExclusiveUnlock(art::Thread*)
嵌套如果过深,基本就看不懂了;所幸我们有另外一个分析脚本!直接运行:
python report.py -g
会启动一个GUI显示分析得到的数据,这个GUI使用python的tk写的,实话说,长得比较丑。。
简单观察分析图,可以知道,的确支持native profiling;gui和TraceView差不多,比较直观,但是指标没有TraceView多。另外,这个工具由于硬件直接支持,对性能的影响非常小;光是这一点就好顶赞了。
你以为这就完了?No!这玩意儿还支持 火焰图! 如果你不知道火焰图为何物,建议看看这篇文章 动态追踪技术漫谈。
然后,要绘制火焰图,我们需要借助 FlameGraph 这个工具;clone下来之后,里面的脚本就可以直接使用了。(perl脚本,windows系统需要安装Perl语言的支持)
接下来我们执行命令:
$python report_sample.py >out.perf
$stackcollapse-perf.pl out.perf >out.folded
$./flamegraph.pl out.folded > graph.svg
我们用Chrome浏览器打开这个 `graph.svg` 即可得到火焰图:
通过火焰图,我们能非常直观地看到性能的瓶颈,对于分析性能问题很有帮助;相信这个图已经说明一切了。
通用技巧
这里有一些技巧给有root权限的Android系统开发者:
- 运行
adb root
后, simpleperf可用于分析系统范围内任何进程。 - 如果不是在主分支上工作,建议使用AOSP main中最新的simpleperf。脚本位置在
system/extras/simpleperf/scripts
,二进制程序在system/extras/simpleperf/scripts/bin/android
. - 推荐使用
app_profiler.py
抓trace,然后用report_html.py
生成html报告。 下面是一个示例。
# Record surfaceflinger process for 10 seconds with dwarf based call graph. More examples are in
# scripts reference in the doc.
$ python app_profiler.py -np surfaceflinger -r "-g --duration 10"
# Generate html report.
$ python report_html.py
- 从 Android >= O 开始系统库默认有符号表,我们不需要用
$ANDROID_PRODUCT_OUT/symbols
中未striped二进制文件来抓了。 但是,在报告中添加源代码和反汇编(带有行号)时需要它们。下面是一个例子。
# Doing recording with app_profiler.py or simpleperf on device, and generates perf.data on host.
$ python app_profiler.py -np surfaceflinger -r "--call-graph fp --duration 10"
# Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
$ python binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
# --binary_filter option to only disassemble selected binaries.
$ python report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
--binary_filter surfaceflinger.so
在system_server进程上抓simpleperf
有时我们希望在发生特殊情况时抓系统进程。在这种情况下,我们可以在检测到情况的点处添加SimplEperf的代码。
- 关掉selinux
adb shell setenforce 0
。因为selinux只允许simpleperf在shell或debuggable/profileable 应用中使用。 - 在检测到特殊情况的地方添加下面的代码。
try {
// for capability check
Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE,
OsConstants.CAP_SYS_PTRACE, 0, 0);
// Write to /data instead of /data/local/tmp. Because /data can be written by system user.
Runtime.getRuntime().exec("/system/bin/simpleperf record -g -p " + String.valueOf(Process.myPid())
+ " -o /data/perf.data --duration 30 --log-to-android-buffer --log verbose");
} catch (Exception e) {
Slog.e(TAG, "error while running simpleperf");
e.printStackTrace();
}
硬件 PMU 计数器限制
监视指令和缓存相关的性能事件时 (在list命令列出的hw/cache/raw/pmu 类别),这些事件被映射到每个cpu核心上的PMU计数器。但每个核心只有有限数量的PMU计数器。如果事件数量 > PMU计数器的数量,然后计数器在事件之间多路复用,这可能不是我们想要的。
在Pixel设备上,每个核上的PMU计数器的数量通常是7个,其中4个被内核用于监视内存延迟。所以只有3个计数器可用。可以同时监控最多3个PMU事件。要监视3个以上的事件,可以使用 --use-devfreq-counters
选项借用内核使用的计数器。
定义
Simpleperf 是Google随NDK一起发布的一款profile工具(注:从NDK r13开始),它是针对Android平台的一个 native 层性能分析工具。
使用步骤
本篇是分析运行在android设备下的程序
1. 将NDK中Simpleperf工具的可执行程序 push 到手机上
cd <NDK>/simpleperf/bin/android/<对应的版本,根据被测程序和CPU,来选择对应的版本>
例如 :我的路径是 /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf/bin/android/arm64
cd /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf/bin/android/arm64
adb push simpleperf /data/local/tmp
adb shell chmod 777 /data/local/tmp/simpleperf
2. 启动手机上的被测程序,ps 出该程序的进程ID
adb shell ps -ef | grep "需要分析的应用程序包名"
3. 使用Simpleperf工具进行分析
(1)record-记录运行结果数据:
adb shell /data/local/tmp/simpleperf record -p <进程号> --duration <持续的时间(秒为单位)> -o <输出文件名称> --call-graph fp
--call-graph dwarf 用在32位系统中,64位则采用--call-graph fp
例子:adb shell /data/local/tmp/simpleperf record -p 4844 --duration 10 -o /data/local/tmp/perf.data --call-graph fp
输出:simpleperf I cmd_record.cpp:658] Samples recorded: 12013. Samples lost: 0.
(2)报告结果数据
adb shell /data/local/tmp/simpleperf report -i /data/local/tmp/perf.data -o /data/local/tmp/perf_report.txt
4. 解析火焰图
(1)将data文件和txt文件,从手机pull到电脑
电脑上新建simpleperf_test目录,例子:/home/hqb/simpleperf_test
mkdir simpleperf_test
adb pull /data/local/tmp/perf.data /home/hqb/simpleperf_test
adb pull /data/local/tmp/perf_report.txt /home/hqb/simpleperf_test
经常使用simpleperf就用alias设置别名
vim ~/.bashrc
alias pf='python /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf/report_html.py'
source ~/.bashrc
(2)data格式转化成html格式
cd /home/hqb/simpleperf_test
//设置别名
pf -i ./perf.data -o ./perf.html
//如果不设置别名
python /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf/report_html.py -i ./perf.data -o ./perf.html
(3)下载FlameGraph到simpleperf_test目录下,将simpleperf复制到simpleperf_test目录下
git clone <https://github.com/brendangregg/FlameGraph.git>
chmod 777 FlameGraph/flamegraph.pl
chmod 777 FlameGraph/stackcollapse-perf.pl
cp -r /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf /home/hqb/simpleperf_test
(4)生成火焰图
cd /home/hqb/simpleperf_test
python ./simpleperf/report_sample.py > out.perf
./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
./FlameGraph/flamegraph.pl out.folded > out.svg
out.svg就是最后得到的火焰图,用浏览器打开就可以看
/
Android Studio 包含 Simpleperf 的图形前端,记录在使用 CPU Profiler 检查 CPU Activity 中。大多数用户更喜欢使用该图形前端,而不是直接使用 Simpleperf。
如果您更喜欢使用命令行,可以直接使用 Simpleperf。Simpleperf 是一个通用的命令行 CPU 性能剖析工具,包含在面向 Mac、Linux 和 Windows 的 NDK 中。
Simpleperf包含两部分:simpleperf可执行文件和Python脚本。路径:Android/sdk/ndk/20.0.5594570/simpleperf
simpleperf可执行文件的工作方式类似于linux-tools-perf,但是具有针对Android分析环境的一些特定功能:详情参看simpleperf
Python 脚本根据功能划成如下三部分:
- 用于记录事件的脚本, like app_profiler.py, run_simpleperf_without_usb_connection.py.
- 用于报告的脚本, like report.py, report_html.py, inferno.
- 用于分析 profiling data的脚本, like simpleperf_report_lib.py.
1.事件
2.分析Android App性能
分析一个 Android 应用的性能可以按照如下三步走:
- 准备一个要分析的APP.
例如Google官方的实例:
$ git clone https://android.googlesource.com/platform/system/extras
$ cd extras/simpleperf/demo
# Open SimpleperfExamplesWithNative project with Android studio, and build this project
# successfully, otherwise the `./gradlew` command below will fail.
$ cd SimpleperfExampleWithNative
# On windows, use "gradlew" instead.
$ ./gradlew clean assemble
$ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
2.抓取性能数据:
python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative --compile_java_code -a .MixActivity --lib app/build/intermediates/cmake/debug/obj/arm64-v8a/
3.分析性能数据:
python report_html.py
4.查看对应的性能分析数据:
通过数据我们可以看到各个线程占用的CPU时钟周期
/
了多少事件的摘要。以下是它的工作原理:
给定用户选项,simpleperf通过对linux内核进行系统调用来启用分析;
Linux 内核在调度到被分析进程时启用计数器;
分析之后,simpleperf从内核读取计数器,并报告计数器摘要。
Record命令在一段时间内记录剖析进程的样本。它的工作原理如下:
给定用户选项,simpleperf通过对linux内核进行系统调用来启用分析;
Simpleperf在simpleperf和linux内核之间创建映射缓冲区;
Linux内核在调度到被分析进程时启用计数器;
每次给定数量的事件发生时,linux内核将样本转储到映射缓冲区;
Simpleperf从映射缓冲区读取样本并生成perf.data。
Report命令读取perf.data文件及所有被剖析进程用到的共享库,并输出一份报告,展示时间消耗在了哪里。
Simpleperf的使用
尽管不推荐使用一键脚本,但得益于Simpleperf的命令不多,使用起来也很简单快捷。
文件准备
将simpleperf可执行文件传输到Android设备上:
adb push 文件源地址 文件目的地址
1
可执行文件都存储在bin/android/目录下,可自行获取。
性能分析
在性能分析前,首先启动需要分析的APP,获取APP的进程号:
adb shell am start -n 包名/启动程序
adb shell pidof 包名
1
2
接下来,就可以使用命令三连击了:
adb shell
cd simpleperf所在目录
./simpleperf stat [options] [command [command-args]]
./simpleperf record [options] [command [command-args]]
./simpleperf report [options]
1
2
3
4
5
stat命令获取一段时间内已分析进程中发生的事件数摘要。 最常使用的选项为:
./simpleperf stat -p 进程号 --duration 检测进程的持续时间(秒)
1
record命令记录一段时间内已分析进程的样本,这是simpleperf的主命令。最常使用的选项为:
./simpleperf record -p 进程号 -o 输出文件(默认perf.data) --duration 监测进程的持续时间(秒)
1
需要注意的是,如果出现Access to kernel symbol addresses is restricted的警告,需要使用一下命令来取消:
echo 0>/proc/sys/kernel/kptr_restrict
1
report命令读取perf.data文件(由simpleperf record创建),并显示报告,表明花费时间的地方。最常使用的选项为:
./simpleperf report --dsos 选定动态共享对象(so库) -f 记录文件(默认perf.data) --sort 用于排序和打印报告的键 -n
1
注意点
如果使用report命令进行查找的时候,发现so现实的Symbol都是地址,而不是函数内容。这多数是因为在安卓编译的时候,设备上使用的so库已经被strip过,也就是说,已经抛离了.symbol段的内容。
那么,我们需要将带有Symbol信息的so下载到设备上。同时需要将so放置到perf.data中记录的相同的路径(否则,simpleperf无法找到它)。
如果找不到路径,可以在perf.data文件中直接搜索需要选定的so库的名称,即可查看到路径。
实例
设备上执行命令:
./simpleperf stat -p 5932 --duration 10
./simpleperf record -p 5932 -o dms.data --duration 10
./simpleperf report --dsos /data/app/com.hobot.dms.sample-1/lib/arm/libhobot_dms.so --sort comm,pid,tid,symbol -n
1
2
3
通过report命令可以看到,主要是集中在libhobot_dms.so中。但是,显示的都是地址,并非函数调用。这是因为Android的so库已经抛离了.symbol段的内容。可以重新指定完整的so文件。
将perf.data从设备上拷贝下来,传输到Linux开发机上,执行命令:
./bin/linux/x86_64/simpleperf report --dsos /data/app/com.hobot.dms.sample-1/lib/arm/libhobot_dms.so --sort comm,pid,tid,symbol
1
/
/
/