Skip to content

Flame Graph

Flame Graph の用途

Flame Graph は、関数で費やされた CPU 時間を視覚化する手法です。同期処理に過剰な時間を費やしている箇所を特定するのに役立ちます。

Flame Graph の作成方法

Node.js の Flame Graph 作成は難しいと聞いたことがあるかもしれませんが、それはもう違います(過去の話です)。Solaris の仮想マシンは Flame Graph にはもう必要ありません!

Flame Graph は perf の出力から生成されます。これは Node 固有のツールではありません。CPU 時間の消費状況を視覚化する最も強力な方法ですが、Node.js 8 以降での JavaScript コードの最適化方法に関する問題が発生する可能性があります。詳細は以下の perf の出力に関する問題 セクションを参照してください。

事前パッケージ化されたツールを使用する

ローカルで Flame Graph を生成するワンステップの方法が必要な場合は、0x を試してください。

本番環境の診断については、これらのメモを参照してください:0x 本番サーバー

システムの perf ツールを使用して Flame Graph を作成する

このガイドの目的は、Flame Graph を作成する手順を示し、各ステップを制御できるようにすることです。

各ステップをより深く理解したい場合は、詳細を説明する次のセクションを参照してください。

では、始めましょう。

  1. perf をインストールします(既にインストールされていない場合は、通常 linux-tools-common パッケージで利用できます)。
  2. perf を実行してみてください。カーネルモジュールが見つからないというエラーが表示される場合があります。その場合は、モジュールもインストールしてください。
  3. perf を有効にして Node を実行します(Node.js のバージョンに固有のヒントについては、perf の出力に関する問題 を参照してください)。
bash
perf record -e cycles:u -g -- node --perf-basic-prof app.js
  1. パッケージがないために perf を実行できないという警告でない限り、警告は無視してください。アクセスできないカーネルモジュールサンプルに関する警告が表示される場合がありますが、それはここでは目的ではありません。
  2. perf script > perfs.out を実行して、すぐに視覚化するデータファイルを作成します。より読みやすいグラフにするために、いくつかのクリーンアップを適用すると便利です。
  3. まだインストールされていない場合は、stackvis をインストールします npm i -g stackvis
  4. stackvis perf < perfs.out > flamegraph.htm を実行します。

これで、お気に入りのブラウザで Flame Graph ファイルを開き、燃える様子を見てみましょう。色分けされているので、最も濃いオレンジ色のバーから先に注目できます。これらは CPU を大量に消費する関数を表している可能性が高いです。

ちなみに、Flame Graph の要素をクリックすると、その周辺の拡大図がグラフの上に表示されます。

perfを使った実行中プロセスのサンプリング

これは、中断したくない既に実行中のプロセスからのフレームグラフデータの記録に最適です。再現が難しい問題を抱える本番プロセスを想像してみてください。

bash
perf record -F99 -p `pgrep -n node` -- sleep 3

sleep 3とは何でしょうか?これは、-pオプションが異なるpidを指しているにもかかわらず、perfを動作し続けるために必要です。コマンドはプロセス上で実行され、それと共に終了する必要があります。perfは、実際にそのコマンドをプロファイリングしているかどうかに関係なく、渡されたコマンドの寿命の間実行されます。sleep 3は、perfが3秒間実行されることを保証します。

-F(プロファイリング頻度)が99に設定されているのはなぜですか?これは妥当なデフォルト値です。必要に応じて調整できます。-F99は、perfに1秒間に99個のサンプリングを行うように指示し、精度を高めるには値を増やします。値を小さくすると、出力は少なくなり、精度は低くなります。必要な精度は、CPU集約型関数が実際に実行される時間に依存します。顕著な速度低下的原因を探している場合、1秒間に99フレームあれば十分でしょう。

3秒間のperf記録を取得したら、上記の最後の2つの手順でフレームグラフを生成してください。

Node.js内部関数のフィルタリング

通常、呼び出しのパフォーマンスのみを確認したいので、Node.jsとV8の内部関数をフィルタリングすると、グラフを読みやすくなります。perfファイルを次のようにクリーンアップできます。

bash
sed -i -r \
    -e '/(_libc_start|LazyCompile) |v8::internal::BuiltIn|Stub|LoadIC:\\[\\[' \
    -e '/^$/d' \
    perf.data > perf.out

フレームグラフを読み込んだときに、何かが欠けているように見え、最も多くの時間を消費している主要な関数に何かが欠けている場合は、フィルタなしでフレームグラフを生成してみてください。Node.js自体に問題が発生しているまれなケースが発生する可能性があります。

Node.jsのプロファイリングオプション

--perf-basic-prof-only-functions--perf-basic-profは、JavaScriptコードのデバッグに役立つ2つのオプションです。他のオプションはNode.js自体のプロファイリングに使用され、このガイドの範囲外です。

--perf-basic-prof-only-functionsは、出力が少なくなるため、オーバーヘッドが最も少ないオプションです。

なぜこれらが必要なのか?

これらのオプションがなければ、フレームグラフは生成されますが、ほとんどのバーにv8::Function::Callというラベルが付きます。

Perf出力の問題

Node.js 8.x V8パイプラインの変更

Node.js 8.x以降では、V8エンジンのJavaScriptコンパイルパイプラインに新しい最適化が導入されており、perfでは関数名/参照にアクセスできなくなる場合があります。(Turbofanと呼ばれています)

その結果、フレームグラフで関数名が正しく表示されない可能性があります。

関数名が表示されるはずの場所にByteCodeHandler:が表示されます。

0xには、それに対するいくつかの軽減策が組み込まれています。

詳細は以下を参照してください。

Node.js 10+

Node.js 10.xでは、--interpreted-frames-native-stackフラグを使用してTurbofanの問題に対処しています。

V8がどのパイプラインを使用してJavaScriptをコンパイルしたかに関係なく、フレームグラフで関数名を取得するには、node --interpreted-frames-native-stack --perf-basic-prof-only-functionsを実行します。

フレームグラフにおける壊れたラベル

次のようなラベルが表示されている場合

bash
node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPMS0_8Zone

これは、使用しているLinux perfがデマングルサポート付きでコンパイルされていないことを意味します。https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654を参照してください。

フレームグラフ演習を使用して、自分でフレームグラフのキャプチャを練習してください!