とりあえず使う Perfetto

PerfettoSystrace の後継となる Android のトレーシングシステムだ。Android P から導入され, Android 10 からだいたいまともに使える。Systrace にはない様々な便利機能があるため森田はここ一年で Systrace から Perfetto に乗り換えた。今後の記事でなにかと Perfetto を使う予定なので、この記事で最低限の使い方を紹介しておきたい。

なおこの記事は Systrace を使っている人が乗り換えるための参考に書いた。トレーシング自体の有用性については、今後の記事で徐々に議論していきたい。

ADB からトレースをとる

Systrace はデバイスにある atrace コマンドを Python でラップしたものだった。Perfetto ではデバイスの perfetto コマンドを直接使う。

$ adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 129024
    fill_policy: RING_BUFFER
}
buffers: {
    size_kb: 2048
    fill_policy: RING_BUFFER
}
data_sources: {
    config {
        name: "linux.process_stats"
        target_buffer: 1
        process_stats_config {
            scan_all_processes_on_start: true
        }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "sched/sched_switch"
            ftrace_events: "power/suspend_resume"
            ftrace_events: "sched/sched_wakeup"
            ftrace_events: "sched/sched_wakeup_new"
            ftrace_events: "sched/sched_waking"
            ftrace_events: "power/cpu_frequency"
            ftrace_events: "power/cpu_idle"
#            ftrace_events: "raw_syscalls/sys_enter"
#            ftrace_events: "raw_syscalls/sys_exit"
            ftrace_events: "sched/sched_process_exit"
            ftrace_events: "sched/sched_process_free"
            ftrace_events: "task/task_newtask"
            ftrace_events: "task/task_rename"
            ftrace_events: "lowmemorykiller/lowmemory_kill"
            ftrace_events: "oom/oom_score_adj_update"
            ftrace_events: "ftrace/print"
            atrace_categories: "gfx"
            atrace_categories: "input"
            atrace_categories: "view"
            atrace_categories: "wm"
            atrace_categories: "am"
            atrace_categories: "camera"
            atrace_categories: "hal"
            atrace_categories: "res"
            atrace_categories: "dalvik"
            atrace_categories: "bionic"
            atrace_categories: "pm"
            atrace_categories: "ss"
            atrace_categories: "aidl"
            atrace_categories: "nnapi"
            atrace_categories: "binder_driver"
            atrace_apps: "*"
        }
    }
}
duration_ms: 10000
flush_period_ms: 10000

EOF

この長大なコマンドは Perfetto UI の “Record New Trace” 画面で生成、コピペできる。覚える必要はない。実際にはテストスクリプトの一部にすることが多く、あとで紹介するオンデバイストレーシングには UI もあるため、ターミナルから入力することも滅多にない。

コマンドを入力したらトレースしたいアプリを操作し、コマンドの実行がおわったあとトレースファイルをとりだす。

$ adb pull /data/misc/perfetto-traces/trace ~/Downloads/trace.pftrace

このファイルは Perfetto UI で表示できる。Systrace よりロードも描画も速く、扱えるデータサイズも大きい。

この UI はキーボード・ショートカットを駆使して使う前提でデザインされている。”?” キーでショートカット一覧を表示できる。

実例

ADB が手元にないひとのために、様々なアプリの起動を一通りトレースした。全て Perfetto UI にロードして表示できる(はず。全部は確認してない): Twitter, Facebook, Airbnb, Discord, Google, Instagram, FB Messenger, Pinterest, Reddit, Spotify, Yelp, TikTok, Flipboard, Medium, NYTimes, Twitch

それぞれアプリ起動から 5 秒間のトレースを含む。スクロールできたものはスクロールしている。 たまにうっかりリンクをタップして画面遷移してしまったものもある。

非同期モード

上のコマンドは所定の時間が経つまで返ってこないので、自動テストなどのスクリプトから使うときは非同期モードを使うとよい。

非同期開始: コマンドに -d オプションが増え、また duration_ms を消した。

$ diff -u scripts/perfetto.sh scripts/perfetto_async.sh 
--- scripts/perfetto.sh 2020-09-24 20:42:14.531563727 -0700
+++ scripts/perfetto_async.sh   2020-09-24 20:42:50.799536914 -0700
@@ -1,6 +1,6 @@
-#!/bin/sh
-adb shell perfetto \
+$ adb shell perfetto \
   -c - --txt \
+  -d \
   -o /data/misc/perfetto-traces/trace \
 <<EOF
 
@@ -60,7 +60,6 @@
         }
     }
 }
-duration_ms: 10000
 flush_period_ms: 10000
 
 EOF

非同期終了は単にプロセスを止めるだけ:

$ adb shell pkill perfetto
$ sleep 3 # Wait for a file to be dumped...
$ adb pull /data/misc/perfetto-traces/trace ~/Downloads/trace.pftrace

そのほか ADB から perfetto コマンドを使う際の Tips:

  • 自動化しないにせよ、コマンドをターミナルにコピペすると視覚的に邪魔なのでシェルスクリプトなどに保存しておくとよい。
  • スクリプト化ついでにファイルを gzip しておくとよく縮む。Perfetto UI は gzip されたトレースをそのまま解釈できる。
  • コマンドを生成する際にはトレース項目を多めに選んでおき、あとから不要なものをコメントアウトするとよい。 なおこのテキストフォーマットには textproto と呼ばれる Protobuf データのテキスト表現が使われている。
  • アプリ名はアスタリスクにしておくと全てのアプリのトレースが取れる(上記サンプルの “atrace_apps” を参照。)
  • Perfetto コマンドはアプリのプロセス起動前に開始する。アプリはトレースの有無をアプリ起動時(初回の Trace API 呼び出し時)に確定するため、事後的に perfetto を開始すると アプリからの記録が残らない。設定を ring buffer モードにしておけばバッファ不足の心配はない。

オンデバイストレーシング

Android P 以降、電話機の UI からトレースを開始/終了できるようになった。Android P では Systrace (atrace) 互換のフォーマットが使われていたが、Android 10 からバックエンドが Perfetto に切り替わり、データフォーマットも Perfetto 形式に変更された。

オンデバイストレーシングの使い方は以下の文書に詳しく説明されている:

Capture a system trace on a device  |  Android Developers

採取したトレースファイルは Google Drive などからクラウドにアップロードしてもいいし、ADB 経由で取り出しても良い。採取したトレースファイルは明示的に消さない限りデバイスの中に残っている。サイズが大きいため、ときどき UI からクリアするといい。

森田は最新のトレースを取り出すのに以下のような one-liner を使いまわしている。(もっと良い書き方があったら教えてください。)

$ adb pull /data/local/traces/`adb ls /data/local/traces | tail -n 1 | awk '{ print $4 }'`

つけっぱなしトレーシングと Dogfooding

オンデバイストレーシングはリングバッファを使うので、電話機の起動中ずっとつけっぱなしできる。オーバーヘッドは小さく、まず体感できない。

トレースをつけっぱなしにしておくと、ふだん電話機を使っていて遅さに気づいた瞬間すぐにトレースを取れる。だから long tail latency すなわち「たまに起きるナゾの遅さ」を調べるのに不可欠。

この機能が導入されて以来、森田のいるチームでは dogfooding (社内実用テスト) のガイドでオンデバイストレーシングをつけっぱなしにするよう勧めている。「遅い」というバグが報告されたときはそのガイドを渡し、トレースの提供を求める。

オンデバイストレーシングの登場以前、社内ベータ版ユーザが報告する性能問題への対処は bugreport ファイル の限られた情報だけが頼りで、できることは少なかった。オンデバイストレーシングによってその状況は一変し、実際に何が遅いのかを特定できるようになった。何が遅いのかわかったところで修正できるとは限らないが、それでもなにもわからないよりはずっとマシ。「遅いバグ」に対処するストレスが減り、森田の QoL は大きく改善した。

なお bugreport ファイルにはトレースと違う情報もたくさんあるので、両方あるのが一番いい。

関連リンク

Ack

草稿に目を通して感想をくれた皆様ありがとう: Kenichi Ishibashi, Jun Mukai, karino2, Kensuke Nagae, Hiroshi Kurokawa