2019-02-28 10:44:47 +08:00
|
|
|
|
# Android 8 上使用 BootChart
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
2020-12-28 16:09:35 +08:00
|
|
|
|
## 1.基本原理
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
bootchart 可以在 Android 系统启动到 init 阶段时开始收集系统信息,并保存成 Log 文件。可在开发机上使用 bootchart 命令对这些 Log 文件进行分析,生成图表。
|
|
|
|
|
|
|
|
|
|
Android 8 上默认编译 bootchart 为 init 内置工具。使用方式可参考:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidSrc}/system/core/init/README.md
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
中与 bootchart 有关的内容。也可以通过分析以下文件了解 bootchart 的启动流程和触发条件。
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidSrc}/system/core/init/Android.mk
|
|
|
|
|
${AndroidSrc}/system/core/init/builtins.cpp
|
|
|
|
|
${AndroidSrc}/system/core/init/bootchart.cpp
|
|
|
|
|
${AndroidOS}/init.rc
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
通过上述文件分析可知,bootchart 会在
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidOS}/data/bootchart/enabled
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
文件存在的情况下被使能。系统启动时,触发其执行的条件可以分析 init.rc 文件,主要触发流程为:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```ini
|
|
|
|
|
on property:sys.boot_from_charger_mode=1
|
|
|
|
|
trigger late-init
|
|
|
|
|
|
|
|
|
|
on late-init
|
|
|
|
|
trigger post-fs-data
|
|
|
|
|
|
|
|
|
|
on post-fs-data
|
|
|
|
|
bootchart start
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
结束 bootchart 执行的条件为:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```ini
|
|
|
|
|
on property:sys.boot_completed=1
|
|
|
|
|
bootchart stop
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
2020-12-28 16:09:35 +08:00
|
|
|
|
## 2.使用方法
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
在开发机上先安装 bootchart 和 pybootchartgui 用于处理和分析开机数据:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
sudo apt-get install bootchart
|
|
|
|
|
sudo apt-get install pybootchartgui
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
接着在目标机上使能 bootchart:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
touch /data/bootchart/enabled
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
之后重启目标机系统,bootchart 会在启动时自动开始和结束收集目标机系统的启动数据信息。系统启动后在 /data/bootchart/ 路径下会多出几个 Log 文件。使用 adb 命令将这些 Log 文件传输到开发机:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
adb pull /data/bootchart/
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
接下来,在开发机上使用以下脚本文件对 Log 文件进行分析并生成 PNG 文件:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
2019-02-28 10:40:34 +08:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
|
|
FILES="header proc_stat.log proc_ps.log proc_diskstats.log"
|
|
|
|
|
TMPTAR="bootchart.tgz"
|
|
|
|
|
|
|
|
|
|
tar -cvzf $TMPTAR $FILES
|
|
|
|
|
bootchart --format=svg -o bootchart.svg $TMPTAR
|
|
|
|
|
rm -rf $TMPTAR
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
或者可以使用源码中官方脚本一次完成 Log 文件的下载和分析工作:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidSrc}/system/core/init/grab-bootchart.sh
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
其内容如下:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
2019-02-28 10:40:34 +08:00
|
|
|
|
#!/bin/sh
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
# This script is used to retrieve a bootchart log generated by init.
|
|
|
|
|
|
|
|
|
|
# All options are passed to adb, for better or for worse.
|
|
|
|
|
|
|
|
|
|
# See the readme in this directory for more on bootcharting.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TMPDIR=/tmp/android-bootchart
|
|
|
|
|
|
|
|
|
|
rm -rf $TMPDIR
|
|
|
|
|
|
|
|
|
|
mkdir -p $TMPDIR
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LOGROOT=/data/bootchart
|
|
|
|
|
|
|
|
|
|
TARBALL=bootchart.tgz
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FILES="header proc_stat.log proc_ps.log proc_diskstats.log"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for f in $FILES; do
|
|
|
|
|
|
|
|
|
|
adb "${@}" pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null
|
|
|
|
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
(cd $TMPDIR && tar -czf $TARBALL $FILES)
|
|
|
|
|
|
|
|
|
|
bootchart ${TMPDIR}/${TARBALL}
|
|
|
|
|
|
|
|
|
|
gnome-open ${TARBALL%.tgz}.png
|
|
|
|
|
|
|
|
|
|
echo "Clean up ${TMPDIR}/ and ./${TARBALL%.tgz}.png when done"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2020-12-28 16:09:35 +08:00
|
|
|
|
## 3.性能对比
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
Google 还给我们提供了一个比较脚本,用来比较两次开机的数据:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidSrc}/system/core/init/compare-bootcharts.py
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
首先将两次得到的 Log 文件打包成 tgz 包并保存到不同的目录中如 cmp1_dir 和 cmp2_dir:
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
FILES="header proc_stat.log proc_ps.log proc_diskstats.log"
|
|
|
|
|
TMPTAR="bootchart.tgz"
|
|
|
|
|
cd cmp1_dir
|
|
|
|
|
tar -cvzf $TMPTAR $FILES
|
|
|
|
|
cd ../cmp2_dir
|
|
|
|
|
tar -cvzf $TMPTAR $FILES
|
|
|
|
|
cd ..
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
接着使用如下命令处理两次开机数据
|
|
|
|
|
|
2020-05-20 16:18:39 +08:00
|
|
|
|
```bash
|
|
|
|
|
${AndroidSrc}/compare-bootcharts.py cmp1_dir cmp2_dir
|
|
|
|
|
```
|
2019-02-28 10:40:34 +08:00
|
|
|
|
|
|
|
|
|
也可以将以上命令按自己的实际需求写成脚本文件来使用。
|
2020-12-28 16:09:35 +08:00
|
|
|
|
|
|
|
|
|
## 4.常见错误处理
|
|
|
|
|
|
|
|
|
|
### 4.1.ZeroDivisionError / substring not found
|
|
|
|
|
|
|
|
|
|
执行脚本时,如提示如下错误:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
File "/usr/lib/pymodules/python2.6/pybootchartgui/draw.py", line
|
|
|
|
|
201, in draw_chart
|
|
|
|
|
yscale = float(chart_bounds[3]) / max(y for (x,y) in data)
|
|
|
|
|
ZeroDivisionError: float division
|
|
|
|
|
# 或
|
|
|
|
|
state = get_proc_state( sample.state )
|
|
|
|
|
File "/usr/lib/pymodules/python2.6/pybootchartgui/draw.py", line 105, in get_proc_state
|
|
|
|
|
return "RSDTZXW".index(flag) + 1
|
|
|
|
|
ValueError: substring not found
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
需修改 /usr/share/pyshared/pybootchartgui/ 或 /usr/lib/python2.7/dist-packages/pybootchartgui 目录的 draw.py, parsing.py, samples.py 三个文件。
|
|
|
|
|
|
|
|
|
|
```diff
|
2020-12-28 16:36:41 +08:00
|
|
|
|
diff a/draw.py b/draw.py
|
|
|
|
|
--- a/draw.py
|
|
|
|
|
+++ b/draw.py
|
|
|
|
|
@@ 195 @@ def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree):
|
2020-12-28 16:09:35 +08:00
|
|
|
|
def transform_point_coords(point, x_base, y_base, xscale, yscale, x_trans, y_trans):
|
|
|
|
|
x = (point[0] - x_base) * xscale + x_trans
|
|
|
|
|
y = (point[1] - y_base) * -yscale + y_trans + bar_h
|
|
|
|
|
return x, y
|
|
|
|
|
|
|
|
|
|
- xscale = float(chart_bounds[2]) / max(x for (x,y) in data)
|
|
|
|
|
- yscale = float(chart_bounds[3]) / max(y for (x,y) in data)
|
|
|
|
|
+ xscale = float(chart_bounds[2]) / max(0.00001, max(x for (x,y) in data))
|
|
|
|
|
+ yscale = float(chart_bounds[3]) / max(0.00001, max(y for (x,y) in data))
|
|
|
|
|
|
|
|
|
|
first = transform_point_coords(data[0], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1])
|
|
|
|
|
last = transform_point_coords(data[-1], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1])
|
|
|
|
|
|
2020-12-28 16:36:41 +08:00
|
|
|
|
@@ 105 @@ def get_proc_state(flag):
|
2020-12-28 16:09:35 +08:00
|
|
|
|
- return "RSDTZXW".index(flag) + 1
|
|
|
|
|
+ return "RSDTZXW".find(flag) + 1
|
|
|
|
|
|
|
|
|
|
def draw_text(ctx, text, color, x, y):
|
|
|
|
|
|
2020-12-28 16:36:41 +08:00
|
|
|
|
diff a/parsing.py b/parsing.py
|
|
|
|
|
--- a/parsing.py
|
|
|
|
|
+++ b/parsing.py
|
|
|
|
|
@@ 166 @@ def _parse_proc_disk_stat_log(file, numCpu):
|
2020-12-28 16:09:35 +08:00
|
|
|
|
for sample1, sample2 in zip(disk_stat_samples[:-1], disk_stat_samples[1:]):
|
|
|
|
|
interval = sample1.time - sample2.time
|
|
|
|
|
sums = [ a - b for a, b in zip(sample1.diskdata, sample2.diskdata) ]
|
|
|
|
|
+ if interval == 0:
|
|
|
|
|
+ interval = 1
|
|
|
|
|
readTput = sums[0] / 2.0 * 100.0 / interval
|
|
|
|
|
writeTput = sums[1] / 2.0 * 100.0 / interval
|
|
|
|
|
|
2020-12-28 16:36:41 +08:00
|
|
|
|
diff a/samples.py b/samples.py
|
|
|
|
|
--- a/samples.py
|
|
|
|
|
+++ b/samples.py
|
|
|
|
|
@@ 82 @@ def calc_load(self, userCpu, sysCpu, interval):
|
2020-12-28 16:09:35 +08:00
|
|
|
|
+ if interval == 0:
|
|
|
|
|
+ interval = 1
|
|
|
|
|
userCpuLoad = float(userCpu - self.last_user_cpu_time) / interval
|
|
|
|
|
sysCpuLoad = float(sysCpu - self.last_sys_cpu_time) / interval
|
|
|
|
|
```
|