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 を作成する手順を示し、各ステップを制御できるようにすることです。
各ステップをより深く理解したい場合は、詳細を説明する次のセクションを参照してください。
では、始めましょう。
perf
をインストールします(既にインストールされていない場合は、通常 linux-tools-common パッケージで利用できます)。perf
を実行してみてください。カーネルモジュールが見つからないというエラーが表示される場合があります。その場合は、モジュールもインストールしてください。perf
を有効にして Node を実行します(Node.js のバージョンに固有のヒントについては、perf の出力に関する問題 を参照してください)。
perf record -e cycles:u -g -- node --perf-basic-prof app.js
- パッケージがないために perf を実行できないという警告でない限り、警告は無視してください。アクセスできないカーネルモジュールサンプルに関する警告が表示される場合がありますが、それはここでは目的ではありません。
perf script > perfs.out
を実行して、すぐに視覚化するデータファイルを作成します。より読みやすいグラフにするために、いくつかのクリーンアップを適用すると便利です。- まだインストールされていない場合は、stackvis をインストールします
npm i -g stackvis
stackvis perf < perfs.out > flamegraph.htm
を実行します。
これで、お気に入りのブラウザで Flame Graph ファイルを開き、燃える様子を見てみましょう。色分けされているので、最も濃いオレンジ色のバーから先に注目できます。これらは CPU を大量に消費する関数を表している可能性が高いです。
ちなみに、Flame Graph の要素をクリックすると、その周辺の拡大図がグラフの上に表示されます。
perf
を使った実行中プロセスのサンプリング
これは、中断したくない既に実行中のプロセスからのフレームグラフデータの記録に最適です。再現が難しい問題を抱える本番プロセスを想像してみてください。
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
ファイルを次のようにクリーンアップできます。
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には、それに対するいくつかの軽減策が組み込まれています。
詳細は以下を参照してください。
- https://github.com/nodejs/benchmarking/issues/168
- https://github.com/nodejs/diagnostics/issues/148#issuecomment-369348961
Node.js 10+
Node.js 10.xでは、--interpreted-frames-native-stack
フラグを使用してTurbofanの問題に対処しています。
V8がどのパイプラインを使用してJavaScriptをコンパイルしたかに関係なく、フレームグラフで関数名を取得するには、node --interpreted-frames-native-stack --perf-basic-prof-only-functions
を実行します。
フレームグラフにおける壊れたラベル
次のようなラベルが表示されている場合
node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPMS0_8Zone
これは、使用しているLinux perfがデマングルサポート付きでコンパイルされていないことを意味します。https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654を参照してください。
例
フレームグラフ演習を使用して、自分でフレームグラフのキャプチャを練習してください!