Edited at

[Android] Bootchartを描く


AndroidでのBootchartの描き方

$ adb shell 'touch /data/bootchart/enabled'

$ adb reboot

これだけ。

簡単すぎて鼻血が出そう。

再起動後、PCからgrab-bootchart.shを実行すると、このとおり。


実装

ちょっとこれだけだとあまりにもなので、実装を確認してみる。

ソースコード及び動作確認はAndroid 8.1.0。

/init.rcにて、post-fs-dataトリガで/dataパーティションにアクセスできるようになるとbootchart startコマンドが実行される。


/system/core/rootdir/init.rc

on post-fs-data

...
# Start bootcharting as soon as possible after the data partition is
# mounted to collect more data.
mkdir /data/bootchart 0755 shell shell
bootchart start

bootchartコマンドは、initプロセスのビルトイン関数の一つとして定義されている。


/system/core/init/builtins.cpp

const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {

constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const Map builtin_functions = {
{"bootchart", {1, 1, do_bootchart}},

do_bootchart_start()が呼びだされ、そこで/data/bootchart/enabledファイルの有無が確認される。

中身によらずファイルが存在する場合は、新しいスレッドにてbootchart_thread_main()が実行される。


/system/core/init/bootchart.cpp

static int do_bootchart_start() {

// We don't care about the content, but we do care that /data/bootchart/enabled actually exists.
std::string start;
if (!android::base::ReadFileToString("/data/bootchart/enabled", &start)) {
LOG(VERBOSE) << "Not bootcharting";
return 0;
}

g_bootcharting_thread = new std::thread(bootchart_thread_main);
return 0;
}


bootchart_thread_main()では、200msに一度procfsの中身を/data/bootchart/に収集する。

収集しているのは、下記の4種。


  • /proc/stat

  • /proc/diskstats

  • /proc/<pid>/cmdline

  • /proc/<pid>/stat


/system/core/init/builtins.cpp

static void bootchart_thread_main() {

LOG(INFO) << "Bootcharting started";

// Open log files.
auto stat_log = fopen_unique("/data/bootchart/proc_stat.log", "we");
if (!stat_log) return;
auto proc_log = fopen_unique("/data/bootchart/proc_ps.log", "we");
if (!proc_log) return;
auto disk_log = fopen_unique("/data/bootchart/proc_diskstats.log", "we");
if (!disk_log) return;

log_header();

while (true) {
{
std::unique_lock<std::mutex> lock(g_bootcharting_finished_mutex);
g_bootcharting_finished_cv.wait_for(lock, 200ms);
if (g_bootcharting_finished) break;
}

log_file(&*stat_log, "/proc/stat");
log_file(&*disk_log, "/proc/diskstats");
log_processes(&*proc_log);
}

LOG(INFO) << "Bootcharting finished";
}


boot_completedbootchartスレッドを停止する。


/system/core/rootdir/init.rc

on property:sys.boot_completed=1

bootchart stop

grab-bootchart.shで、adb pullを使って収集データを転送し、

bootchart (pybootchartgui)コマンドでグラフ化している。

スクリプト実行後、グラフは./bootchart.pngに、生データは/tmp/android-bootchart/に残っているが、

再度スクリプトを走らせると消されるので注意。


/system/core/init/grab-bootchart.sh

#!/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"



おわりに

こういうお膳立てを知ると、「おぉさすがAndroid。。。」と感じる。

まだまだいっぱいあるんだろうなぁ。

ちなみに/data/bootchart/enabledが残ってると、

無駄にデータ収集が走りつづけるので、忘れずに消しましょう。

$ adb shell 'rm /data/bootchart/enabled'


参考

bootchart - Android source