Simpleperf介绍
什么是simpleperf
Simpleperf是Android平臺的一個本地層性能分析工具。它的命令行界面支持與linux-tools perf大致相同的選項,但是它還支持許多Android特有的改進。
Simpleperf是Android開源項目(AOSP)的一部分。其源代碼 位于。其最新的文檔 位于。Bugs 和 功能需求可以提交到 githb上。
Simpleperf是如何工作的
現代的CPU具有一個硬件組件,稱為性能監控單元(PMU)。PMU具有一些硬件計數器,計數一些諸如 經歷了多少次CPU周期,執行了多少條指令,或發生了多少次緩存未命中 等的事件。
Linux內核將這些硬件計數器包裝到硬件perf事件 (hardware perf events)中。此外,Linux內核還提供了獨立于硬件的軟件事件和跟蹤點事件。Linux內核通過 perf_event_open 系統調用將這些都暴露給了用戶空間,這正是simpleperf所使用的機制。
Simpleperf具有三個主要的功能:stat,record 和 report。
Stat命令給出了在一個時間段內被分析的進程中發生了多少事件的摘要。以下是它的工作原理:
Record 命令在一段時間內記錄剖析進程的樣本。它的工作原理如下:
Report 命令讀取 “perf.data” 文件及所有被剖析進程用到的共享庫,并輸出一份報告,展示時間消耗在了哪里。
主 simpleperf 命令
Simpleperf 支持一些子命令,包括 list,stat,record,report。每個子命令支持不同的選項。這一節只描述最重要的子命令和選項。要了解所有的子命令和選項,請使用 --help。
# List all subcommands. $simpleperf --help# Print help message for record subcommand. $simpleperf record --helpsimpleperf list
simpleperf list 被用于列出設備上所有可用的事件。由于應該和內核的差異,不同的設備可以支持不同的事件。
$simpleperf list List of hw-cache events:branch-loads... List of hardware events:cpu-cyclesinstructions... List of software events:cpu-clocktask-clock...simpleperf stat
simpleperf stat 被用于獲取被剖析程序或系統范圍內的原始事件計數器信息。通過傳入選項,我們可以選擇使用哪些事件,監視哪個進程/線程,監視多長時間,以及打印的間隔。下面是一個例子。
# Stat using default events (cpu-cycles,instructions,...), and monitor # process 7394 for 10 seconds. $simpleperf stat -p 7394 --duration 10 Performance counter statistics:1,320,496,145 cpu-cycles # 0.131736 GHz (100%)510,426,028 instructions # 2.587047 cycles per instruction (100%)4,692,338 branch-misses # 468.118 K/sec (100%) 886.008130(ms) task-clock # 0.088390 cpus used (100%)753 context-switches # 75.121 /sec (100%)870 page-faults # 86.793 /sec (100%)Total test time: 10.023829 seconds.選擇事件
我們可以通過 -e 選項選擇使用哪個事件。下面是例子:
# Stat event cpu-cycles. $simpleperf stat -e cpu-cycles -p 11904 --duration 10# Stat event cache-references and cache-misses. $simpleperf stat -e cache-references,cache-misses -p 11904 --duration 10當運行 stat 命令時,如果硬件事件的數量大于 PMU中可用的硬件計數器的數量,則內核在事件間共享硬件計數器,因此每個事件只在總時間中的一部分內被監視。在下面的例子中,每一行的最后都有一個百分比,展示了每個事件實際被監視的時間占總時間的百分比。
# Stat using event cache-references, cache-references:u,.... $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 選項。下面是一個例子。
# Stat using event cache-references, cache-references:u,.... $simpleperf stat -p 7394 --group cache-references,cache-misses --group cache-references:u,cache-misses:u --group cache-references:k,cache-misses:k -e instructions --duration 1 Performance counter statistics:3,638,900 cache-references # 4.786 M/sec (74%)65,171 cache-misses # 1.790953% miss rate (74%) 2,390,433 cache-references:u # 3.153 M/sec (74%)32,280 cache-misses:u # 1.350383% miss rate (74%)879,035 cache-references:k # 1.251 M/sec (68%)30,303 cache-misses:k # 3.447303% miss rate (68%) 8,921,161 instructions # 10.070 M/sec (86%)Total test time: 1.029843 seconds.選擇監視目標
我們可以通過 -p 選項或 -t 選項選擇監視哪個進程或線程。監視一個進程如同監視進程中的所有線程。Simpleperf 也可以 fork 一個子進程來運行新命令,然后監視子進程。下面是例子。
# Stat process 11904 and 11905. $simpleperf stat -p 11904,11905 --duration 10# Stat thread 11904 and 11905. $simpleperf stat -t 11904,11905 --duration 10# Start a child process running `ls`, and stat it. $simpleperf stat ls決定監視多長時間
當監視已有線程時,我們可以使用 --duration 選項決定監視多長時間。當監視執行一個新命令的子進程時,simpleperf 將一直監視子進程直至其結束。在這種情況下,我們可以使用 Ctrl-C 在任何時間停止監視。例子如下。
# Stat process 11904 for 10 seconds. $simpleperf stat -p 11904 --duration 10# Stat until the child process running `ls` finishes. $simpleperf stat ls# Stop monitoring using Ctrl-C. $simpleperf stat -p 11904 --duration 10 ^C決定打印的間隔
當監視 perf 計數器時,我們還可以使用 --interval 選項決定打印的間隔。例子如下。
# Print stat for process 11904 every 300ms. $simpleperf stat -p 11904 --duration 10 --interval 300# Print system wide stat at interval of 300ms for 10 seconds (rooted device only). # system wide profiling needs root privilege $su 0 simpleperf stat -a --duration 10 --interval 300在 systrace 中顯示計數器
simpleperf 還可以與systrace一起工作來將計數器轉儲進收集的trace中。下面是一個執行系統范圍的 stat 的例子。
# capture instructions (kernel only) and cache misses with interval of 300 milliseconds for 15 seconds $su 0 simpleperf stat -e instructions:k,cache-misses -a --interval 300 --duration 15 # on host launch systrace to collect trace for 10 seconds (HOST)$external/chromium-trace/systrace.py --time=10 -o new.html sched gfx view # open the collected new.html in browser and perf counters will be shown upsimpleperf record
simpleperf record用于轉儲被剖析程序的記錄。通過傳入選項,我們可以選擇使用哪個事件,監視哪個進程/線程,以什么頻率轉儲記錄,監視多長時間,以及將記錄存儲到哪里。
# Record on process 7394 for 10 seconds, using default event (cpu-cycles), # using default sample frequency (4000 samples per second), writing records # to perf.data. $simpleperf record -p 7394 --duration 10 simpleperf I 07-11 21:44:11 17522 17522 cmd_record.cpp:316] Samples recorded: 21430. Samples lost: 0.選擇事件
在大多數情況下,cpu-cycles 事件被用于評估消耗的CPU時間。作為一個硬件事件,它精確而高效。我們還可以通過 -e 選項使用其它事件。下面是一個例子。
# Record using event instructions. $simpleperf record -e instructions -p 11904 --duration 10選擇監視目標
record命令中選擇目標的方式與 stat 命令中的類似。例子如下。
# Record process 11904 and 11905. $simpleperf record -p 11904,11905 --duration 10# Record thread 11904 and 11905. $simpleperf record -t 11904,11905 --duration 10# Record a child process running `ls`. $simpleperf record ls設置記錄的頻率
我們可以通過 -f 或 -c 選項設置轉儲記錄的頻率。比如,-f 4000 意味著當監視的線程運行時每秒轉儲接近 4000 個記錄。如果監視的線程一秒鐘運行了 0.2 s(其它時間它可能被搶占或阻塞),simpleperf 每秒轉儲大約 4000 * 0.2 / 1.0 = 800 個記錄。另一種方式是使用 -c 選項。比如,-c 10000 意味著每發生 10000 次事件轉儲一個記錄。例子如下。
# Record with sample frequency 1000: sample 1000 times every second running. $simpleperf record -f 1000 -p 11904,11905 --duration 10# Record with sample period 100000: sample 1 time every 100000 events. $simpleperf record -c 100000 -t 11904,11905 --duration 10決定監視多長時間
record 命令中決定監視多長時間的方式與 stat 命令中的類似。例子如下。
# Record process 11904 for 10 seconds. $simpleperf record -p 11904 --duration 10# Record until the child process running `ls` finishes. $simpleperf record ls# Stop monitoring using Ctrl-C. $simpleperf record -p 11904 --duration 10 ^C設置存儲記錄的路徑
默認情況下,simpleperf 將記錄保存至當前文件夾下的 perf.data 文件中。我們可以使用 -o 選項設置存儲記錄的路徑。下面是一個例子。
# Write records to data/perf2.data. $simpleperf record -p 11904 -o data/perf2.data --duration 10simpleperf report
simpleperf report 被用來基于 simpleperf record 命令生成的 perf.data 產生報告。Report 命令將記錄分組為不同的樣本項,基于每個樣本項包含的事件的多少對樣本項排序,并打印每個樣本項。通過傳入選項,我們可以選擇到哪里尋找被監視的程序使用的 perf.data 和 可執行二進制文件,過濾不感興趣的記錄,并決定如何分組記錄。
下面是一個例子。記錄被分為 4 個樣本項,每項一行。有一些列,每列展示了屬于一個樣本項的信息片段。第一列是 Overhead,它展示了當前樣本項中的事件占總事件的百分比。由于 perf 事件是 cpu-cycles,overhead 可被視為是每個函數占用的 cpu 的百分比。
# Reports perf.data, using only records sampled in libsudo-game-jni.so, # grouping records using thread name(comm), process id(pid), thread id(tid), # function name(symbol), and showing sample count for each row. $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: 546356211Overhead 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設置存儲記錄的路徑
默認情況下,simpleperf 讀取當前目錄下的 perf.data。我們可以使用 -i 選項選擇從另一個文件讀取記錄。
$simpleperf report -i data/perf2.data設置查找可執行二進制文件的路徑
如果要生成函數符號報告,simpleperf 需要讀取被監視的進程使用的可執行二進制文件來獲取符號表和調試信息。默認情況下,路徑是記錄時被監視的進程使用的可執行二進制文件,然而,在生成報告時這些二進制文件可能不存在,或不包含符號表和調試信息。因此我們可以使用 --symfs 來重定向路徑。下面是一個例子。
$simpleperf report # In this case, when simpleperf wants to read executable binary /A/b, # it reads file in /A/b.$simpleperf report --symfs /debug_dir # In this case, when simpleperf wants to read executable binary /A/b, # it prefers file in /debug_dir/A/b to file in /A/b.過濾記錄
當生成報告時,可能不是對所有記錄都感興趣。Simpleperf 支持五鐘過濾器來選擇感興趣的記錄。下面是例子。
# Report records in threads having name sudogame. $simpleperf report --comms sudogame# Report records in process 7394 or 7395 $simpleperf report --pids 7394,7395# Report records in thread 7394 or 7395. $simpleperf report --tids 7394,7395# Report records in libsudo-game-jni.so. $simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so# Report records in function checkValid or canFindSolution_r. $simpleperf report --symbols "checkValid(Board const&, int, int);canFindSolution_r(Board&, int, int)"決定如何將記錄分組為樣本項
Simpleperf 使用 --sort 選項決定如何分組樣本項。下面是例子。
# Group records based on their process id: records having the same process # id are in the same sample entry. $simpleperf report --sort pid# Group records based on their thread id and thread comm: records having # the same thread id and thread name are in the same sample entry. $simpleperf report --sort tid,comm# Group records based on their binary and function: records in the same # binary and function are in the same sample entry. $simpleperf report --sort dso,symbol# Default option: --sort comm,pid,tid,dso,symbol. Group records in the same # thread, and belong to the same function in the same binary. $simpleperf reportsimpleperf 的特性
Simpleperf 的工作方式與 linux-tools-perf 類似,但它有如下的提升:
ndk中的Simpleperf工具
ndk 中的 simpleperf 工具包含三個部分:在 Android 設備上運行的 simpleperf 可執行文件,在主機上運行的 simpleperf 可執行文件,和 python 腳本。
設備上的 simpleperf
在設備上運行的 simpleperf 位于 bin/android 目錄下。它包含不同體系架構的 Android 上運行的靜態二進制文件。它們可被用于剖析運行在設備上的進程,并生成 perf.data。
主機上的 simpleperf
運行與主機上的 Simpleperfs 位于bin/darwin,bin/linux 和 bin/windows。它們可被用于在主機上解析 perf.data。
腳本
腳本被用于使得剖析和解析剖析結果更方便。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。
使用 simpleperf 工具的例子
這個部分展示了如何使用 simpleperf 工具來剖析一個 Android 應用。
準備一個可調試(debuggable)的應用
應用程序的包名是 com.example.sudogame。它包含 java 代碼和 c++ 代碼。我們需要運行一份在其 AndroidManifest.xml 元素中 android:debuggable=”true” 的app,因為我們不能為 non-debuggable apps 使用 run-as。應用應該已經安裝在設備上了,而且我們可以通過 adb 連接設備。
使用命令行剖析
為了記錄剖析數據,我們需要下載 simpleperf 和包含調試信息的本地庫到設備上,運行 simpleperf 產生剖析數據:perf.data,并運行 simpleperf 為 perf.data 生成報告。步驟如下。
1. Enable profiling
$adb shell setprop security.perf_harden 02. 尋找運行 app 的進程
在 app 的上下文運行 ps。在 >=O 的設備上,用 ps -e 來替代。
$adb shell angler:/ $ run-as com.example.sudogame angler:/data/data/com.example.sudogame $ ps u0_a93 10324 570 1030480 58104 SyS_epoll_ 00f41b7528 S com.example.sudogame u0_a93 10447 10441 7716 1588 sigsuspend 753c515d34 S sh u0_a93 10453 10447 9112 1644 0 7ba07ff664 R ps因此是進程 10324 運行app。
3. 將 simpleperf 下載到 app 的 data 目錄
首先我們需要找出 app 使用的是哪個體系架構。有許多中方式,這里我們只檢查進程的映射。
angler:/data/data/com.example.sudogame $cat /proc/10324/maps | grep boot.art 70f34000-7144e000 r--p 00000000 fd:00 1082 /system/framework/arm/boot.oat路徑顯示是它是 arm。因此我們將 simpleperf 下載到設備上的 arm 目錄下。
$adb push bin/android/arm/simpleperf /data/local/tmp $adb shell angler:/ $ run-as com.example.sudogame angler:/data/data/com.example.sudogame $ cp /data/local/tmp/simpleperf .4. 記錄 perf.data
angler:/data/data/com.example.sudogame $./simpleperf record -p 10324 --duration 30 simpleperf I 01-01 09:26:39 10598 10598 cmd_record.cpp:341] Samples recorded: 49471. Samples lost: 0. angler:/data/data/com.example.sudogame $ls -lh perf.data -rw-rw-rw- 1 u0_a93 u0_a93 2.6M 2017-01-01 09:26 perf.data在記錄時不要忘記運行 app。否則,我們可能無法獲得樣本,由于進程仍在休眠。
5. 為 perf.data 生成報告
有不同的方式來為 perf.data 生成報告。下面展示了一些例子。
報告不同線程中的樣本。
angler:/data/data/com.example.sudogame $./simpleperf report --sort pid,tid,comm Cmdline: /data/data/com.example.sudogame/simpleperf record -p 10324 --duration 30 Arch: arm64 Event: cpu-cycles (type 0, config 0) Samples: 49471 Event count: 16700769019Overhead Pid Tid Command 66.31% 10324 10324 xample.sudogame 30.97% 10324 10340 RenderThread ...報告主線程中不同二進制文件中的樣本。
angler:/data/data/com.example.sudogame $./simpleperf report --tids 10324 --sort dso -n ... Overhead Sample Shared Object 37.71% 9970 /system/lib/libc.so 35.45% 9786 [kernel.kallsyms] 8.71% 3305 /system/lib/libart.so 6.44% 2405 /system/framework/arm/boot-framework.oat 5.64% 1480 /system/lib/libcutils.so 1.55% 426 /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so ...報告主線程中 libsudo-game-jni.so 中的不同函數的采樣。
angler:/data/data/com.example.sudogame $./simpleperf report --tids 10324 --dsos /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so --sort symbol -n ... Overhead Sample Symbol 8.94% 35 libsudo-game-jni.so[+1d54] 5.71% 25 libsudo-game-jni.so[+1dae] 5.70% 23 @plt 5.09% 22 libsudo-game-jni.so[+1d88] 4.54% 19 libsudo-game-jni.so[+1d82] 3.61% 14 libsudo-game-jni.so[+1f3c] ...在上面的結果中,大多數符號是 二進制文件名[+virual_addr] 的形式呈現。那是由于設備上使用的 libsudo-game-jni.so 已經拋離了 .symbol 段。我們可以將帶有調試信息的 libsudo-game-jni.so 下載到設備上。在 android studio工程中,它位于 app/build/intermediates/binaries/debug/arm/obj/armeabi-v7a/libsudo-game-jni.so。我們不得不下載 libsudo-game-jni.so 到與 perf.data 中記錄的相同的相對路徑(否則,simpleperf無法找到它)。在這個例子中,是/data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so。
為包含了調試信息的庫使用的符號生成報告。
$adb push app/build/intermediates/binaries/debug/arm/obj/armeabi-v7a/libsudo-game-jni.so /data/local/tmp $adb shell angler:/ $ run-as com.example.sudogame angler:/data/data/com.example.sudogame $ mkdir -p data/app/com.example.sudogame-1/lib/arm angler:/data/data/com.example.sudogame $cp /data/local/tmp/libsudo-game-jni.so data/app/com.example.sudogame-1/lib/arm angler:/data/data/com.example.sudogame $./simpleperf report --tids 10324 --dsos /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so --sort symbol -n --symfs . ... Overhead Sample Symbol 75.18% 317 checkValid(Board const&, int, int) 14.43% 60 canFindSolution_r(Board&, int, int) 5.70% 23 @plt 3.51% 20 randomBlock_r(Board&, int, int, int, int, int) ...報告一個函數中的采樣。
angler:/data/data/com.example.sudogame $./simpleperf report --tids 10324 --dsos /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so --symbols “checkValid(Board const&, int, int)” --sort vaddr_in_file -n --symfs . ... Overhead Sample VaddrInFile 11.89% 35 0x1d54 7.59% 25 0x1dae 6.77% 22 0x1d88 6.03% 19 0x1d82 ...6. 記錄并報告調用圖
調用圖是顯示了函數調用關系的樹。下面是一個例子。
main() {FunctionOne();FunctionTwo(); } FunctionOne() {FunctionTwo();FunctionThree(); } callgraph:main-> FunctionOne| || |-> FunctionTwo| |-> FunctionThree||-> FunctionTwo基于調用圖記錄 dwarf
為了生成調用圖,simpleperf 需要為每個記錄生成調用鏈。Simpleperf 需要內核為每個記錄轉儲用戶棧和用戶寄存器集,然后它追蹤用戶棧來查找函數調用鏈。為了解析調用鏈,它需要 dwarf 調用幀信息的支持,這些通常位于二進制文件的 .eh_frame 或 .debug_frame段。因此我們需要使用 --symfs 指出帶有調試信息的 libsudo-game-jni.so 位于哪里。
angler:/data/data/com.example.sudogame $./simpleperf record -p 10324 -g --symfs . --duration 30 simpleperf I 01-01 09:59:42 11021 11021 cmd_record.cpp:341] Samples recorded: 60700. Samples lost: 1240.注意內核無法轉儲 >= 64K 的用戶棧,因此基于調用圖的 dwarf 不要包含消耗了 >= 64K 棧的調用鏈。此外,由于我們需要轉儲每個記錄的棧,則可能丟失一些記錄。通常,失去一些記錄沒關系。
基于調用圖記錄棧幀
另外一種生成調用圖的方式依賴內核為每個記錄解析調用鏈。為了使它成為可能,內核需要能夠識別每個函數調用的棧幀。這不總是可能的,因為編譯器可能優化掉棧幀,或內核無法識別使用的棧幀風格。因此它如何工作視情況而定(它在 arm64 上工作的很好,但在 arm 上不行)。
angler:/data/data/com.example.sudogame $./simpleperf record -p 10324 --call-graph fp --symfs . --duration 30 simpleperf I 01-01 10:03:58 11267 11267 cmd_record.cpp:341] Samples recorded: 56736. Samples lost: 0.報告調用圖
報告累積的周期。在下面的表中,第一列是 “Children”,它是函數及那個函數調用的函數所占的cpu 周期百分比。第二列是 “Self”,它只是一個函數的cpu 周期百分比。比如,checkValid() 自身消耗 1.28% 的 cpus,但通過運行它自身及調用其它函數,它消耗了 29.43%。
angler:/data/data/com.example.sudogame $./simpleperf report --children --symfs . ... Children Self Command Pid Tid Shared Object Symbol 31.94% 0.00% xample.sudogame 10324 10324 [kernel.kallsyms] [kernel.kallsyms][+ffffffc000204268] 31.10% 0.92% xample.sudogame 10324 10324 /system/lib/libc.so writev 29.43% 1.28% xample.sudogame 10324 10324 /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so checkValid(Board const&, int, int) 28.43% 0.34% xample.sudogame 10324 10324 /system/lib/liblog.so __android_log_print 28.24% 0.00% xample.sudogame 10324 10324 /system/lib/libcutils.so libcutils.so[+107b7] 28.10% 0.27% xample.sudogame 10324 10324 /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so canFindSolution_r(Board&, int, int) ...報告調用圖。
angler:/data/data/com.example.sudogame $./simpleperf report -g --symfs . >report angler:/data/data/com.example.sudogame $exit angler:/ $cp /data/data/com.example.sudogame/report /data/local/tmp angler:/ $exit $adb pull /data/local/tmp/report . $cat report ... 29.43% 1.28% xample.sudogame 10324 10324 /data/app/com.example.sudogame-1/lib/arm/libsudo-game-jni.so checkValid(Board const&, int, int)|-- checkValid(Board const&, int, int)||--95.50%-- __android_log_print| |--0.68%-- [hit in function]| || |--51.84%-- __android_log_buf_write| | |--2.07%-- [hit in function]| | || | |--30.74%-- libcutils.so[+c69d] ...以 callee 模式報告調用圖。我們還可以展示一個函數是如何被其它函數調用的。
angler:/data/data/com.example.sudogame $./simpleperf report -g callee --symfs . >report $adb shell run-as com.example.sudogame cat report >report $cat report … 28.43% 0.34% xample.sudogame 10324 10324 /system/lib/liblog.so __android_log_print|-- __android_log_print||--97.82%-- checkValid(Board const&, int, int)| |--0.13%-- [hit in function]| || |--94.89%-- canFindSolution_r(Board&, int, int)| | |--0.01%-- [hit in function]| | | ...剖析 Java 代碼
Simpleperf 只支持剖析 ELF 格式二進制文件中的本地指令。如果 java 代碼由解釋器執行,或使用 jit 緩存,則它不能由 simpleperf 剖析。由于 Android 支持提前編譯,它可以將 java 字節碼編譯為包含調試信息的本地層指令。在 Android 版本 <= M 的設備上,我們需要 root 權限來編譯包含調試信息的 java 字節碼。然而,在 Android 版本 >= N 的設備上,我們無需 root 權限就可以做這些。
在Android N上
1. 將 java 代碼完整編譯為本地層指令。
$adb shell setprop debug.generate-debug-info true $adb shell cmd package compile -f -m speed com.example.sudogame // restart the app to take effect2. 記錄 perf.data
angler:/data/data/com.example.sudogame $./simpleperf record -p 11826 -g --symfs . --duration 30 simpleperf I 01-01 10:31:40 11859 11859 cmd_record.cpp:341] Samples recorded: 50576. Samples lost: 2139.3. 為 perf.data 生成報告
angler:/data/data/com.example.sudogame $./simpleperf report -g --symfs . >report angler:/data/data/com.example.sudogame $exit angler:/ $cp /data/data/com.example.sudogame/report /data/local/tmp angler:/ $exit $adb pull /data/local/tmp/report . $cat report ... 21.14% 0.00% xample.sudogame 11826 11826 /data/app/com.example.sudogame-1/oat/arm/base.odex boolean com.example.sudogame.MainActivity.onOptionsItemSelected(android.view.MenuItem)|-- boolean com.example.sudogame.MainActivity.onOptionsItemSelected(android.view.MenuItem)|--99.99%-- void com.example.sudogame.GameView.startNewGame()|--0.01%-- [hit in function]||--99.87%-- void com.example.sudogame.GameModel.reInit()| |--0.01%-- [hit in function]| || |--89.65%-- boolean com.example.sudogame.GameModel.canFindSolution(int[][])| | || | |--99.95%-- Java_com_example_sudogame_GameModel_canFindSolution| | | || | | |--99.49%-- canFindSolution(Board&)| | | | |--0.01%-- [hit in function]| | | | || | | | |--99.97%-- canFindSolution_r(Board&, int, int)| | | | | canFindSolution_r(Board&, int, int) ...在 Android M上
在 M 設備上,我們需要 root 權限來強制 Android 將 java 代碼完全編譯為帶調試信息的 ELF 二進制文件中的本地層指令。我們還需要 root 權限來讀取編譯后的本地層二進制文件(由于 installd 將它們寫到了一個 uid/gid 是 system:install的目錄下)。因而剖析 java 代碼只能在 root 了的設備上完成。
$adb root $adb shell setprop dalvik.vm.dex2oat-flags -g# Reinstall the app. $adb install -r app-debug.apk# Change to the app’s data directory. $ adb root && adb shell device# cd `run-as com.example.sudogame pwd`# Record as root as simpleperf needs to read the generated native binary. device#./simpleperf record -p 25636 -g --symfs . -f 1000 --duration 30 simpleperf I 01-02 07:18:20 27182 27182 cmd_record.cpp:323] Samples recorded: 23552. Samples lost: 39.在 Android L上
在 L 設備上,我們也需要 root 權限來編譯帶調試信息的 app 并訪問本地層二進制文件。
$adb root $adb shell setprop dalvik.vm.dex2oat-flags --include-debug-symbols# Reinstall the app. $adb install -r app-debug.apk使用腳本剖析
盡管使用命令行很靈活,但它可能太復雜了。因而我們提供了 pthon 腳本來幫助運行命令。
使用 app_profiler.py 記錄
app_profiler.py 用于剖析 Android 應用程序。它設置剖析環境,下載 simpleperf 和帶有調試信息的本地層庫,運行 simpleperf 產生 perf.data,并從設備上將 perf.data 和二進制文件拉到主機上。它由 app_profiler.config 配置。下面是一個例子。
app_profiler.config:
app_package_name = “com.example.sudogame” android_studio_project_dir = “/AndroidStudioProjects/SudoGame” # absolute path of the project ... record_options = "-e cpu-cycles:u -f 4000 -g --dump-symbols --duration 30" ...運行 app_profiler.py:
$python app_profiler.py ... INFO:root:profiling is finished.它將生成的 perf.data 拉到主機上,并從設備的 binary_cache 中收集二進制文件。
使用 report.py 生成報告
$python report.py -g它生成一個GUI接口來報告數據。
使用 simpleperf_report_lib.py 處理樣本
simpleperf_report_lib.py 提供了一個接口從 perf.data 讀取樣本。一個例子是 report_sample.py。
展示流程圖
$python report_sample.py >out.perf $stackcollapse-perf.pl out.perf >out.folded $./flamegraph.pl out.folded >a.svg注解源代碼
annotate.py 讀取 perf.data 和 binary_cache 下的二進制文件。然后它知道每個樣本命中哪個 源文件:line。因此它可以注解源代碼。annotate.py 由 annotate.config 配置。下面是一個例子。
annotate.config:
... source_dirs = [“/AndroidStudio/SudoGame”] # It is a directory containing source code. ...運行 annotate.py:
$python annotate.py它生成 annotated_files 目錄。 annotated_files/summary 文件包含每個源文件的概要信息。例子如下。
/AndroidStudioProjects/SudoGame/app/src/main/jni/sudo-game-jni.cpp: accumulated_period: 25.587937%, period: 1.250961%function (checkValid(Board const&, int, int)): line 99, accumulated_period: 23.564356%, period: 0.908457%function (canFindSolution_r(Board&, int, int)): line 135, accumulated_period: 22.260125%, period: 0.142359%function (canFindSolution(Board&)): line 166, accumulated_period: 22.233101%, period: 0.000000%function (Java_com_example_sudogame_GameModel_canFindSolution): line 470, accumulated_period: 21.983184%, period: 0.000000%function (Java_com_example_sudogame_GameModel_initRandomBoard): line 430, accumulated_period: 2.226896%, period: 0.000000%line 27: accumulated_period: 0.011729%, period: 0.000000%line 32: accumulated_period: 0.004362%, period: 0.000000%line 33: accumulated_period: 0.004427%, period: 0.000000%line 36: accumulated_period: 0.003303%, period: 0.000000%line 39: accumulated_period: 0.010367%, period: 0.004123%line 41: accumulated_period: 0.162219%, period: 0.000000%annotated_files/ 還包含由 annotate.py 找到的經過注解的源文件。比如, libsudo-game-jni.cpp 中的 checkValid() 函數的一部分注解后如下。
/* [func] acc_p: 23.564356%, p: 0.908457% */static bool checkValid(const Board& board, int curR, int curC) { /* acc_p: 0.037933%, p: 0.037933% */ int digit = board.digits[curR][curC]; /* acc_p: 0.162355%, p: 0.162355% */ for (int r = 0; r < BOARD_ROWS; ++r) { /* acc_p: 0.020880%, p: 0.020880% */ if (r == curR) { /* acc_p: 0.034691%, p: 0.034691% */ continue;} /* acc_p: 0.176490%, p: 0.176490% */ if (board.digits[r][curC] == digit) { /* acc_p: 14.957673%, p: 0.059022% */ LOGI("conflict (%d, %d) (%d, %d)", curR, curC, r, curC); /* acc_p: 0.016296%, p: 0.016296% */ return false;}}原文地址
總結
以上是生活随笔為你收集整理的Simpleperf介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android app native代码
- 下一篇: 网络优化实践探索文章