1 介绍
1 介绍
perf是从Linux 2.6开始引入的一个profiling工具,通过访问包括pmu在内的软硬件性能计数器来分析性能,支持多架构,是目前kernel的主要性能检测手段,和kernel代码一起发布,所以兼容性良好。
2 功能
性能瓶颈如果要分类的话,大致可以分为几个大类:cpu/gpu/mem/storage,其中gpu用perf没法探测(这个目前比较好用的工具就只有ds5),storage只能用tracepoint来统计。总的说来,perf还是侧重于分析cpu的性能,其他功能都不是很好用。
perf
usage: perf [--version] [--help] COMMAND [ARGS]
The most commonly used perf commands are:
annotate Read perf.data (created by perf record) and display annotated code
archive Create archive with object files with build-ids found in perf.data file
bench General framework for benchmark suites
buildid-cache Manage <tt>build-id</tt> cache.
buildid-list List the buildids in a perf.data file
diff Read two perf.data files and display the differential profile
inject Filter to augment the events stream with additional information
kmem Tool to trace/measure kernel memory(slab) properties
kvm Tool to trace/measure kvm guest os
list List all symbolic event types
lock Analyze lock events
probe Define new dynamic tracepoints
record Run a command and record its profile into perf.data
report Read perf.data (created by perf record) and display the profile
sched Tool to trace/measure scheduler properties (latencies)
script Read perf.data (created by perf record) and display trace output
stat Run a command and gather performance counter statistics
test Runs sanity tests.
timechart Tool to visualize total system behavior during a workload
top System profiling tool.
See 'perf help COMMAND' for more information on a specific command.
其中比较常用的功能有几个:
- record:收集profile数据
- report:根据profile数据生成统计报告
- stat:打印性能计数统计值
- top:cpu占有率实时统计
3 在Android平台使用
3.1 准备工作
1. 首先按google或芯片厂商的指导,构建一个完整的android和kernel的编译环境(如果不关心kernel可以忽略), 这样分析的时候符号表才能匹配上。
2. 编译perf
```shell # setup your build environment first ~$ . build/envsetup.sh ~$ lunch ~$ mmm external/linux-tools-perf ~$ adb root ~$ adb remount ~$ adb push perf /system/bin/ ~$ adb shell sync
```
3. 准备符号文件
符号文件可以简单分为三类:
a. 平台native代码,这部分代码在编译的过程中会自动生成符号表,不需要我们干预
b. 平台java代码,对于art虚拟机来说(老版本的dalvik就不说了)最终的编译结果是oat文件,这也是正规的elf文件,但是默认是不带debug信息。而新版本的android也提供了自动生成java符号表的工具:
bash art/tools/symbolize.sh
c. 第三方apk,如果是来自开源社区,则可以通过修改makefile和套用android提供的java符号表工具来生成符号表文件,然后拷贝到android的符号表目录,==注意路径必须要和设备上的完全一致==,可以通过showmap来获取设备上的路径。
~$ adb shell showmap apk_pid
38540 36296 36296 0 0 36216 80 0 3 /data/app/com.android.webview-2/lib/arm/libwebviewchromium.so
~$ cp libwebviewchromium.so $ANDROID_PRODUCT_OUT/symbols/data/app/com.android.webview-2/lib/arm/libwebviewchromium.so
如果是商业的apk,基本上已经做过混淆和strip,除非开发商能配合,不然就没招。
4. 稍微新一点的android都开起了kernel的指针保护,这也会影响perf的record,所以需要临时关闭保护:
~$ adb shell echo 0 > /proc/sys/kernel/kptr_restrict
5. 为了方便分析,一般会把record的数据pull到host端,在host端做分析,所以需要在设备端也安装一下perf工具,ubuntu下安装命令如下:
~$ sudo apt-get install linux-tools-common
6. 目前大部分的android平台默认perf功能都是打开的,所以一般不需要重新配置kernel,如果碰到perf被关闭的情况,可以打开下面几个配置
CONFIG_PERF_EVENTS=y
CONFIG_HW_PERF_EVENTS=y
3.2 获取当前平台支持的事件
rk3399:/data/local # ./perf list
List of pre-defined events (to be used in -e):
cpu-cycles OR cycles [Hardware event]
instructions [Hardware event]
cache-references [Hardware event]
cache-misses [Hardware event]
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]
cpu-clock [Software event]
task-clock [Software event]
page-faults OR faults [Software event]
context-switches OR cs [Software event]
cpu-migrations OR migrations [Software event]
minor-faults [Software event]
major-faults [Software event]
alignment-faults [Software event]
emulation-faults [Software event]
dummy [Software event]
L1-dcache-loads [Hardware cache event]
L1-dcache-load-misses [Hardware cache event]
L1-dcache-stores [Hardware cache event]
L1-dcache-store-misses [Hardware cache event]
L1-dcache-prefetch-misses [Hardware cache event]
L1-icache-loads [Hardware cache event]
L1-icache-load-misses [Hardware cache event]
dTLB-load-misses [Hardware cache event]
dTLB-store-misses [Hardware cache event]
iTLB-load-misses [Hardware cache event]
branch-loads [Hardware cache event]
branch-load-misses [Hardware cache event]
实际上android移植的perf还不完整,tracepoint的事件还不支持,例如:block事件,所以如果想要抓去一些内核子系统的性能信息就无法满足。android 7.0开始已经去掉了perf工具,替代它的是simpleperf1工具,对tracepoint的支持比原来的好很多。
3.3 获取系统热点进程
perf中的top工具可以列出当前cpu的热点,还可以附加kernel的符号表让信息可方便分析。命令如下:
$ adb shell mkdir -p /data/local/symbols
$ adb push vmlinux /data/local/symbols/vmlinux
$ adb shell
# perf top --vmlinux=/path/to/vmlinux -d 2
结果输出如下:
</img>
perf top还可以只抓取指定进程的pid,这一般是用在要优化某个程序是非常有用,命令如下:
perf top --vmlinux=/path/to/vmlinux -d 2 -p pid_of_prog
perf top还和系统的top一样可以指定刷新间隔2, 以上命令中的-d选项就是这个功能,单位是秒。
3.4 获取进程的统计信息
perf stat用于获取进程某个时间段内的pmu统计信息,命令如下:
# ./perf stat -p 1415
ctrl+c退出,或发信号让perf进程退出都可以看到统计结果,例如:
</img>
一些明显的异常值会被标注为红色,例如上图是浏览器跑fishtank时候抓的统计信息,可以看到分支预测的失败率非常高,结合perf的热点分析工具可以进一步缩小范围找到分支预测失败的原因。
3.5 收集进程的profile数据
perf record用于记录详细的profile数据,可以指定记录某个进程,还可以记录调用栈,命令如下:
# perf record -g -p pid -o /data/local/perf.data
也可以指定只抓取某个事件,事件列表可以通过上面的perf list得到,例如:
# ./perf record -e cache-misses -p 1415
3.6 分析profile数据
perf report用户分析抓到的profile数据,一般会先把数据发到pc上再分析,命令如下:
$ adb pull /data/local/perf.data
$ perf report --objdump=aarch64-linux-android-objdump --vmlinux=/path/to/vmlinux --symfs $ANDROID_PRODUCT_OUT/symbols -i perf.data
结果如图:
</img>
上图有‘+’的地方可以用‘enter’键来遍历其调用关系。
3.7 FlameGraph
还可以通过一些脚本来方便分析调用关系,Flame Graph就是一个比较好用的可视化分析工具。
下载:
$ git clone https://github.com/brendangregg/FlameGraph.git
生成图形:
perf script --vmlinux=<kernel_folder>/vmlinux --symfs $ANDROID_PRODUCT_OUT/symbols -i perf.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.html
在Linux平台使用
arm版本的linux发行版很多都没有提供perf的包,所以需要自己手动编译一个perf,由于perf依赖的elfutils/binutils/zlib,所以实际上需要交叉编译四个东西。
首先编译zlib,源码地址
$ CC=aarch64-linux-gnu-gcc ./configure --prefix=/home/cmc/workspace/linaro/toolchain/armlinux/aarch64/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/usr
$ make && make install
==Note: prefix要指向你的交叉编译工具的库目录==
编译elfutils,我直接用的最新的版本的:
$ git clone git://sourceware.org/git/elfutils.git
配置:
$ cd /path/to/elfutils
$ mkdir build
$ ./configure --enable-maintainer-mode --host=aarch64-linux-gnu --prefix=/home/cmc/workspace/linaro/elfutils/build
修改Makefile: 删除elfutils根目录下Makefile里面的libcpu
修改backends/Makefile: 删除backends/Makefile中的libebl_i386和libebl_x86_64有关的所有东西
编译:
$ make && make install
编译binutils,这个要考虑和gcc版本的兼容,我用的2.28.1的版本,源代码地址
$ cd /path/to/binutils
$ mkdir build
$ ../configure --target=aarch64-linux-gnu --host=aarch64-linux-gnu --prefix=/home/cmc/workspace/linaro/binutils-2.28.1/build
$ make && make install
编译perf,perf是kernel一起发布的,所以直接下载一个kernel就有了,但是交叉编译的话,需要改一些东西:
修改Makefile.perf,在前面加入:
EXTRA_CFLAGS=-I/path/to/elfutils/build/inclue -L/path/to/elfutils/build/lib -I/path/to/binutils/build/include -L/path/to/binutils/build/lib
WERROR=0
NO_LIBPERL=1
NO_LIBPYTHON=1
编译
$ cd /path/to/kernel/tools/perf
$ make -f Makefile.perf perf ARCH=arm64 CROSS_COMPILE=/home/cmc/workspace/linaro/toolchain/armlinux/aarch64/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- -j8
理论上在arm的linux发行版上直接编译perf应该也是可以的,但是我没有试过。用法的话和android是一样的,这里就不叙说了。
Simpleperf使用
android 7.0开始提供了一个更完整的perf版本simpleperf:
$ source build/envsetup.sh
$ lunch
$ mmma system/extras/simpleperf
simpleperf相对之前google移植的perf有以下改进
- 支持剖析apk中兼容的共享库,从 .gnu_debugdata 段读取符号表和调试信息
- 提供更方便分析的脚本
- 纯静态,所以和android版本无关,只要指令集兼容都能跑
ndk r13开始就提供了simpleperf工具,所以也可以直接下载编译好的工具:
$ git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/simpleperf
用法上和perf是类似的,命令基本通用,可以直接参考上面perf的命令。
simpleperf更多信息, 特别是调试java程序的方法,请参考官方手册
1. 介绍
streamline是ds5提供的一个性能分析工具,通过抓取cpu和gpu的内部硬件计数器和kernel的一些软件tracepoint来实现性能分析,功能比较强大。
2. 抓取数据
-
准备工具
- 首先需要下载ds5,一些新的cpu和gpu可能需要较新的ds5才能支持,我目前用的是5.26版本。ds5是需要license的,不过可以先申请一个30天的全功能试用license,下载安装和申请license的流程就不在这里介绍了。
- 设备端需要和host建立连接,目前支持两种连接方式:adb和网络
- 需要设备端的root权限,不然没法在设备端运行gatord
- 需要和固件匹配的符号表,方便后续的分析
-
gatord
设备端需要运行一个守护进程来和host端做交互,早期版本的mali驱动或ds5版本还需要重新编译kernel的gpu驱动来启用gatord,不过目前新版本的ds5已经没有这个问题了。
$ adb push /path/to/ds5/sw/streamline/bin/$ARCH/gatord /data/local/ # cd /data/local/ # ./gatord &
-
建立连接
gatord跑起来之后就可以在host端打开streamline,在左上角点击</img>按钮,会弹出如下对话框:
</img>
选中你要调试的设备,点“select”即可。
-
配置计数器
streamline支持的计数器和设备相关,在连接完成后,可以点击</img>按钮,会弹出对话框,列出所有支持的计数器:
</img>
左边是可选的计数器,右边是已选的计数器,在可选计数器上双击即可移动到已选计数器,完成后点“save”按钮即可,全部重选可以点击“load defaults”先恢复默认设置。
-
抓取数据
计数器选好以后,可以点击</img>开启抓取,结束时点击</img>按钮,这时候会自动跳转到分析界面。
3. 分析结果
streamline相对于传统的profile工具的优势在于:丰富的硬件计数器支持,可以很方便的看到cache,bus和gpu内部状态。
-
加载符号表
右键单击左侧的我们抓取到的数据名称,在弹出的菜单中点击”analyze”,会弹出如下对话框:
</img>
点击红色箭头指向的按钮就可以添加带符号信息的elf文件了。不用全部添加,根据热点添加即可。
-
Heat Map
这个视图可以很方便的找到热点线程,点击左下角的</img>
点击▶️可以展开各个进程,点击每个线程的名字,可以只显示这个线程的统计值,例如线程的cpu占有率,线程的miss rate等。
时间轴上有个滑块,可以拉伸和移动,以显示某个时间段内的统计数据,类似下图:
</img>
-
Core Map
这个视图可以看到每个线程各个时刻都在那个core上跑,对于看调度问题比较方便,比如不合理的cpu迁移。
-
Cluster Map
这个视图可以看到每个线程在当前跑在哪个cluster,可以分析是否有不合理的大小核迁移导致性能下降。
-
Samples
这个视图可以分析每个时间片内函数的cpu占比,时间片可以通过上面时间轴上的滑块控制,对于分析热点函数比较有用。
-
Processes
这个视图可以分析热点进程的cpu占比。
-
Functions
点击上部的”functions”选项卡,可以看到整个抓取周期内的函数热点统计,如下图所示:
</img>
-
Call Paths
如果想看函数的调用关系,可以切换到“call paths”选项卡
1. 介绍
systrace是目前android上最主要的性能调试手段,有以下优点:
- 完全免费,安装和使用都比较简便
- 由于不需要在设备端运行监控程序,所以不需要root权限1
- 界面友好
同时也有一些缺点:
- 基于tracepoint,所以只会收集你加过trace的函数信息,android在大部分模块的重要函数里都加了trace了,所以大部分情况下还是够用,同时android也提供了几个函数方便添加自己的trace。
- 看不到pmu计数器的信息,也看不到gpu和memory的信息(理论上内核驱动如果定时收集这些信息并加到trace里,systrace应该也能看到)
2. 用法
为了更方便介绍systrace,我这里举一个实际的性能分析例子:fishtank在1000只鱼的情况下帧率很低
准备工作
获取systrace有三种方式:
-
路径:/path_to_sdk/platform-tools/systrace
-
提供了图形化抓取功能,实际上也是嗲用sdk里的systrace
-
直接用android源码里的
路径:/path_to_android/external/chromium-trace
抓取数据
systrace的命令格式:
$ cd external/chromium-trace/
$ python ./systrace.py -h
Usage: systrace.py [options] [category1 [category2 ...]]
Example: systrace.py -b 32768 -t 15 gfx input view sched freq
Options:
-h, --help show this help message and exit
-o FILE write trace output to FILE
-t N, --time=N trace for N seconds
-l, --list-categories
list the available categories and exit
-j, --json write a JSON file
--link-assets (deprecated)
--from-file=FROM_FILE
read the trace from a file (compressed) rather
thanrunning a live trace
--asset-dir=ASSET_DIR
(deprecated)
-e DEVICE_SERIAL_NUMBER, --serial=DEVICE_SERIAL_NUMBER
adb device serial number
--target=TARGET chose tracing target (android or linux)
--timeout=TIMEOUT timeout for start and stop tracing (seconds)
--collection-timeout=COLLECTION_TIMEOUT
timeout for data collection (seconds)
Atrace and Ftrace options:
-b N, --buf-size=N use a trace buffer size of N KB
--no-fix-threads don't fix missing or truncated thread names
--no-fix-tgids Do not run extra commands to restore missing thread to
thread group id mappings.
--no-fix-circular don't fix truncated circular traces
Atrace options:
--atrace-categories=ATRACE_CATEGORIES
Select atrace categories with a comma-delimited list,
e.g. --atrace-categories=cat1,cat2,cat3
-k KFUNCS, --ktrace=KFUNCS
specify a comma-separated list of kernel functions to
trace
-a APP_NAME, --app=APP_NAME
enable application-level tracing for comma-separated
list of app cmdlines
--no-compress Tell the device not to send the trace data in
compressed form.
--boot reboot the device with tracing during boot enabled.The
report is created by hitting Ctrl+C after the
devicehas booted up.
Battor trace options:
--battor-categories=BATTOR_CATEGORIES
Select battor categories with a comma-delimited list,
e.g. --battor-categories=cat1,cat2,cat3
--hubs=HUB_TYPES List of hub types to check for for BattOr mapping.
Used when updating mapping file.
--serial-map=SERIAL_MAP
File containing pregenerated map of phone serial
numbers to BattOr serial numbers.
--battor_path=BATTOR_PATH
specify a BattOr path to use
--update-map force update of phone-to-BattOr map
--battor Use the BattOr tracing agent.
Ftrace options:
--ftrace-categories=FTRACE_CATEGORIES
Select ftrace categories with a comma-delimited list,
e.g. --ftrace-categories=cat1,cat2,cat3
systrace支持的atrace类别有:
$ adb root
$ python ./systrace.py -l
gfx - Graphics
input - Input
view - View System
webview - WebView
wm - Window Manager
am - Activity Manager
sm - Sync Manager
audio - Audio
video - Video
camera - Camera
hal - Hardware Modules
app - Application
res - Resource Loading
dalvik - Dalvik VM
rs - RenderScript
bionic - Bionic C Library
power - Power Management
pm - Package Manager
ss - System Server
database - Database
network - Network
sched - CPU Scheduling
freq - CPU Frequency
idle - CPU Idle
load - CPU Load
memreclaim - Kernel Memory Reclaim
binder_driver - Binder Kernel driver
binder_lock - Binder global lock trace
NOTE: more categories may be available with adb root
Note: ==有写事件需要设备的root权限才能操作,所以最好先切到root权限==
除了支持android在ftrace基础上扩展的atrace,systrace也是支持kernel原生的ftrace的,还支持单独抓取某个kernel函数,当然前提是这个函数本身有tracepoint,具体可以参见上面的命令帮助信息。还可以直接用trace文件做输入,这种离线分析功能应该在分析android引导过程的时候比较有用。
在抓取前要先大致确定这个场景涉及到哪些模块,再回到我们这次要分析的场景是:浏览器跑fishtank中开启1000只鱼的时候帧率很低;第一时间能想到的模块有:gfx webview sched freq load workq disk
先在设备上重现问题,然后在host端执行如下命令:
$ cd external/chromium-trace
$ python ./systrace.py -t 10 -o fishtank.html gfx webview sched freq load workq disk
这个fishtank.html即我们抓到的数据。为了方便和本文对照,我上传到网盘了。
3. 分析
分析数据需要chrome浏览器,版本最好要新一些,太旧可能会有兼容问题,因为这个html并不符合w3c的标准。
用chrome打开以后,界面如下:
</img>
左列是抓取的线程名或trace名,既然是绘制问题,我们第一个要看肯定是绘制的线程,android 5.0以前是在ui线程做绘制的,以后的版本都是在render线程做绘制,所以我们先拉到render线程,可以看到如下:
</img>
点击右边的有四个按键,分别对应四种模式如下:
</img>
先用”时间线“模式拉个1s左右的时间线,然后切到“选择”模式,选择这段时间线内render线程的区域,会自动在下方列出这个区域的函数统计:
</img>
可以看到帧率确实很低,这段时间的绘制只有9次(drawgl次数),平均耗时29ms,平均间隔是111ms,所以主要原因是绘制间隔太大。继续往下分析就要根据浏览器的渲染模型了,我们知道chromium里是由光栅化和canvas线程完成实际绘制的(内部叫paint),而ui线程或render线程来完成贴图(内部叫draw)。因为这个网页用的canvas,所以我们先用“时间线”模式拉出绘制间隔,然后顺着时间线往下找绘制线程如下:
</img>
可以看到最近这一次的绘制耗时124ms,拉开看一下具体耗时:
</img>
刚好有1000个绘制,这里会不会就是那1000只鱼,通过查看网页源码,可以确认:
</img>
</img>
javascript是单线程运行的,所以这里无法用到多核,javascript worker技术是让js跑多线程,但是这个网页并没有用到这个技术。
要解决这个问题,要么改网页代码,启用javascript worker技术,这样应该能让帧率提升不少;还有一种办法就是启用chromium的gpu光栅化技术,即不调用skia做2d绘制,直接用gpu来绘制,但是目前这个技术缺陷较多,会导致某些场景下闪屏。