Simpleperf 工具介绍

常用的Android性能剖析工具:Systrace、Simpleperf。其中,Systrace的优点在于它可以给出整个系统的一些关键模块的性能信息,但是它分析自定义模块,使用起来稍显复杂。另外,在AOSP中的性能剖析工具有:Simpleperf。

一、简介

1、概述
Simpleperf 是Google随NDK一起发布的一款profile工具(注:从NDK r13开始),它是针对Android平台的一个 native 层性能分析工具。它的命令行界面支持与linux-tools perf大致相同的选项,但是它还支持许多Android特有的改进。
NDK 中的 Simpleperf 工具包含三个部分:在 Android 设备上运行的 simpleperf 可执行文件;在主机上运行的 simpleperf 可执行文件;python 脚本。
(1)设备上的 simpleperf
在设备上运行的 simpleperf 位于 bin/android 目录下。它包含不同体系架构的 Android 上运行的静态二进制文件。它们可被用于剖析运行在设备上的进程,并生成 perf.data 数据文件。
(2)主机上的 simpleperf
运行与主机上的 simpleperf 位于bin/darwin、bin/linux、bin/windows,它们可被用于在主机上解析 perf.data。
(3)python 脚本(注:脚本被用于使得剖析和解析剖析结果更方便。)
<1> app_profiler.py:被用于剖析一个 android 应用程序。它的主要工作:准备剖析环境、下载 simpleperf 到设备上,在主机上生成并拉出 perf.data 数据文件。( 注:它由 app_profiler.config 配置 )
<2> binary_cache_builder.py:被用于从设备拉出本地层二进制文件到主机上。( 注:它由 app_profiler.py 使用 )
<3> annotate.py:被用于使用 perf.data 注解源文件。( 注:它由 annotate.config 配置 )
<4> report.py:用于在一个 GUI 窗口中报告 perf.data 数据。
<5> simpleperf_report_lib.py:用于枚举 perf.data 中的样本。在内部它使用 libsimpleperf_report.so 来解析 perf.data。它可被用于将 perf.data 中的样本翻译为其它形式。

2、原理
简单来说,现代 CPU 一般都带有一个叫做:性能监视单元(PMU)的组件,这个硬件能够记录诸如:CPU周期数、执行的指令数、缓存失效次数等关键信息;Linux 内核对这个硬件做了一层封装,通过 perf_event_open 系统调用,把接口暴露给用户空间,这就是 Simpleperf 工具的由来。
Simpleperf 是一个命令行工具,与Systrace不同的是,它的工具集包涵 client 端、host 端。client 端运行在 Android 系统上,负责收集性能数据;host 端则运行在你的开发机上,负责对数据进行分析和可视化(注:这些可执行文件在下载后的 bin 文件夹的 android 和 win/mac/linux 下)。整个工具链使用起来相对复杂,所幸 Simpleperf 的创造者提供了一个简单的使用脚本,使用起来非常方便!

二、常用命令

1、Simpleperf 的三个主要功能:
(1)Stat 命令:给出了在一个时间段内被分析的进程中发生了多少事件的摘要。
(2)Record 命令:在一段时间内记录剖析进程的样本,生成 perf.data 文件。
(3)Report 命令:读取 “perf.data” 文件及所有被剖析进程用到的共享库,并输出一份报告,展示时间消耗在了哪里。

主要命令:
Simpleperf 支持一些子命令,包括:list、stat、record、report,每个子命令支持不同的选项。
(注:要了解所有的子命令和选项,可使用 --help)
(1)simpleperf list:用于列出设备上所有可用的事件。由于内核的差异,不同的设备可以支持不同的事件。

(2)simpleperf stat:用于获取被剖析程序或系统范围内的原始事件计数器信息。通过输入选项参数,我们可以选择使用哪些事件,监视哪个进程/线程,监视多长时间,以及打印的间隔。
<1> -p:指定具体进程:$ simpleperf stat -p <进程号> --duration <持续时间> (注:-t:表示指定线程)
<2> -e:选择具体事件:$ simpleperf stat -e <事件1>, <事件2> -p <进程号> --duration <持续时间>
注意:当运行 stat 命令时,如果硬件事件的数量大于 PMU 中可用的硬件计数器的数量,则内核在事件间共享硬件计数器,因此每个事件只在总时间中的一部分内被监视。如下例所示:每一行的最后都有一个百分比,展示了每个事件实际被监视的时间占总时间的百分比。

$ simpleperf stat -p 7394 -e cache-references,cache-references:u,cache-references:k,cache-misses,cache-misses:u,cache-misses:k,instructions --duration 1
Performance counter statistics:
	4,331,018  cache-references    # 4.861 M/sec (87%)
	3,064,089  cache-references:u  # 3.439 M/sec (87%)
	1,364,959  cache-references:k  # 1.532 M/sec (87%)
	   91,721  cache-misses        # 102.918 K/sec (87%)
	   45,735  cache-misses:u      # 51.327 K/sec (87%)
	   38,447  cache-misses:k      # 43.131 K/sec (87%)
	9,688,515  instructions        # 10.561 M/sec (89%)
Total test time: 1.026802 seconds.

如上所示,每个事件被监视的时间大概占总时间的 87%。但是不保证任何一对事件总是在相同的时间被监视。如果我们想要让一些事件在同一时间被监视,我们可以使用 --group 选项。例如:
$ simpleperf stat -p 7394 --group cache-references,cache-misses --group cache-references:u,cache-misses:u -e instructions --duration 10
<3> --interval:指定打印的间隔时间(ms为单位):$ simpleperf stat -p <进程号> --duration <持续时间> --interval <打印间隔时间>

(3)simpleperf record:用于转储被剖析程序的记录。通过输入选项参数,可以选择使用哪个事件,监视哪个进程/线程,以什么频率转储记录,监视多长时间,以及将记录存储到哪里。
<1> -e:选择事件(一般 cpu-cycles 事件被用于评估消耗的CPU时间。作为一个硬件事件,它精确而高效。还可以通过 -e 选项使用其它事件。)例如:$ simpleperf record -e instructions -p 11904 --duration 10
<2> 设置转储记录的频率:
-f:设置当监视的线程运行时,每秒转储记录个数,例如:$ simpleperf record -f 1000 -p 11904 --duration 10
-c:设置当每发生多少次事件转储一个记录,例如:$ simpleperf record -c 10000 -p 11904 --duration 10
<3> -o:记录转储记录的路径,例如:$ simpleperf record -p <进程号> -o data/perf.data --duration <持续时间>

(4)simpleperf report:用来基于 simpleperf record 命令生成的 perf.data 数据,产生报告。
Report 命令将记录分组为不同的样本项,基于每个样本项包含的事件的多少对样本项排序,并打印每个样本项。通过输入选项参数,我们可以选择到哪里寻找被监视的程序,使用的 perf.data 和 可执行二进制文件,过滤不感兴趣的记录,并决定如何分组记录。
例如:

$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so --sort comm,pid,tid,symbol -n
Cmdline: /data/data/com.example.sudogame/simpleperf record -p 7394 --duration 10
	Arch: arm64
	Event: cpu-cycles (type 0, config 0)
	Samples: 28235
	Event count: 546356211
		Overhead  Sample  Command    Pid   Tid   Symbol
		59.25%    16680   sudogame  7394  7394  checkValid(Board const&, int, int)
		20.42%    5620    sudogame  7394  7394  canFindSolution_r(Board&, int, int)
		13.82%    4088    sudogame  7394  7394  randomBlock_r(Board&, int, int, int, int, int)
		6.24%     1756    sudogame  7394  7394  @plt

<1> -i:表示从指定路径下的文件读取记录(注:不加该参数,则默认是从当前目录下读取 perf.data 数据)
例如:$ simpleperf report -i data/perf.data

三、使用步骤

1、将NDK中Simpleperf工具的可执行程序 push 到手机上
(路径:<下载的NDK>/simpleperf/bin/android/目录下,根据被测程序和CPU,来选择对应的版本)

$ cd /simpleperf/bin/android/<对应的版本>
$ adb push simpleperf /data/loacal/tmp/
$ adb shell chmod 777 /data/loacal/tmp/simpleperf

2、启动手机上的被测程序,ps 出该程序的进程ID

dipper:/ # ps -ef | grep "应用包名"

3、使用Simpleperf工具进行分析
(1)record-记录运行结果数据:$ adb shell /data/local/tmp/simpleperf record -p <进程号> --duration <持续的时间(秒为单位)>
注意:此时会得到错误提示(说只读分区无法写入perf.data)
此时,用 -o 参数,指定输出文件,来解决问题:$ adb shell /data/local/tmp/simpleperf record -p <进程号> --duration <持续时间> -o /sdcard/perf.data
会得到如下警告:

simpleperf W 04-19 15:17:47  4299  4299 environment.cpp:504] Access to kernel symbol addresses is restricted.
If possible, please do `echo 0 >/proc/sys/kernel/kptr_restrict` to fix this.

暂且不去理会这个警告,持续时间过后,采集结束,得到如下输出:

simpleperf I 04-19 15:18:19  4299  4299 cmd_record.cpp:346] Samples recorded: 125706. Samples lost: 0.

(2)report-报告结果数据:$ adb shell /data/local/tmp/simpleperf report -i /sdcard/perf.data -n --sort dso
[参数说明]
<1> -n:用于显示 Sample 那列数据,表示该行一共命中了多少个Sample。
<2> -sort:用于指定输出结果显示哪些列(此处写为:dso,即:dynamic shared object,所以输出的结果只显示一列“Shared Object”)
若不加该参数,默认显示这几列:Command、Pid、Tid、Shared Object、Symbol

(3)查看某个App内部,哪些函数占比较大:$ adb shell /data/local/tmp/simpleperf report -i /sdcard/perf.data --dsos /data/local/rvdecApp
[参数说明]
<1> -comms:按照 command 过滤
<2> -pids:按照进程ID号过滤
<3> -tids:按照线程ID号过滤
<4> -dsos:按照库文件/可执行文件名过滤
<5> -symbols:按照函数名过滤,比如: --symbols “RVComFunc::getPUMVPredictor(RefBlockInfo*, unsigned int, int, int, unsigned int)”
( 注意:函数里有空格的,需要用双引号引起来。)

注意:Perf 命令(performance)是 Linux 系统原生提供的性能分析工具,返回CPU正在执行的函数名以及调用栈(stack)。执行之后产生一个庞大的文本文件,长达几十万甚至上百万行。为了便于阅读 perf record 命令可以统计每个调用栈出现的百分比,然后从高到低排列。但是,对于结果还是不易读,所以才有了火焰图。

四、火焰图(Flame Graph)

1、火焰图是基于 perf 命令结果产生的图片,用于展示 CPU 的调用栈。其中,纵轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。 横轴表示抽样数,如果一个函数在横轴占据的宽度越宽,就表示它被抽到的次数越多,即:执行的时间越长(注意:横轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的)。火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
Simpleperf 工具介绍_第1张图片
比方说,最经典的火焰图是统计某一个软件的所有代码路径在 CPU 上的时间分布。通过这张分布图我们就可以直观地看出哪些代码路径花费的 CPU 时间较多,而哪些则是无关紧要的。进一步地,我们可以在不同的软件层面上生成火焰图,比如说可以在系统软件的 C/C++ 语言层面上画出一张图,然后再在更高的,比如说:动态脚本语言的层面,例如:Lua、Perl 代码的层面,画出火焰图。不同层面的火焰图常常会提供不同的视角,从而反映出不同层面上的代码热点。火焰图可以帮助用户快速地进行性能问题的定位,而不至于反复地试错、胡乱猜测,节约时间。

值得注意的是,即使是遇到我们并不了解的陌生程序,即使从未阅读过它的一行源码,通过看火焰图,也可以大致推出性能问题的所在。因为通过火焰图上面直接显示出来的函数名,我们可以大致推测出对应的函数,乃至对应的某一条代码路径,大致是做什么事情的,从而推断出这个程序所存在的性能问题。

火焰图其实可以拓展到其他维度,比如:上面所说的火焰图是看程序运行在 CPU 上的时间,在所有代码路径上的分布,这是 on-CPU 时间这个维度。类似地,某一个进程不运行在任何 CPU 上的时间其实也是非常有趣的,我们称之为 off-CPU 时间。off-CPU 时间一般是这个进程因为某种原因处于休眠状态,比如:在等待某一个系统级别的锁,或者被一个非常繁忙的进程调度器(scheduler)强行剥夺 CPU 时间片。这些情况都会导致这个进程无法运行在 CPU 上,但是仍然花费很多的挂钟时间。通过这个维度的火焰图我们可以得到另一幅很不一样的图景。通过这个维度上的信息,我们可以分析系统锁方面的开销(例如:sem_wait 这样的系统调用),某些阻塞的 I/O 操作(例如:open、read 等),还可以分析进程或线程之间争用 CPU 的问题。通过 off-CPU 火焰图,都一目了然。类似地,我们可以把火焰图拓展到其它的系统指标维度,比如:内存泄漏的字节数、文件 I/O 的延时和数据量等。

2、将鼠标悬停在图中的某一行时,会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。
在这里插入图片描述

五、使用 simpleperf 工具的脚本简化操作

1、准备一个可调试(debuggable)的应用
例如:应用程序的包名是:com.example.sudogame。它包含 java 代码和 c++ 代码。我们需要运行一份在其 AndroidManifest.xml 元素中 android:debuggable=”true” 的 App,因为我们不能为 non-debuggable Apps 使用 run-as。应用应该已经安装在设备上了,而且我们可以通过 adb 连接设备。

2、使用脚本简化操作

# 进入具体AOSP源目录下,自带Python脚本工具
$ cd <具体项目>/system/extras/simpleperf/scripts/
# 相当于Record命令,在一段时间内记录剖析进程的样本,生成 perf.data 文件
$ python app_profiler.py -p <进程号或进程名称> -a <具体Activity> [-其它选项参数]
# 相当于Report命令,读取生成的 “perf.data” 文件,并输出一份 .html报告
$ python report_html.py

你可能感兴趣的:(常用工具汇总)