perfコマンドからflamegraphを直接生成する

気が付いたらperfコマンドから直接flamegraphが生成できるようになっていたので利用方法のメモです。

# flamegraphとは
["Systems Performance"](https://www.oreilly.com/library/view/systems-performance-2nd/9780136821694/)の本などでも有名なGreggさんの考案したサンプリングしたスタックトレースの情報を可視化するの手法です。視覚的にサンプリングデータの中でどのスタックトレースの実行箇所が多かったかなどを把握することができます。一次情報としては[webページ](https://www.brendangregg.com/flamegraphs.html)や[github](https://github.com/brendangregg/FlameGraph)を参照してください。

とりあえず図を見た方が理解が早いので早速作成してみます。

# perfコマンドから直接生成する方法
ここでは```perf```コマンドを利用して実行中のプロセスに対してスタックトレースのサンプリングを行います。冒頭で触れた通り2020年にperf-toolsに取り込まれた[コミット](https://github.com/torvalds/linux/commit/5287f926920688e1151741d49da37a533ccf1960)によりperfコマンドから直接flamegraphが生成できるようになっています。

(因みにflamegraphは例えばbpf-tools一部のコマンド(e.g. profile)の結果からもgithubのスクリプトを用いて生成できますがここでは扱いません)

## 準備
perfから生成するflamegraphは[d3版](https://github.com/spiermar/d3-flame-graph)のhtmlテンプレートを利用しています。

fedoraの場合は以下のパッケージをインストールします。
```
$ sudo dnf install perf js-d3-flame-graph
```
また名前解決をするために適宜debuginfoも入れておきます。
```sh
$ sudo dnf debuginfo-install <対象のパッケージ>
```

## 生成
call graph(= スタックトレース)を取るオプションを有効にして```perf record```でサンプリングを行ったのち、```perf script```でflamegraphのスクリプト呼び出すだけです。
```sh
$ sudo perf record -a --call-graph dwarf -F 99 -- sleep 5
$ sudo perf script report flamegraph
dumping data to flamegraph.html
```
```perf record```のオプションの説明:
 - -a ... 全CPUを対象にする
     - 今回は5秒間のシステム全体のサンプリングをしたいので追加
     - (これを指定しないとこの場合は sleepコマンドのプロセスだけの結果になります)
 - --call-graph \<type\> ... call graphのサンプリングを実行
     - \<type\>は```fp```, ```dwarf```, ```lbr```が現在指定できます。
         - ```fp``` ... デフォルト。ただしframe pointerを利用するため```-fomit-framepointer```オプションなどでコンパイルしているアプリケーション(恐らく通常はそう)は正しくスタックトレースが取得できない。
         - ```dwarf``` ... dwarfのデバッグ情報を利用。
         - ```lbr``` ... 最近のIntel CPU(AMD Zen4も恐らく対応)にあるLast Branch Recordの機能を利用。CPUが対応している場合は恐らくこちらのオプションが一番良い
     - -gも使用できますがその場合は"--call-graph fp"を指定する挙動になります
 - -F 99 ... サンプリング周波数を指定
     - 今回は1秒に99回サンプリング
     - 毎回同じ実行箇所でサンプリングしてしまう可能性を下げるために奇数(49, 99 etc.)を指定する事が多いようです

詳しくは ```man perf record``` などを見てください。
コマンドの実行により"flamegraph.html"が作成されるのでこれをブラウザで開くと以下のようなページが得られます。

![flamegraph-html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241792/c804f0de-5fc6-86c5-348f-6c966a839335.png)

次のような見方です:
- 色が緑の部分がユーザーコードの実行箇所、青の部分がカーネルコードの実行箇所
- 縦軸がスタックの深さです
- 横軸の長さはサンプリング全体に占めるそのフレームが動いていた比率です。つまり横軸が長い部分のスタックの箇所が実行時間がかかっていた処理と考えられます (もちろん適切にサンプリングされている前提の上で)

また上の検索バーで文字列で検索したり、クリックするとそのフレームをズームしたりすることが出来ます。

今回は特に何もしていない間にサンプリングを行いましたのでperfコマンド自身やswapperスレッドの処理の実行が多いことが分かります。またシステム全体のプロセスを対象にサンプリングしたのでやけに細かくなっていますが、実際は対象プロセスを絞る事が多いと思うのでもう少し見やすくなると思います。

なお以下のようにすると1コマンドでサンプリングからflamegraphの生成まで行うこともできます。
```sh
sudo perf script flamegraph -a -F 99 --call-graph dwarf -- sleep 5
```
ただしこの場合はサンプリングした結果のファイル(```perf.data```)が保存されません。```perf.data```に対して他のscriptの実行等もできますので基本的にはサンプリングとflamegraphの生成は分けて行った方が良いでしょう。

# svg版と比較
flamegraphのgithubにあるスクリプトではperfの実行結果からsvgファイルとしてflamegraphを生成できます。

同じ```perf.data```を使用して生成される図を比較してみます。

## 準備
[github](https://github.com/brendangregg/FlameGraph)のスクリプトにパスをしておきます。

あるいはFedoraではdnfでもインストールできます。
```sh
$ sudo dnf install flamegraph flamegraph-stackcollapse-perf 
```

## 生成

手順ですが```perf record```でサンプリングするところは同じです。
サンプリングの結果を保持する```perf.data```ファイルが作成されるので、これに対して以下のコマンドで出力の変形(stackcollapse-perf.pl)および図の生成(flamegraph.pl)実行します。

```sh
$ sudo perf script > out.perf
$ sudo stackcollapse-perf.pl out.perf > out.folded
$ sudo flamegraph.pl out.folded > out.svg
```

生成されたsvgファイルをブラウザで開くと以下のようになります。

![flamegraph-svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241792/e4200ea6-d8df-f448-8cb9-9e01585b1d4b.png)

なおsvgファイルの場合もhtmlと同様に検索(右上またはctrl+F)したり、クリックでフレームを拡大したりできます。

デフォルトの色が大きく違うので結果も異なるように見えるかもしれませんが基本的には同じ結果です。違いとしては以下のような点になります:
- svg版ではuser/kernelの色の区別はない (ただし色に関してはflamegraph.plのオプションでいろいろと変えられます)
- svg版では横軸はアルファベット順にソートされている。html版では横軸は割合の大きい順でソートされている
- svg版では同じコマンドはPIDが異なっていても1つにまとめられている。html版では同じコマンドでもPIDが違うと別のフレームとして扱われる

# おわりに

perfコマンドから直接生成する場合(html版)も、元のgithubのスクリプトを使用る場合(svg版)も、どちらも文字列の検索やフレームの拡大等ができるので使い勝手はあまり変わらないと思います(個人的にはhtml版の方がデフォルトの色は見やすいと思う一方、svg版のようにコマンド単位で集計する方が分かりやすいような気もします)。どちらを使うかは好みですがperfコマンドだけで簡単にflamegraphが生成できるようになったのは1つの利点かなと思います。

以上