性能工具|ANRdaemon
一、README
ANRdaemon是一個守護程序,用於分析由cpu占用過高導致的ANR問題。守護程序借助debugfs來實現信息記錄。要抓的trace模塊提前在/d/tracing
中配置好。根據CPU使用級別的不同,trace可以通過全局開關/d/trace/trace_on
來控制。原始trace文件存儲在/d/tracing/trace
。
如下操作會讓守護程序會啟動:
$ anrd -t 9990 sched gfx am
這意味着在99.90%的CPU利用率以上將開始抓trace,抓的模塊是sched gfx am
使用ANRdaemon_get_trace.sh [device serial]
dump和獲取壓縮的trace文件。
可以使用systrace解壓:
$ systrace.py --from-file=<path to compressed trace file>
已知問題:
在 systrace 輸出中,當跟蹤未運行時,將顯示 anrdaemon。 這是因為守護進程在 CPU 使用率下降時關閉跟蹤,它留在原始跟蹤文件中的最后一個條目是調度程序從某個其他進程切換到守護進程。 然后一段時間后(比如 20 秒后),當 CPU 使用率變高並且守護進程再次打開跟蹤時,sched 記錄的 /d/tracing/trace 中的第一個條目正在從守護進程切換到某個其他進程。 由於這個機制,當 systrace.py 解析原始跟蹤文件時,守護進程顯示為運行了整個 20 秒(因為從 systrace 的角度來看,關於守護進程的兩個間隔 20 秒的 sched 跟蹤條目表示守護進程 連續運行所有 20 秒)。 但是,在高 CPU 使用率情況下,這不會影響實際捕獲的跟蹤。
二、使用
anrd help信息
emulator_x86_64:/ # anrd -h
usage: ANRdaemon [options] [categoris...]
Options includes:
-a appname enable app-level tracing for a comma separated list of cmdlines
-t N cpu threshold for logging to start (uint = 0.01%, min = 5000, max = 9999, default = 9990)
-s N use a trace buffer size of N KB default to 2048KB
-h show helps
Categoris includes:
am - activity manager
sm - sync manager
input - input
dalvik - dalvik VM
audio - Audio
gfx - Graphics
rs - RenderScript
hal - Hardware Modules
irq - kernel irq events
sched - kernel scheduler activity
stack - kernel stack
sync - kernel sync activity
workq - kernel work queues
Control includes:
SIGQUIT: terminate the process
SIGSTOP: suspend all function of the daemon
SIGCONT: resume the normal function
SIGUSR1: dump the current logging in a compressed form
使用見readme就好了,就是運行個監控程序,cpu超過設定值他就抓trace。
:/ # anrd -t 5000 sched gfx am
:/ # ps -e |grep anr
root 5715 1 12346524 2064 __arm64_sys_nanosleep 0 S anrd
:/ # logcat |grep 5715
08-25 18:19:21.768 5715 5715 I anrdaemon: ANRdaemon starting
你覺得抓完了就可以停了,把trace導出來。
設備上的日志:
:/ # logcat |grep anrd
08-25 18:34:13.727 9882 9882 D anrdaemon: High cpu usage, start logging.
08-25 18:34:14.233 9882 9882 D anrdaemon: Usage back to low, stop logging.
08-25 18:38:07.754 9882 9882 I anrdaemon: Started to dump ANRdaemon trace.
08-25 18:38:16.081 9882 9882 I anrdaemon: Finished dump. Output file stored at: /data/misc/anrd/dump_of_anrdaemon.2021-08-25.18:38:07
pc端執行sh導出trace:
qiucheng@haha:~/aosp/system/extras/ANRdaemon$ ./ANRdaemon_get_trace.sh
/data/misc/anrd/dump_of_anrdaemon.2021-08-25.18:38:07: 1 file pulled, 0 skipped. 27.1 MB/s (5279290 bytes in 0.186s)
SUCCEED!
Trace stored at /home/qiucheng/aosp/system/extras/ANRdaemon/dump_of_anrdaemon.2021-08-25.18:38:07
三、源碼
代碼位置:
system/extras/ANRdaemon/
ANRdaemon.cpp
ANRdaemon_get_trace.sh
就倆文件,一個cpp編譯成設備端的二進制可執行文件,另一個是pc上的shell腳本,用來導出抓好的trace
先看anrd可執行程序吧
anrd
system/extras/ANRdaemon/ANRdaemon.cpp
int main(int argc, char* argv[]) {
if (get_options(argc, argv) != 0) return 1;
if (daemon(0, 0) != 0) return 1;
//注釋1:信號監聽,收信號時保存trace文件
register_sighandler();
//注釋2:新的"/d/tracing/trace" fd
/* Clear any the trace log file by overwrite it with a new file */
int fd = creat(dfs_trace_output_path, 0);
if (fd == -1) {
ALOGE("Faield to open and cleaup previous log");
return 1;
}
close(fd);
//注釋3:開啟死循環監聽cpu負載監聽signal,抓trace、保存trace
ALOGI("ANRdaemon starting");
start();
if (err) ALOGE("ANRdaemon stopped due to Error: %s", err_msg);
ALOGI("ANRdaemon terminated.");
return (err ? 1 : 0);
}
----------------------------------------------------------------
static void start(void) {
if ((set_tracing_buffer_size()) != 0) return;
dfs_set_property(tag, apps, true);
dfs_poke_binder();
get_cpu_stat(&old_cpu);
sleep(check_period);
while (!quit && !err) {
if (!suspend && is_heavy_load()) {//注釋4:檢測cpu負載
/*
* Increase process priority to make sure we can stop logging when
* necessary and do not overwrite the buffer
*/
setpriority(PRIO_PROCESS, 0, -20);
start_tracing();//注釋5:抓trace和保存trace
setpriority(PRIO_PROCESS, 0, 0);
}
sleep(check_period);
}
return;
}
--------------------------------------------------------
static void handle_signal(int signo) {//注釋6:攔截信號做不同反應
switch (signo) {
case SIGQUIT:
suspend = true;
quit = true;
break;
case SIGSTOP:
suspend = true;
break;
case SIGCONT:
suspend = false;
break;
case SIGUSR1:
request_dump_trace();
原理就是readme里講的那么簡單,開啟一個死循環,不斷檢查cpu負載,超過設定值就抓trace
下面看看導出trace的shell腳本
ANRdaemon_get_trace.sh
#!/bin/bash
TRACE_DIR=/data/misc/anrd
TRACE_FILE_PATTEN=dump_of_anrdaemon
if [ $# -eq 1 ]; then
DEVICE=$(echo "-s $1")
else
DEVICE=""
fi
#注釋1:檢測anrd在運行
PID=$(adb $DEVICE shell "ps | grep anrd")
if [ $? -ne 0 ]; then
echo "FAILED. ADB failed or Daemon is not running."
exit 1
fi
#注釋2:給anrd發signal,觸發trace的保存
PID=$(echo "$PID" | awk '{ print $2 }')
adb $DEVICE shell "kill -s SIGUSR1 $PID"
#注釋3:找到最后一個生成的trace文件
TRACE_FILE=$(adb $DEVICE shell "ls $TRACE_DIR \
| grep $TRACE_FILE_PATTEN | tail -n1" | tr -d '\r')
#注釋4:查看anrd進程打開的文件
# Wiat the trace file generation to complete
adb $DEVICE shell "lsof -p $PID" | grep $TRACE_FILE > /dev/null
while [ $? -eq 0 ];
do
sleep 1
adb $DEVICE shell "lsof -p $PID" | grep "$TRACE_FILE" > /dev/null
done
if [ -z "$TRACE_FILE" ]; then
echo "FAILED. Trace file not created"
fi
#注釋5:把trace pull到pc
adb $DEVICE pull "${TRACE_DIR}/${TRACE_FILE}" ${TRACE_FILE}
CURRENT_DIR=$(pwd)
echo SUCCEED!
echo Trace stored at ${CURRENT_DIR}/${TRACE_FILE}
簡單的順序流程。到設備里找anrd進程,然后發信號SIGUSR1觸發anrd中的信號攔截,然后保存好trace后把最新的trace pull到本地pc。
四、有bug
anrd和ANRdaemon
最初的可執行程序叫anrdaemon,后面改成了anrd,usage和readme中的寫法不對。這需要修正。
'/data/misc/anrd/': No such file or directory
看了下git log,從來就沒有將trace保存的目錄創建到文件系統上過。所以一開始開發的時候就是手動創建?還需要確認下要不要修改,以及在rc里改還是在anrd里改。
而像/data/misc/wmtrace/
這樣的調試目錄,都是在init.rc里開機階段創建好的
system/core/rootdir/init.rc
mkdir /data/misc/wmtrace 0700 system system
on property:ro.debuggable=1
# Give writes to anyone for the trace folder on debug builds.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
# Give reads to anyone for the window trace folder on debug builds.
chmod 0775 /data/misc/wmtrace
不過手動創建后是可以讓程序順利保存並導出trace的。