本記事ではChrome内蔵のプロファイリングツール、tracingの活用方法を紹介する。
本記事は私の個人的な意見に基づき書かれております。私の所属する組織、団体には一切の関係はありません。
はじめに
Chromeは誕生以来、常に最速のブラウザを目指して開発されてきた。しかし処理速度を向上させようと頑張ると、うっかりメモリ使用量や電力消費量を犠牲にしてしまう可能性がある。特にAndroidのような携帯端末ではメモリや電池寿命の制約が強く、あらゆるデバイスで最高の使い心地のChromeを目指す上では、これらのトレードオフを実際のデータで深く理解した上で開発の意思決定をする必要がある。
このような背景から、処理速度・メモリ使用・電力消費をはじめ、あらゆるパフォーマンスデータを時系列で統合的に理解出来るように開発されたのがChrome内蔵プロファイリングツール、tracingである。
多機能が故、複雑な仕組みになっているtracingに愚痴をこぼしてしまう私だが、日頃の感謝を込めてtracingの様々な活用方法をサラッと紹介していこうかと思う。私はヘビーユーザーであって、開発チームでは無いので間違いも多々あるかも知れません。ご指摘ください。
tracingの構成
先ほどtracingを一つのモノであるかのように紹介したが、実際には「traceファイル」という共通データフォーマットを扱う様々なツールの集合体である。
下図に示すとおりTracingを構成するツールは大きく二つに分けられる。traceファイルを生み出すProducerと読み込み・加工をするConsumerである。
なお、Chromeチームで主に開発されているtracingだが、一部のツールはAndroidやGoのような他のプロジェクトでも採用されているため、ほとんどのソースコードはChromiumとは別のCatapultというリポジトリに収められている。
traceファイルの中身を理解する
traceファイルは基本的にはtrace eventを記録したファイルの事である。trace eventはここに定義されている。
trace eventの集合のコンテナフォーマットとしては以下のものがある。
- JSON Objectフォーマット(デフォルト)
- JSON Arrayフォーマット
- Line separated JSONフォーマット(非公式)
更に任意でgzip・zipで圧縮してもそのまま読み込むことができる。
一部のツールは、trace viewerにtraceファイルを埋め込んだ「htmlフォーマット」を出力するものがある。これは他のconsumerツールでそのまま読み込むことはできないが、Catapultリポジトリにあるhtml2traceというツールでtraceファイルに変換することができる。
traceファイルはただのJSONファイルなのでお手製パーサライブラリを書きたくなるが、かなり複雑で処理が多いので無理をしてでもCatapultリポジトリのtracing/modelを使うことをおすすめする。特にmemory dumpなんかはライブラリに知識が集積されており自分で書くのは不可能に近い。traceファイルを生成する、producerツールを作るときは、必要に応じて機能を追加していけばいいので、お手製ライブラリの使用は問題ない。
tracing/modelのモデルの基礎知識もここにまとめておく。
Slice
とは、始まりと終わりのある、時間軸上の期間を表すものであり、関数の実行期間を表示するためによく使われる。時間的に内包されるSlice
はSlice.subSlices
に入っており、関数呼び出しの様子を階層構造で表している。Slice
の集合はSliceGroup
というオブジェクトで管理している。trace-viewerで同じ行(track)に表示されるSlice
の配列をSliceGroup
のsubrowと呼ぶ。
Model
オブジェクトの中にはプロセスごとにProcess
オブジェクトがあり、Process
オブジェクトの中にはスレッドごとにThread
オブジェクトがある。Thread
オブジェクトは一つのSliceGroup
を持つ(async slice用にはAsyncSliceGroup
を別に持つ)。
trace-viewerの操作
tracingの主役と言っても過言ではないのが可視化ツールtrace-viewerである。chrome://tracingで表示されるのもtrace-viewerである。
絶対覚えるべきキーボードショットカットを以下にまとめた。マウスはSelect Mode固定で困る事はないだろう。
キー | 動作 |
---|---|
w/s/a/d | 移動・ズーム |
f | 選択中のsliceにズーム |
m | sliceをマーク(別のtrackにいる複数のsliceの前後関係を調べたい時に使う) |
memory-infraで見るメモリ使用量の内訳
メモリ使用量の内訳を詳細に把握するために開発されたのがmemory-infraである。MemoryDumpProvider
というインタフェースを通じてChromeの各コンポーネントにメモリ使用量を定期的に報告させるわけであるが、この際に重複して報告されるメモリ使用量を正しく調整するべくノウハウが駆使されている。しかし細かな齟齬は残る可能性があるのでsuballocationのデータを読むときは要注意。
memory-infraの収集したmemory dumpはtrace-viewerではMのアイコンまたはで表示される。紫のアイコンのほうがheavy memory dumpで、より詳細なデータが含まれる。
memory dumpの間隔は以下のようにtrace configから変更可能である。またRemote debugging protocolを使えばChromeの外部からタイミングを指示することもできる。
{
// 省略
"memory_dump_config": {
"triggers": [
{
"mode": "detailed",
"periodic_interval_ms": 2000
}
]
}
}
Flowで見るinput latency
trace-viewer右上のView Optionsメニューに隠されているFlow eventsが便利なので紹介する。IPCやポストしたタスクのSlice間に矢印を表示することができる。
以下はTouchStartイベントのinput latencyを見た場合の例である。
- BrowserプロセスがOSからUIイベントを受け取る。
- RendererプロセスのCompositorスレッドがイベントを受け取り、Compositorスレッド上で処理できるイベントか判定する。今回はJSイベントハンドラがセットされているのでMainスレッドに送る。
- RendererプロセスのMainスレッドでJSイベントハンドラが実行される。
- BeginFrameで表示内容が決まりLayerTreeがCompositorへCommitされる。
- CompositorはSkiaでLayerを描画する。
- Browserプロセス経由でGPUプロセスに表示の指示。
- GPUがディスプレイに表示を完了の報告。
この例では入力から表示まで16msで完了していて問題ないが、jankが生じている場合は、どのタスクにブロックされているのか一目瞭然になる。
Chrome on Androidでtraceをとる
普通にchrome://tracing
でとりたい場合は、USBで端末と接続した上でchrome://inspect?tracing
から簡単にtraceをとることができる。
万一お手製ツールでRemote Debugging Protocol経由でtraceをとりたいような場合は、下記のコマンドを実行する。すると、それ以降はデスクトップのChromeでremote debugging portに接続するのと同じ要領でhttp://localhost:9222
に接続すれことができる。--remote-debugging-portを指定する必要はない。
$ adb forward tcp:9222 localabstract:chrome_devtools_remote `
まとめ
tracingはとにかく多機能で複雑なツール群であるが、理解をすると様々な応用方法が開けてくる。例えば最近私のチームではtracingを利用したメモリリーク検出を実装した。皆様も是非tracingをご活用ください。