////////////////////////////////////////////////////////////谈到Android上的性能剖析很多人会想到TraceViewSysTrace。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 profilinggui和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 its 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的代码。关掉selinuxadb 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/arm64cd /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 出该程序的进程IDadb shell ps -ef | grep 需要分析的应用程序包名3. 使用Simpleperf工具进行分析1record-记录运行结果数据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_testmkdir 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 pfpython /home/hqb/Android/Sdk/ndk/21.3.6528147/simpleperf/report_html.py source ~/.bashrc2data格式转化成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.html3下载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_test4生成火焰图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/simpleperfsimpleperf可执行文件的工作方式类似于linux-tools-perf但是具有针对Android分析环境的一些特定功能详情参看simpleperfPython 脚本根据功能划成如下三部分:用于记录事件的脚本, 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 shellcd simpleperf所在目录./simpleperf stat [options] [command [command-args]]./simpleperf record [options] [command [command-args]]./simpleperf report [options]stat命令获取一段时间内已分析进程中发生的事件数摘要。 最常使用的选项为./simpleperf stat -p 进程号 --duration 检测进程的持续时间(秒)1record命令记录一段时间内已分析进程的样本这是simpleperf的主命令。最常使用的选项为./simpleperf record -p 进程号 -o 输出文件(默认perf.data) --duration 监测进程的持续时间(秒)1需要注意的是如果出现Access to kernel symbol addresses is restricted的警告需要使用一下命令来取消echo 0/proc/sys/kernel/kptr_restrictreport命令读取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,symbol1/////////////////////////////////////////////////////////////使用 Android SimplePerf生成的网页无法打开或加载的问题一直困扰了很多使用 Simpleperf 的 Android 性能开发工作者。这是因为本地生成的页面依赖一些 JavaScript 文件国内的网络访问不到 CDN 源此时需要将这些源替换成国内可以访问的源。以下是两种解决办法修改 Simpleperf 生成的 HTML 文件将 JavaScript 源替换成国内网络可以访问到的源修改 Python 使其在生成 HTML 前完成 JavaScript 源的替换工作。这两种方法前者仅一次生效后者永久生效。修改 HTML 文件因为这个方法是使用了 Simpleperf 的 Python 生成 HTML 后再去做替换所以最大的弊端就是每次都需要手动替换。具体的替换步骤如下使用记事本软件打开 HTML 文件我本地这个文件名字叫report.html按照如下的方式进行替换替换前htmlheadlink relstylesheet typetext/css hrefhttps://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css/link link relstylesheet typetext/css hrefhttps://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css/link script srchttps://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js/script script srchttps://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js/script script srchttps://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js/script script srchttps://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js/script script srchttps://www.gstatic.com/charts/loader.js/script scriptgoogle.charts.load(current, {packages: [corechart, table]});/script style typetext/css .colForLine { width: 50px; } .colForCount { width: 100px; } .tableCell { font-size: 17px; } .boldTableCell { font-weight: bold; font-size: 17px; } /style /head bodyscript替换后htmlheadlink relstylesheet typetext/css hrefhttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css/link link relstylesheet typetext/css hrefhttps://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css/link script srchttps://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js/script script srchttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js/script script srchttps://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js/script script srchttps://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js/script script srchttps://www.gstatic.com/charts/loader.js/script scriptgoogle.charts.load(current, {packages: [corechart, table]});/script style typetext/css .colForLine { width: 50px; } .colForCount { width: 100px; } .tableCell { font-size: 17px; } .boldTableCell { font-weight: bold; font-size: 17px; } /style /head bodyscript然后刷新页面即可正确渲染数据替换成国内源后渲染速度加倍修改 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, }最后再次执行命令即可正确渲染数据替换成国内源后渲染速度加倍//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////