性能工具|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的。