[Android] Bootchartを描く

# AndroidでのBootchartの描き方

```shell-session
$ adb shell 'touch /data/bootchart/enabled'
$ adb reboot
```

これだけ。
簡単すぎて鼻血が出そう。
再起動後、PCから[grab-bootchart.sh](https://android.googlesource.com/platform/system/core/+/refs/tags/android-8.1.0_r68/init/grab-bootchart.sh)を実行すると、このとおり。

![bootchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/2419a609-68b0-ce58-1686-d765980b2ca4.png)


# 実装

ちょっとこれだけだとあまりにもなので、実装を確認してみる。
ソースコード及び動作確認はAndroid 8.1.0。


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

```conf:/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プロセスのビルトイン関数の一つとして定義されている。

```c++:/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()`が実行される。

```c++:/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/`に収集する。
収集しているのは、下記の４種。

- `/proc/stat`
- `/proc/diskstats`
- `/proc/<pid>/cmdline`
- `/proc/<pid>/stat`

```c++:/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_completed`で`bootchart`スレッドを停止する。

```conf:/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/`に残っているが、
再度スクリプトを走らせると消されるので注意。

```shell:/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`が残ってると、
無駄にデータ収集が走りつづけるので、忘れずに消しましょう。

```shell-session
$ adb shell 'rm /data/bootchart/enabled'
```


# 参考
[bootchart - Android source](https://source.android.com/devices/tech/perf/boot-times#bootchart)