フレームグラフ

フレームグラフは何に役立つのか?

フレームグラフは、関数で費やされたCPU時間を視覚化する方法です。同期的な操作に時間をかけすぎている場所を特定するのに役立ちます。

フレームグラフの作成方法

Node.jsのフレームグラフ作成は難しいと聞いたことがあるかもしれませんが、それは(もはや)事実ではありません。フレームグラフにSolaris VMはもう必要ありません!

フレームグラフは、Node固有のツールではないperfの出力から生成されます。CPU時間を視覚化する最も強力な方法ですが、Node.js 8以降でJavaScriptコードが最適化される方法に問題がある場合があります。以下のperf出力の問題のセクションを参照してください。

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

ローカルでフレームグラフを生成する単一のステップが必要な場合は、0xをお試しください。

本番環境のデプロイを診断するには、次のメモをお読みください: 0x 本番サーバー

システムのperfツールでフレームグラフを作成する

このガイドの目的は、フレームグラフを作成する手順を示し、各ステップを自分で制御できるようにすることです。

各ステップをよりよく理解したい場合は、詳細を説明している以下のセクションをご覧ください。

さあ、始めましょう。

  1. perfをインストールします(まだインストールされていない場合は、通常linux-tools-commonパッケージを通じて利用可能です)。

  2. perfを実行してみてください - カーネルモジュールが不足していると文句を言うかもしれませんが、それらもインストールしてください。

  3. perfを有効にしてnodeを実行します(Node.jsのバージョンに特有のヒントについてはperf出力の問題を参照してください)。

    perf record -e cycles:u -g -- node --perf-basic-prof --interpreted-frames-native-stack app.js
    
  4. パッケージが不足しているためにperfを実行できないという警告でない限り、警告は無視してください。探しているわけではないカーネルモジュールのサンプルにアクセスできないという警告が出るかもしれませんが、問題ありません。

  5. perf script > perfs.out を実行して、後で視覚化するデータファイルを生成します。読みやすいグラフにするために、いくつかのクリーンアップを適用すると便利です。

  6. Brendan GreggのFlameGraphツールをクローンします: https://github.com/brendangregg/FlameGraph

  7. cat perfs.out | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl --colors=js > profile.svg を実行します。

これで、お気に入りのブラウザでフレームグラフファイルを開き、燃える様子を見てください。色分けされているので、まずは最も彩度の高いオレンジ色のバーに注目できます。それらはCPU負荷の高い関数を表している可能性が高いです。

特筆すべきは、フレームグラフの要素をクリックすると、クリックしたセクションにズームインすることです。

perfを使用して実行中のプロセスをサンプリングする

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

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

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

なぜ-F(プロファイリング頻度)が99に設定されているのですか?これは妥当なデフォルト値です。必要に応じて調整できます。-F99はperfに毎秒99サンプルを取得するように指示します。より高い精度が必要な場合は値を増やしてください。低い値は、より精度の低い結果でより少ない出力を生成するはずです。必要な精度は、CPU負荷の高い関数が実際にどれくらいの時間実行されるかによって異なります。顕著な速度低下の原因を探している場合、毎秒99フレームで十分すぎるはずです。

その3秒間のperfレコードを取得したら、上記の手順の最後の2つでフレームグラフの生成に進みます。

Node.jsの内部関数を除外する

通常、自分の呼び出しのパフォーマンスだけを見たいので、Node.jsとV8の内部関数を除外すると、グラフがはるかに読みやすくなります。perfファイルを次のようにクリーンアップできます。

sed -i -r \
  -e "/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|\[unknown\]| LoadPolymorphicIC:)/d" \
  -e 's/ LazyCompile:[*~]?/ /' \
  perfs.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の問題に対処します。

node --interpreted-frames-native-stack --perf-basic-prof-only-functions を実行すると、V8がどのパイプラインを使用してJavaScriptをコンパイルしたかに関わらず、フレームグラフに関数名が表示されます。

フレームグラフの壊れたラベル

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

node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPNS0_8ZoneListIPNS0_9StatementEEE

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

フレームグラフ演習で、自分でフレームグラフをキャプチャする練習をしましょう!