1.Bootchart介绍
Bootchart是对GNU / Linux的启动过程的性能分析和可视化工具。在开机过程中会收集资源利用和处理信息,最后这些信息会呈现在一个PNG,SVG或EPS编码图上(官网英文介绍地址:http://www.bootchart.org/).
2.Bootchar调试
Bootchart代码已在Android的源代码中,需要我们修改对应的配置来启用bootchart.
system/core/init/Android.mk中,添加如下信息,打开BOOTCHART功能:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+INIT_BOOTCHART := true
system/core/init/bootchart.h中定义BOOTCHART:
#ifndef BOOTCHART
-#define BOOTCHART 0
+#define BOOTCHART 1
#endif
touch system/core/init/bootchart.c (更新文件)
mmm system/core/init/ (重新编译init模块)
编译提示信息:target Symbolic: init (out/target/product/cht_cr_mrd/symbols/init)表示已经生成init程序,重新打包boot.img,烧录boot.img.
烧录完成后,系统开机,写入bootchart采集时间:
adb shell "echo 120 > /data/bootchart-start"
然后重启系统.(以上操作可以阅读官方文档:system/core/init/README.BOOTCHART)
这时候问题来了,/data/bootchart目录下本该有很多记录数据的文件,但居然没有产生!
log上提示”audit: type=1400 audit(1325552984.022:5): avc: denied"权限问题,开始怀疑selinux问题导致,把main函数里的selinux_initialize()注释掉,即关闭selinux,但问题依旧。.
于是就开始跟踪分析bootchart代码,在init.c里
queue_builtin_action(bootchart_init_action, "bootchart_init");添加action正常.
有调用bootchart_init_action函数,此函数有执行,添加打印后发现此函数里bootchart_count的值居然为0!,代码如下
#if BOOTCHART
static int bootchart_init_action(int nargs, char **args)
{
bootchart_count = bootchart_init();
ERROR("bootchart_init_action:bootchart_count %d\n",bootchart_count);
if (bootchart_count < 0) {
ERROR("bootcharting init failure\n");
} else if (bootchart_count > 0) {
ERROR("bootcharting started (period=%d ms)\n", bootchart_count*BOOTCHART_POLLING_MS);
} else {
ERROR("bootcharting ignored\n");
}
return 0;
}
#endif
于是就继续跟代码到bootchart_init函数里.找到创建/data/bootchart目录的代码:
do {ret=mkdir(LOG_ROOT,0755);}while (ret < 0 && errno == EINTR);
添加打印,这里ret返回值为0,即创建/data/bootchart目录是成功的!,那为什么没有目录呢?冷静下来思考,怀疑是创建后被删除了?但是按照bootchart的设计,这个目录是用来保存数据,需要用户获取的,bootchart本身不会去删除的.所以在/data路径下手动创建此目录,重启后,该目录是存在的,排除了删除的可能.跟踪发现其他文件open返回值也是正常的,当时很奇怪.折腾了一整天,后来查看系统启动过程信息,分析发现在bootchart运行之后/data分区才挂载上来导致的!!!!!即bootchart_init里open/mkdir方式创建的这些目录和文件,全部被之后挂载的分区覆盖了!所以在不修改挂载前提下,就修改bootchart文件路径吧,山不过来,我就过去~找到问题点,修改就容易了,已知/dev目录在bootchart调用之前,是一定会先创建的.所以修改方法,就是把使用的路径从/data修改到/dev即可,修改如下:
--- a/system/core/init/bootchart.c
+++ b/system/core/init/bootchart.c
@@ -36,15 +36,17 @@
#define VERSION "0.8"
#define SAMPLE_PERIOD 0.2
-#define LOG_ROOT "/data/bootchart"
+#define LOG_ROOT "/dev/bootchart"
#define LOG_STAT LOG_ROOT"/proc_stat.log"
#define LOG_PROCS LOG_ROOT"/proc_ps.log"
要注意启动bootchart需要读取LOG_STARTFILE文件的值(默认为/data/bootchart-start)来确定启动采样,目前data分区挂载的问题,无法使用,需要修改.尝试在/dev/下创建bootchart-start节点,并写入值,但是失败了,因为/dev目录下的文件在重启后是重新生成的,重启前创建的文件都不存在了,此方法不行. 于是就是去修改启动bootchart的条件的代码.一种方式是通过修改/proc/cmdline的androidboot.bootchart=值,另外一种就是直接修改修改bootchart_init函数里代码,本文采取后者:
proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) );
s = strstr(cmdline, KERNEL_OPTION);
+//force to 180s
+timeout = 180;
重新编译,烧录.测试图表生成,打包bootchart图表源文件
adb shell
cd /dev/bootchart
exit
adb pull /dev/bootchart/bootchart.tgz ./
3.生成bootchart性能分析图
ubuntu上安装bootchart工具:
sudo apt-get install bootchart
运行bootchart解析所获取到的数据:
enhuiou@enhuiou-OptiPlex-3020:~/bootchart$ bootchart bootchart.tgz
parsing 'bootchart.tgz'
parsing 'header'
parsing 'proc_stat.log'
parsing 'proc_ps.log'
Traceback (most recent call last):
File "/usr/bin/bootchart", line 23, in <module>
sys.exit(main())
File "/usr/lib/pymodules/python2.7/pybootchartgui/main.py", line 111, in main
options.crop_after, options.annotate)
File "/usr/lib/pymodules/python2.7/pybootchartgui/parsing.py", line 248, in parse
state = parse_paths(writer, ParserState(), paths)
File "/usr/lib/pymodules/python2.7/pybootchartgui/parsing.py", line 237, in parse_paths
state = _do_parse(writer, state, name, tf.extractfile(name))
File "/usr/lib/pymodules/python2.7/pybootchartgui/parsing.py", line 201, in _do_parse
state.ps_stats = _parse_proc_ps_log(writer, file)
File "/usr/lib/pymodules/python2.7/pybootchartgui/parsing.py", line 88, in _parse_proc_ps_log
userCpuLoad, sysCpuLoad = process.calc_load(userCpu, sysCpu, time - ltime)
File "/usr/lib/pymodules/python2.7/pybootchartgui/samples.py", line 83, in calc_load
userCpuLoad = float(userCpu - self.last_user_cpu_time) / interval
ZeroDivisionError: float division by zero
MD,报错了...
错误信息看,是存在除0的异常,查看这部分代码,interval存在为0的情况,所以在/usr/lib/pymodules/python2.7/pybootchartgui/samples.py报错的这行,添加如下代码在
if interval == 0:
interval = 1
再次编译,会报同样除0的问题,修改方法与上面一样.
编译后生成png图片
这里有个问题,我们设置的是180s,实际上只采集了90秒左右,采集时间怎么少了90秒?
查看init.c代码里,发现for循环中,在执行的command是非常多的,每次循环的间隔并不是固定的200ms,而每次循环都会执行--bootchart_count操作,即bootchart希望每次执行的间隔是200ms,但init循环任务中处理的多个command,时间都不固定(即代码中"poll(ufds, fd_count, timeout);的timeout不固定"),所以这里的采集总时间就跟设定的所不同了.
图标上标示信息还是挺全的,如果还是不清楚,可以先阅读官网(http://www.bootchart.org/)samples栏的示例分析.
4.总结
在开机优化方面,可以通过以下几点来减少开机时间:
1.bootchart生成数据图表,查看是否存在长期占用资源的情况.
2.删除不需要的APK,减少开机预加载时间.
3.去除init.rc中不需要的服务.
4.清除systemserver.java中不需要的服务.