////////////////////////////////////////////////////////////
谈到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/simpleperf2. 启动手机上的被测程序,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.txt4. 解析火焰图
(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.svgout.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.apk2.抓取性能数据:
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.py4.查看对应的性能分析数据:
通过数据我们可以看到各个线程占用的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 文件源地址 文件目的地址
可执行文件都存储在bin/android/目录下,可自行获取。
性能分析
在性能分析前,首先启动需要分析的APP,获取APP的进程号:
adb shell am start -n 包名/启动程序
adb shell pidof 包名
接下来,就可以使用命令三连击了:
adb shell
cd simpleperf所在目录
./simpleperf stat [options] [command [command-args]]
./simpleperf record [options] [command [command-args]]
./simpleperf report [options]
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
report命令读取perf.data文件(由simpleperf record创建),并显示报告,表明花费时间的地方。最常使用的选项为:
./simpleperf report --dsos 选定动态共享对象(so库) -f 记录文件(默认perf.data) --sort 用于排序和打印报告的键 -n
注意点
如果使用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
通过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
/////////////////////////////////////////////////////////////
使用 Android SimplePerf生成的网页无法打开或加载的问题,一直困扰了很多使用 Simpleperf 的 Android 性能开发工作者。这是因为本地生成的页面依赖一些 JavaScript 文件,国内的网络访问不到 CDN 源,此时需要将这些源替换成国内可以访问的源。
以下是两种解决办法:
- 修改 Simpleperf 生成的 HTML 文件,将 JavaScript 源替换成国内网络可以访问到的源;
- 修改 Python 使其在生成 HTML 前完成 JavaScript 源的替换工作。
这两种方法前者仅一次生效,后者永久生效。
修改 HTML 文件
因为这个方法是使用了 Simpleperf 的 Python 生成 HTML 后再去做替换,所以最大的弊端就是每次都需要手动替换。具体的替换步骤如下:
- 使用记事本软件打开 HTML 文件(我本地这个文件名字叫:report.html);
- 按照如下的方式进行替换:
替换前:
<html><head><link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css"></link> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css"></link> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js"></script> <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script> <script src="https://www.gstatic.com/charts/loader.js"></script> <script>google.charts.load('current', {'packages': ['corechart', 'table']});</script> <style type="text/css"> .colForLine { width: 50px; } .colForCount { width: 100px; } .tableCell { font-size: 17px; } .boldTableCell { font-weight: bold; font-size: 17px; } </style> </head> <body><script>替换后:
<html><head><link rel="stylesheet" type="text/css" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css"></link> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css"></link> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js"></script> <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script> <script src="https://www.gstatic.com/charts/loader.js"></script> <script>google.charts.load('current', {'packages': ['corechart', 'table']});</script> <style type="text/css"> .colForLine { width: 50px; } .colForCount { width: 100px; } .tableCell { font-size: 17px; } .boldTableCell { font-weight: bold; font-size: 17px; } </style> </head> <body><script>然后刷新页面,即可正确渲染数据:
替换成国内源后,渲染速度加倍
修改 Python 脚本
因为这个方法是在 HTML 生成之前就做了替换,所以最大的优点就是不需要每次手动替换。在获取到perf.data 文件后,我们会使用 Simpleperf 中的 report_html.py 脚本进行渲染出 report.html 文件:
python ./report_html.py -i D:\temp\perf.data -o D:\temp\report.html因此,需要修改的是 report_html.py 文件。具体步骤如下:
- 使用记事本打开 report_html.py;
- 搜索关键字“URLS”,找到 JavaScript ;
- 按照如下的方式进行替换:
替换前:
URLS = { 'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js', 'bootstrap4-css': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css', 'bootstrap4-popper': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js', 'bootstrap4': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js', 'dataTable': 'https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js', 'dataTable-bootstrap4': 'https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js', 'dataTable-css': 'https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css', 'gstatic-charts': 'https://www.gstatic.com/charts/loader.js', }替换后:
URLS = { 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js', 'bootstrap4-css': 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css', 'bootstrap4-popper': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js', 'bootstrap4': 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js', 'dataTable': 'https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js', 'dataTable-bootstrap4': 'https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js', 'dataTable-css': 'https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css', 'gstatic-charts': 'https://www.gstatic.com/charts/loader.js', }最后,再次执行命令,即可正确渲染数据:
替换成国内源后,渲染速度加倍
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////