パフォーマンス計測に困らない！tracing活用術100

本記事ではChrome内蔵のプロファイリングツール、tracingの活用方法を紹介する。

本記事は私の個人的な意見に基づき書かれております。私の所属する組織、団体には一切の関係はありません。

# はじめに

Chromeは誕生以来、常に最速のブラウザを目指して開発されてきた。しかし処理速度を向上させようと頑張ると、うっかりメモリ使用量や電力消費量を犠牲にしてしまう可能性がある。特にAndroidのような携帯端末ではメモリや電池寿命の制約が強く、あらゆるデバイスで最高の使い心地のChromeを目指す上では、これらのトレードオフを実際のデータで深く理解した上で開発の意思決定をする必要がある。

このような背景から、処理速度・メモリ使用・電力消費をはじめ、あらゆるパフォーマンスデータを時系列で統合的に理解出来るように開発されたのがChrome内蔵プロファイリングツール、tracingである。

多機能が故、複雑な仕組みになっているtracingに愚痴をこぼしてしまう私だが、日頃の感謝を込めてtracingの様々な活用方法をサラッと紹介していこうかと思う。私はヘビーユーザーであって、開発チームでは無いので間違いも多々あるかも知れません。ご指摘ください。

# tracingの構成

先ほどtracingを一つのモノであるかのように紹介したが、実際には[「traceファイル」という共通データフォーマットを扱う様々なツールの集合体](https://docs.google.com/document/d/1QADiFe0ss7Ydq-LUNOPpIf6z4KXGuWs_ygxiJxoMZKo/edit#)である。

下図に示すとおりTracingを構成するツールは大きく二つに分けられる。traceファイルを生み出す**Producer**と読み込み・加工をする**Consumer**である。

![Tracing Ecosystem概容](https://qiita-image-store.s3.amazonaws.com/0/103140/78ac5f3a-9ec9-6d46-9f04-347d259311ed.png)

なお、Chromeチームで主に開発されているtracingだが、一部のツールは[Android](https://developer.android.com/studio/command-line/systrace.html)や[Go](https://golang.org/cmd/trace/)のような他のプロジェクトでも採用されているため、ほとんどのソースコードはChromiumとは別の[Catapultというリポジトリ](https://github.com/catapult-project/catapult)に収められている。

# traceファイルの中身を理解する

traceファイルは基本的にはtrace eventを記録したファイルの事である。trace eventは[ここ](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)に定義されている。

trace eventの集合のコンテナフォーマットとしては以下のものがある。
- JSON Objectフォーマット（デフォルト）
- JSON Arrayフォーマット
- Line separated JSONフォーマット（非公式）

更に任意でgzip・zipで圧縮してもそのまま読み込むことができる。

一部のツールは、trace viewerにtraceファイルを埋め込んだ「htmlフォーマット」を出力するものがある。これは他のconsumerツールでそのまま読み込むことはできないが、Catapultリポジトリにある[html2trace](https://github.com/catapult-project/catapult/blob/master/tracing/bin/html2trace)というツールでtraceファイルに変換することができる。

traceファイルはただのJSONファイルなのでお手製パーサライブラリを書きたくなるが、かなり複雑で処理が多いので無理をしてでも[Catapultリポジトリのtracing/model](https://github.com/catapult-project/catapult/tree/master/tracing/tracing/model)を使うことをおすすめする。特にmemory dumpなんかはライブラリに知識が集積されており自分で書くのは不可能に近い。traceファイルを生成する、producerツールを作るときは、必要に応じて機能を追加していけばいいので、お手製ライブラリの使用は問題ない。

[tracing/model](https://github.com/catapult-project/catapult/tree/master/tracing/tracing/model)のモデルの基礎知識もここにまとめておく。

![trace modelの基本知識](https://qiita-image-store.s3.amazonaws.com/0/103140/6abac821-a791-a6db-c32d-7b795f583c37.png)

`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](https://github.com/catapult-project/catapult/tree/master/tracing)である。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のアイコン](https://storage.googleapis.com/chromium-docs.appspot.com/d7bdf4d16204c293688be2e5a0bcb2bf463dbbc3)または![light memory dumpのアイコン](https://storage.googleapis.com/chromium-docs.appspot.com/b60f342e38ff3a3767bbe4c8640d96a2d8bc864b)で表示される。紫のアイコン![heavy memory dumpのアイコン](https://storage.googleapis.com/chromium-docs.appspot.com/d7bdf4d16204c293688be2e5a0bcb2bf463dbbc3)のほうがheavy memory dumpで、より詳細なデータが含まれる。

memory dumpの間隔は以下のようにtrace configから変更可能である。また[Remote debugging protocol](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#method-requestMemoryDump)を使えばChromeの外部からタイミングを指示することもできる。

```js
{
  // 省略
  "memory_dump_config": {
    "triggers": [
      {
        "mode": "detailed",
        "periodic_interval_ms": 2000
      }
    ]
  }
}
```

# Flowで見るinput latency

trace-viewer右上のView Optionsメニューに隠されているFlow eventsが便利なので紹介する。IPCやポストしたタスクのSlice間に矢印を表示することができる。

以下はTouchStartイベントのinput latencyを見た場合の例である。

![TouchStartイベントのフロー](https://qiita-image-store.s3.amazonaws.com/0/103140/dc007687-966b-94f0-62b2-b7cb9570c389.png)

1. BrowserプロセスがOSからUIイベントを受け取る。
2. RendererプロセスのCompositorスレッドがイベントを受け取り、Compositorスレッド上で処理できるイベントか判定する。今回はJSイベントハンドラがセットされているのでMainスレッドに送る。
3. RendererプロセスのMainスレッドでJSイベントハンドラが実行される。
4. BeginFrameで表示内容が決まりLayerTreeがCompositorへCommitされる。
5. CompositorはSkiaでLayerを描画する。
6. Browserプロセス経由でGPUプロセスに表示の指示。
7. 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をご活用ください。