SimplePerf 性能分析火焰图

https://xiaomi.f.mioffice.cn/docs/dock4H4YGLmcxzi44iXyL93nMpd

通用方法:

  1. 拉代码:https://android.googlesource.com/platform/prebuilts/simpleperf/
  2. 机器需要刷ROOT版本,方可debug三方应用等;
  3. 抓取特定进程:python3 run_simpleperf_on_device.py record -p 【pid of process】 —call-graph dwarf 【注:此处会直接在/data/local/tmp下push一个simpleperf,后续也可直接通过此抓取数据////// system/bin下面本身也有一個】
  4. 从手机拉取抓取的数据:adb pull /data/local/tmp/perf.data
  5. 解析为火焰图:python3.10 report_html.py -i perf.data 【python版本有要求】

源码及clone地址:https://android.googlesource.com/platform/prebuilts/simpleperf/

直接下载压缩文件:

https://android.googlesource.com/platform111/prebuilts/simpleperf/+archive/master.tar.gz

谷歌的定位

Simpleperf is a native CPU profiling tool for Android. It can be used to profile both Android applications and native processes running on Android. It can profile both Java and C++ code on Android. The simpleperf executable can run on Android >=L, and Python scripts can be used on Android >= N.

“Android的原生CPU分析工具,可用于分析第三方APP和系统原生进程”

简介

Simpleperf包括两部分:Simpleperf可执行文件和Python脚本。

Simpleperf可执行文件会在分析数据时记录更多的信息,不仅会收集待分析数据样本,还会收集设备信息和记录时间等。

api_profiler.py //含有prepare和collect两个命令,用于无收集期间不连接USB的情况
 
run_simpleperf_without_usb_connection.py //同api_profiler.py
 
run_simpleperf_on_device.py //在设备上布置simpleperf所需环境并收集数据,省去adb命令
 
app_profiler.py //收集数据,默认10S,生成perf.data数据
 
binary_cache_builder.py //获取二进制文件用于report.py的反汇编
 
report.py //统计分析生成的perf.data数据,窗口展示
 
report_html.py //统计分析生成的perf.data数据,并生成火焰图,HTML展示

inferno.sh //只生成火焰图,并在HTML界面展示,可分析现有数据也可该脚本抓取

app_profiler.py 被用于剖析一个 android 应用程序。它准备剖析环境,下载 simpleperf 到设备上,在主机上生成并拉出 perf.data。它由 app_profiler.config 配置。binary_cache_builder.py 被用于从设备拉出本地层二进制文件到主机上。它由 app_profiler.py 使用。annotate.py 被用于使用 perf.data 注解源文件。它由 annotate.config 配置。report.py 在一个 GUI 窗口中报告 perf.data。simpleperf_report_lib.py 被用于枚举 perf.data 中的样本。在内部它使用 libsimpleperf_report.so 来解析 perf.data。它可被用于将 perf.data 中的样本翻译为其它形式。使用 simpleperf_report_lib.py 的一个例子是 report_sample.py。

APP分析前提条件

“对于Android系统开发者,Root环境下可分析任意APP,该部分无需阅读,仅需注意非Root包大概率无法分析APP是正常的”

  1. 分析APP的调试版本[debug]:在Manifest中设置android:debuggable=“true”,打开JNI检查并且不优化C/C++代码。——无需更改可直接使用Simpleperf分析
  2. 分析APP的发行版本[release]:在Manifest中设置android:debuggable=“false”,关闭JNI检查并且优化C/C++代码。当且仅当以下三种情况才可以分析发行版本的APP:
  • Root系统可分析任何APP
  • Android > Q,在Manifest中设置profileableFromShell
<manifest ...>
 
​    <application ...>
 
​      <profileable android:shell="true" />
 
​    </application>
 
</manifest>
  • Android > O,在Manifest中添加debuggable为“true”,并在lib/arch目录下添加wrap.sh文件,无需向ART传递调试标志,因此本身还是发行版本
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’)

​ }

​ }

}

  1. 分析C/C++代码:AS会在apk中删除本地库的符号表和调试信息,分析结果会包含未知符号等,因此Simpleperf可通过app_profiler.py -lib传递一个包含未删除本地库的目录,通常为AS的路径。
  2. 分析Java代码:
  • Android ⇐ M,Simpleperf不支持分析Java
  • Android == N,需要wrap.sh文件,通过app_profiler.py -compile_java_code进行分析
  • Android == O,可直接通过app_profiler.py -compile_java_code进行分析
  • Android >= P,Simpleperf可直接分析

APP分析步骤

极简用法

  1. 【Way1-STEP 1】通过app_profiler.py收集数据

具体可选参数详情键入”-h/—help”可查看,在此仅说明几个重要属性:

  • -p PACKAGE_NAME: 指定待分析的APP,APP的具体包名可通过systrace等工具确认,也可通过” adb shell pm list packages”筛选确认。
  • -np NATIVE_PROGRAM: 指定待分析的原生程序,如”-np surfaceflinger”。
  • —pid PID..: 指定待分析的原生进程。
  • —tid TID..: 指定待分析的原生线程。
  • -a ACTIVITY: 指定待分析的activity,需与”-p”搭配使用,适用与冷热起等场景。

​ python3 app_profiler.py -p com.miui.home

与抓取systrace一样,务必在抓取期间复现问题。该脚本会在当前目录下生成perf.data文件,默认同时在binary_cache/下生成二进制文件用于反编译等,二进制文件可于收集数据时键入”-nb”取消。

同时,该脚本默认收集10S数据,如有修改时间需求,可通过’-r “—duration 20“‘来指定,单位为秒。另外也可通过run_simpleperf_on_device.py脚本执行record命令,并通过”—duration”指定具体秒数,默认perf.data会在手机/data/local/tmp/目录下,具体参数请自行查阅,与app_profile.py存在差异。

—call-graph dwarf 若使用run_simpleperf_on_device在手机生成data,务必带上上述参数,否则火焰图会有异常

python3 run_simpleperf_on_device.py ``record``  --duration 60
  1. 【Way1-STEP 2】通过report_html.py解析数据

在HTML视图中打开解析的数据,且包含火焰图。

python3 report_html.py

  1. 【Way2】一键式生成火焰图

该命令会同时执行收集surfaceflinger的数据与HTML展示火焰图的功能,达到上面STEP1和2的效果。

./inferno.sh -np surfaceflinger

或通过如下命令可生成HTML页面的火焰图。

./inferno.sh -sc

进阶用法

  1. 在run_simpleperf_on_device.py脚本中,还支持如下命令:

  api-collect         Collect recording data generated by app api
 
​    api-prepare         Prepare recording via app api
 
​    debug-unwind        Debug/test offline unwinding.
 
​    dump                dump perf record file
 
​    help                print help information for simpleperf
 
​    inject              parse etm instruction tracing data
 
​    kmem                collect kernel memory allocation information
 
​    list                list available event types
 
​    record              record sampling info in perf.data
 
​    report              report sampling information in perf.data
 
​    report-sample       report raw sample information in perf.data
 
​    stat                gather performance counter information
 
​    trace-sched         Trace system-wide process runtime events.
  1. 通过kmem查看slab分配器的性能
python3 run_simpleperf_on_device.py kmem record
 
python3 run_simpleperf_on_device.py kmem report

通过如上命令可查看到slab的性能,输出如下。

Slab allocation information:
 
Total requested bytes: 12313833
 
Total allocated bytes: 13104992
 
Total fragment: 791159, 6.037081%
 
Total allocations: 13308
 
Total frees: 16595
 
Total cross cpu allocation/free: 2610, 19.612263%
 
 
 
Hit    Caller   BytesReq  BytesAlloc  Fragment  Pingpong
 
13308  unknown  12313833  13104992    791159    2610
  1. 通过stat查看一段时间内某特定事件的运行次数

具体可查看事件可通过以下命令,窗口中仅展示大部分,除了经典的tracepoint如sched_switch等以外,还包括cache-misses等硬件软件事件。

python3 run_simpleperf_on_device.py list

在确认事件名称后,可通过以下命令来获取指定时间内指定线程的指定时间的统计运行次数,其中”-e”指定事件,“-p”指定进程或”-a”不限进程,“—interval”指定时间间隔打印,“—duration”指定总的统计时间。

python3 run_simpleperf_on_device.py stat -e block:block_bio_queue,block:block_bio_remap -a —interval 300—duration 15

或者通过”ls”命令来查看性能事件的统计。

python3 run_simpleperf_on_device.py stat ls
 
 
 
\#         count  event_name                # count / runtime,  runtime / enabled_time
 
​      7,874,067  cpu-cycles                # 1.321198 GHz        (100%)
 
​      4,943,625  stalled-cycles-frontend   # 319.791 M/sec       (100%)
 
​     12,966,162  stalled-cycles-backend    # 792.961 M/sec       (100%)
 
​      5,908,816  instructions              # 568.605 M/sec       (64%)
 
​          3,469  branch-misses             # 3.886 M/sec         (5%)
 
  16.330986(ms)  task-clock                # 0.793354 cpus used  (100%)
 
​             16  context-switches          # 979.733 /sec        (100%)  //上下文切换
 
​            405  page-faults               # 24.799 K/sec        (100%)   //缺页异常的次数

Total test time: 0.020585 seconds.

  1. 通过record转存记录

可通过”-f”或”-c”设置转存的频率,前者单位为”T次/每秒”,后者单位为”次/每T次”,其他参数与stat相近,具体设置可通过”—help”查看。

如下示例对dso共享库的使用次数进行排序,进一步可查看指定so库下各方法的调用频率,

> python3 report.py -n --sort dso
 
Cmdline: /data/local/tmp/simpleperf record -o /data/local/tmp/perf.data -e task-clock:u -f 1000 -g --duration 10 --app com.miui.home
 
Arch: arm64
 
Event: task-clock:u (type 1, config 1)
 
Samples: 1489
 
Event count: 1489000000
 
 
 
Overhead  Sample  Shared Object
 
33.58%    500     /system/lib64/libhwui.so
 
11.48%    171     /vendor/lib64/egl/libGLESv2_adreno.so
 
10.54%    157     /apex/com.android.art/lib64/libart.so
 
10.48%    156     /system/framework/arm64/boot-framework.oat
 
7.05%     105     /apex/com.android.runtime/lib64/bionic/libc.so
 
5.51%     82      /apex/com.android.art/javalib/arm64/boot.oat
 
4.97%     74      /system/priv-app/MiuiHome/oat/arm64/MiuiHome.odex
 
2.55%     38      /system/lib64/libbinder.so
 
2.28%     34      [JIT app cache]
 
1.81%     27      /system/lib64/libgui.so
 
1.48%     22      /system/lib64/libutils.so
 
0.94%     14      /system/lib64/libandroid_runtime.so
 
0.60%     9       /apex/com.android.art/javalib/arm64/boot-core-icu4j.oat
> python3 report.py --dsos  /system/lib64/libhwui.so   --sort symbol
 
Cmdline: /data/local/tmp/simpleperf record -o /data/local/tmp/perf.data -e task-clock:u -f 1000 -g --duration 10 --app com.miui.home
 
Arch: arm64
 
Event: task-clock:u (type 1, config 1)
 
Samples: 500
 
Event count: 500000000
 
 
Overhead  Symbol
 
14.40%    android::uirenderer::skiapipeline::SkiaDisplayList::prepareListAndChildren(android::uirenderer::TreeObserver&, android::uirenderer::TreeInfo&, bool, std::__1::function<void (android::uirenderer::RenderNode*, android::uirenderer::TreeObserver&, android::uirenderer::TreeInfo&, bool)>)
 
10.80%    android::uirenderer::RenderNode::prepareTreeImpl(android::uirenderer::TreeObserver&, android::uirenderer::TreeInfo&, bool, int)
 
10.60%    android::uirenderer::skiapipeline::RenderNodeDrawable::forceDraw(SkCanvas*) const
 
3.00%     android::uirenderer::$_24::__invoke(void const*, SkCanvas*, SkMatrix const&)
 
3.00%     android::uirenderer::skiapipeline::RenderNodeDrawable::onDraw(SkCanvas*)
 
2.20%     android::uirenderer::skiapipeline::StartReorderBarrierDrawable::onDraw(SkCanvas*)

工作机制

CPU具有一个硬件组件——PMU(性能监控单元),在CPU运行时会收集各种统计信息,内核将PMU的硬件计数器包装到硬件perf事件中,同事提供独立与硬件的软件时间和跟踪点事件,通过perf_event_open暴露给用户空间,达成simpleperf的使用基础。

假如一秒内读取四次CPU上运行的事件,采样时分别为 A,B,C,C,而且其之间存在调用关系,那么最后组合栈的关系A(4),B(1),C(2):最底层为A,其上为BC分别占比25% ,50%。

def A():
 
​    B()
 
​    C()

红蓝差分火焰图

由于火焰图的颜色是随机的,深浅并不具有任何的现实意义,FlameGrdph中实现了一个脚本生成红蓝差分火焰图,用以区分修改前后的差别。