mcpField
P
performance-rca

performance-rca

github.com/grafana開発295128 回インストール1 日前に更新
リポジトリ

performance-rca について

gcx profilesを使って性能問題を調査するスキルです。ワークロードの比較からコードパスの特定までを進め、根拠と不確実性を明示します。

主な機能

  1. 01プロファイルと時間範囲の確認
  2. 02適切なベースライン比較
  3. 03ワークロード区分の特定
  4. 04関数の順位付けと呼び出し経路の追跡
  5. 05根拠に基づく調査レポート

活用シーン

  1. 01サービスの性能劣化の調査
  2. 02CPUや割り当てコストのコードパスへの帰属
  3. 03プロファイリング範囲の不足の報告

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/grafana/skills --skill performance-rca

インストール後、Claude Code で/performance-rcaを入力して読み込まれたか確認します。

同梱ファイルreferences/advanced-analysis.md

詳細

パフォーマンスの根本原因分析

観測されたパフォーマンスの問題について、どのワークロードが変化したのか、どのコードパスがその変化の原因なのか、そのメカニズムを裏付ける証拠は何かを説明します。ホットスポットは場所を示すものであり、異常は手がかりです。どちらも、それだけでは根本原因の証明にはなりません。結論は、裏付けられる最も確かな範囲にとどめ、結論が出ない場合も明記します。コードの変更と予防的な最適化は、別の作業です。

調査の手順

  1. セクション1~5を順に進めます。前のステップまたは参照先の調査で確認済みの証拠は再利用し、手順を満たすためだけに発見作業を繰り返さないでください。結果を解釈する前に、各ステップの前提条件を確認します。
  2. 比較の前に、コンテキスト、データソース、UTC の時間範囲、セレクター、プロファイルタイプを確定します。不明な識別子は特定し、決して捏造しないでください。スコープや比較方法を正当な理由で変更した場合は、その内容を記録します。
  3. 回帰を主張する前に、セクション2で比較対象のコントロールが適切であることを確認します。コントロールがない場合は、現在のコストの帰属分析のみを続け、比較対象がないことを明記します。
  4. 空の結果、未対応の機能、クエリの失敗を区別します。該当するプロファイリングパスにはセクション1の終了条件を適用し、前提条件が満たされる場合に限って、記載されたフォールバックを使用します。
  5. 出力形式を明示的に選択します。発見とメトリクスには JSON、関数の順位付けには table、呼び出し元と呼び出し先の追跡には DOT、エクスポートには pprof を使用します。診断情報と制限事項も保持します。
  6. 終了条件に達した場合も含め、必ずセクション5のレポートを作成します。対応できない項目には、理由を添えて unknown、not measured、または not applicable を記入します。測定がないことを、サービスが正常である証拠として扱ってはいけません。

1. 質問を維持し、利用可能なプロファイルを確認する

ユーザーまたは参照元のスキルから提供されたコンテキスト、データソース、サービス、症状、インシデント期間、デプロイの事実、トレース/プロファイルのリンクを再利用します。インシデントとベースラインの時刻は絶対 UTC 時刻で指定します。時刻付きの症状の証拠から不足しているインシデント期間を導き出すか、クエリを実行する前にユーザーに確認します。リモートコマンドでは同じ --context <context> を指定し(以下では省略)、データソース UID を明示的に渡します。調査は読み取り専用のまま行い、コンテキストを切り替えたり、コレクションを変更したりしないでください。

gcx help-tree profiles -o text

以下の例には gcx v1.3.0 以降が必要です。gcx --version で確認してください。gcx が見つからないか古い場合は、続行する前に https://github.com/grafana/gcx からインストールまたはアップグレードするようユーザーに依頼してください。コマンド名の変更を推測したり、時間フィルターを黙って省略したりしないでください。コマンド、フラグ、出力形式がバックエンドでサポートされていない場合があります。その場合の代替方法は、セクション 4 と高度なリファレンスで説明しています。

# Discover only if the datasource UID is unknown; choose the target environment.
gcx datasources list -t grafana-pyroscope-datasource -o json
gcx profiles data-range -d <pyro-uid> -o json
gcx profiles list-profile-types -d <pyro-uid> \
  --from <incident-from> --to <incident-to> -o json

CLI のサポートは、バックエンドでのサポートを意味するものではありません。data-range はテナント全体の dataIngested、oldestProfileTime、newestProfileTime を返します(Unix ミリ秒。0 は不明を意味します)。これらの範囲だけでは、選択したサービスやレンズに継続的なデータがあることは証明できません。固定の保持期間があると想定しないでください。

続行する前に、以下の結果に従ってください。いずれかの終了条件に該当した場合、影響を受けるプロファイルクエリをすべて停止し、セクション 5 のレポートを作成します。不足している証拠についての質問を明示したうえで、gcx にバンドルされている debug-with-grafana スキル(gcx agent skills get debug-with-grafana)を通じて、ほかの利用可能なシグナルの調査を続けてもかまいません。

  • 終了 A — プロファイリングを利用できない: 対象のデータソースがない、アクセス権が不足している、または必要な CLI 機能を利用できません。制限の内容を正確に記録します。セットアップやアクセスが不足している場合は gcx login と gcx config check を使用し、CLI の不一致がある場合は互換性のある gcx ビルドを使用します。
  • 終了 B — データ取り込みの報告なし: 統計情報の取得は成功したものの、dataIngested: false が返されました。サービスの健全性ではなく、計装/データの不足を報告します。
  • 終了 C — インシデント期間がカバーされていない: インシデント期間が、既知のクエリ可能な範囲と重複していません。両方の範囲を報告し、インシデント期間を今日に置き換えないでください。一部のみ重複している場合は、その重複部分だけを分析し、カバー範囲の不足を記録して、観測した期間に結論を限定します。
  • 終了 D — 利用可能なインシデントプロファイルがない: 確認済みの事実に照らしてセレクター、期間、レンズを確認しても、インシデントのサンプルが見つからないか、サンプル数が 0 のままです。ベースラインだけが欠けている場合はこの終了条件には該当せず、セクション 2 の手順に従います。コストが 0 である証拠ではなく、未解決のカバー範囲/計装の不足を報告します。

統計情報がない、サポートされていない、または範囲が不明な場合は、範囲を絞って確認を続けてください。統計情報がないことだけを理由に、利用可能なタイプ/プロファイルクエリを中断してはいけません。こうしたクエリを帰属分析に利用するには、対応するサンプルが必要です。

クエリエラーが発生した場合は、操作と診断内容を記録します。セレクターやフラグの誤りが確認できた場合は修正し、機能がサポートされていない場合は文書化された代替方法を使用します。認証エラーに対して再試行したり、根拠なくセレクターを変えたりしないでください。クエリの失敗は、結果が空のクエリ成功と同じではありません。

2. 比較するレンズと適切な比較対象を選ぶ

ディスカバリーで取得したプロファイルタイプ ID を、単位も含めて正確にコピーします。Prometheus のジョブ、Tempo のサービス名、Kubernetes のワークロード、Pyroscope の service_name が同じ値であると思い込まず、ラベルを確認してください。

症状確認するレンズ解釈上の制限
CPU 使用率が高い、または CPU がボトルネックのレイテンシCPU 時間すべてのリクエストレイテンシ、スケジューリング遅延、スロットリングは測定しません
アロケーション/GC の負荷割り当てバイト数/オブジェクト数頻繁な割り当ては、保持されているヒープやメモリーリークを示すものではありません
ヒープの増加/OOM使用中のバイト数/オブジェクト数と、割り当てを併せて確認生存ヒープのサンプルはスナップショットです。プロセスの RSS、ネイティブメモリー、保持の所有元を示すものではありません
競合、またはブロックされている処理ミューテックス/ブロックの遅延と回数ランタイム固有のサンプリングと帰属に左右され、CPU 時間とは異なります
goroutine/スレッドリークの疑いgoroutine/スレッド数とスタック増加だけでは、処理がリークしている証拠にはなりません

必要なレンズがない場合は、計装の不足を記録してください。待機を否定するために CPU を代用したり、リークを証明するために割り当てを代用したりしないでください。

ベースラインは、単にインシデント発生前の期間ではなく、通常時の動作を表す必要があります。リクエストの種類/レート、レプリカ数、収集範囲、ランタイム、サンプリング設定が比較可能な、同じ長さの期間を優先してください。日内変動のあるワークロードでは、同じ時刻の過去の期間が必要になる場合があります。ベースライン期間のプロファイルタイプとデータも確認してください。適切な過去のベースラインがない場合は、影響を受けていないピアと比較してください。適切な比較対象がなければ、リグレッションだと断定せず、現在のコストの発生箇所を報告してください。安定していても非効率なホットパスが、負荷増加時に新たな症状を引き起こすことはあります。プロファイルの形状が変わっていなくても、そのホットパスが原因でないとは言い切れません。

デプロイメントを比較する場合は、安定したワークロードラベルを固定します。ただし、インシデント時のバージョン/Pod が存在していなかった場合は、ベースラインに古いバージョン/Pod を選択してください。両方のセレクターと、比較可能であると判断した理由を記録します。存在しない古い Pod/バージョンと、データのある新しい Pod/バージョンを比較し、無限大のリグレッションと呼ぶことは避けてください。

異常の結果から、サービス、レンズ、発生時刻、影響を受けたラベル、候補となるベースラインを得られる場合があります。リンク、期間、観測値/期待値、スコアが提示されている場合は、それらを保持し、プロファイルと症状の証拠で検証してください。異常を起点とするエントリーパスについては、高度な分析を参照してください。このスキルの実行に異常検出器は必要ありません。

3. 変化箇所を特定する:サービス → ワークロードの範囲

テナント全体のランキングではなく、判明している疑いのある対象から始めます。サービスが不明な場合は、profiles labels で service_name の値を確認してから、判明している環境内で順位付けします。必要な場合にのみ、テナント全体へのクエリを使用してください。

gcx profiles labels -d <pyro-uid> \
  --from <incident-from> --to <incident-to> -o json
gcx profiles labels -d <pyro-uid> --label service_name \
  --from <incident-from> --to <incident-to> -o json
# Establish onset and coverage; repeat for the baseline window.
gcx profiles metrics -d <pyro-uid> '{service_name="<suspect>"}' \
  --profile-type <lens> --from <incident-from> --to <incident-to> -o json
# Pick a discovered label that can distinguish affected/unaffected workloads.
gcx profiles metrics -d <pyro-uid> '{service_name="<suspect>"}' \
  --profile-type <lens> --from <incident-from> --to <incident-to> \
  --top --group-by <label> --limit 20 -o json

profiles series を使用すると、プロファイルタイプを指定せずに、サービスの実際のラベルの組み合わせを確認できます。ラベルの射影については、スコープを指定した系列の検出を参照してください。テナント全体のラベル一覧だけでは、疑いのある対象でラベルが共存している証拠にはなりません。

ベースラインでも、選択したグループ分けを繰り返します。リーダーボードに表示されるのは上限数までの候補であり、フリートの全数調査ではありません。欠けているグループは上限の範囲外だった可能性があります。単一のグループだけでは、ピアとの比較はできません。合計値が似ていても、インスタンスごとの動作が均一とは限りません。また、最大のグループは、単により多くのトラフィックを処理しているか、レプリカを多く含んでいる可能性があります。必要に応じて、既存のメトリクスからリクエスト数またはレプリカの稼働状況を確認してください。確認できない場合は、その交絡要因を明記します。固定の比率しきい値を使って、スライスが異常だと判定しないでください。

時間系列の値を使って、継続性と発生時点を確認します。--top は期間内の値を集約します。特に生存ヒープやカウントのスナップショットでは、単位と集計の意味を明確にしてください。スナップショット値を合計して「割り当てられたバイト数」としたり、生のプロファイル合計からリクエストあたりのコストを推定したりしないでください。

4. 変更されたアプリケーションスタックを特定する

以下のクエリを、インシデント用セレクターと、条件を絞り込んだベースライン用セレクター/ウィンドウの両方で実行します。サービス全体を分析する場合は、スライスマッチャーを省略します。

# Rank functions.
gcx profiles query -d <pyro-uid> \
  '{service_name="<suspect>",<label>="<value>"}' \
  --profile-type <lens> --from <incident-from> --to <incident-to> -o table
# Trace callers and callees of the ranked functions.
gcx profiles query -d <pyro-uid> \
  '{service_name="<suspect>",<label>="<value>"}' \
  --profile-type <lens> --from <incident-from> --to <incident-to> -o dot

関数の順位とシェアはテーブルから取得します。テーブルは各関数を集計し、SELF の上位 20 件を一覧表示します。PERCENTAGE 列は SELF をプロファイル合計で割った値であり、関数の TOTAL シェアではありません。テーブルには上位 20 件までしか表示されないため、GC 関数全体などの関数群のシェアを合計するには、-o json または pprof エクスポートを使って、プロファイル全体の SELF を合計します。60 文字を超える名前は末尾が ... に切り詰められます。報告または解釈する前に、対応する DOT ノードから完全な名前を取得してください。

呼び出し元/呼び出し先のコンテキスト、ソース位置、完全な関数名を確認するには DOT を使います。ノードが単一のソース行(file.go:line)を表す場合があるため、1 つの関数が複数のノードとして表示されることがあります。ラベルでは関数名が複数行に分割され、パッケージパスの最後の要素から始まり、その後に self (%) of cumulative (%)、または両者が等しい場合は単一の値が続きます。関数の順位付けや SELF シェアの確認に DOT ノードを使わないでください。バックエンドが DOT をサポート対象外として明示的に拒否した場合、またはフレームグラフを返した場合、gcx は警告を出してテーブル出力にフォールバックします。それ以外のエラーではクエリは失敗します。返された形式を確認し、stderr を保持してください。JSON は人間が読む関数テーブルではなく、フレームグラフのワイヤーデータをエンコードします。また、エージェントの出力では、読み取りが必要なスピルファイルが参照されることがあります。

DOT はデフォルトで枝刈りされており、点線のエッジは中間フレームを飛ばしている場合があります。呼び出し元のコンテキストが不足している場合は、両方のウィンドウで同じクエリを -o dot --max-nodes 250 を指定して再実行します。これによりノード上限は引き上げられますが、枝刈りされていないグラフが保証されるわけではありません。どちらの形式でも、パスが欠落していないとは断定できません。帰属先が曖昧な場合や数値に差異がある場合は、pprof の比較を読み、両方のプロファイルをエクスポートしてください。エクスポートしたプロファイルによって収集時の欠落が修復されると想定しないでください。

以下の制約を踏まえて、証拠を解釈します。

  • 各ウィンドウで絶対コストとシェアを、単位と分母を明示して比較します。シェアが同じでもコストが倍増している場合があります。シェアが大きくなるのは、ほかの処理が減ったための場合もあります。パーセンテージはリクエストのレイテンシではなく、回帰の証明にもなりません。
  • Self/flat はそのフレームでの処理を意味し、total/cumulative は子孫フレームを含みます。呼び出し元/呼び出し先のコンテキストを使って、意味のあるアプリケーションパスを特定します。祖先の合計は重複するため、足し合わせたり、実行時処理を推測した呼び出し元に割り当てたりしないでください。
  • ランタイムシンボルは、検証すべき仕組みを示唆します。mallocgc は割り当て処理を示唆します。GC ワーカーは割り当て箇所とは別に実行されることがあります。nanotime だけではビジーループの証明になりません。futex/ロック内の CPU サンプルでは、スリープ中の時間は測定されません。待機の確認には、サポートされている待機用レンズとほかの証拠を使います。
  • Go では、ヒープスタックが示すのは割り当て箇所であり、参照を保持しているオブジェクトではありません。ミューテックスプロファイルでは競合が保持側/ロック解除側のパスに帰属し、ブロックプロファイルでは待機側に帰属します。この解釈を別の言語やガベージコレクターに適用する前に、実際のランタイムのセマンティクスを確認してください。

対応するチェックアウト/ビルドが利用できる場合は、該当する関数を調査して、その仕組みを検証します。プロファイルの位置情報と、検証済みのデプロイリビジョンのメタデータを確認します。Build ID が Git SHA である、または service_repository が常に存在すると想定しないでください。シンボル解決とリビジョン情報で裏付けられる場合に限り file:line を報告します。それ以外の場合は関数レベルの帰属を維持し、何が不足しているかを記載します。この調査の一環としてソースを編集しないでください。

5. 原因を検証して報告する

疑わしい経路を、元の症状までたどります。gcx の debug-with-grafana スキルを通じて、関連する証拠を確認してください。リクエスト/CPU/ヒープの推移、スロットリング、キューイング、依存関係のレイテンシー、ログ、設定やデプロイの変更を調べます。既存の調査結果を再利用し、主な説明と重要な代替説明を区別できる場合に限り、追加でクエリを実行してください。リクエストが提供されている場合は、トレース相関を参照してください。

デプロイのタイムスタンプや検出器のスコアは相関関係を示すにすぎません。因果関係の主張を強めるには、発生時期の一致、具体的なメカニズム、影響を受けた対象と受けていない対象の対照、再現可能な差異、またはすでに観測された回復を示してください。仮説を検証するためだけに、ロールバックや負荷テストを行わないでください。

ユーザーの質問に答えられた場合、または証拠不足によりこれ以上の原因特定ができない場合は、調査を終了してください。メカニズムと症状とのつながりが裏付けられている場合にのみ根本原因を報告してください。それ以外の場合は、原因の疑い、コストが発生している箇所、または結論が出ない旨を報告してください。「これらのプロファイルでは観測されなかった」を「このサービスでは発生していない」と言い換えてはいけません。

どのような結果の場合も、このレポートを使用してください。フィールド名と順序は維持し、結論を裏付ける必要がある場合にのみ Evidence の下に詳細を追加してください。調査が妨げられた場合、または証拠が得られなかった場合は inconclusive とし、終了理由と不足している証拠を記録してください。適切なベースラインがない場合は、測定されたコストの発生箇所を報告し、回帰とは断定しないでください。不明なフィールドは明示したままにし、推測した値で埋めないでください。

Conclusion:     <established cause | suspected cause | cost location |
                 inconclusive> — <answer to the original question>
Exit:           <A | B | C | D | none> — <reason, if applicable>
Scope:          <context, datasource UID, service>
Lens:           <exact profile type and units>
Incident:       <UTC from/to; exact selector; observed coverage>
Baseline:       <UTC from/to; exact selector; or unavailable with reason>
Control:        <why comparable; workload/collection differences or gaps>
Slice:          <affected label/value; evidence of scope or unknown>
Location:       <application stack; file:line and revision only if verified>
Cost:           <incident vs baseline absolute values, units, aggregation>
Share:          <incident vs baseline; self/cumulative and denominator>
Normalization:  <method and measured denominator | not applied, with reason>
Mechanism:      <supported explanation or explicitly labeled hypothesis>
Evidence:       <profile artifacts/links and corroborating symptom evidence>
Counterevidence: <alternatives checked and results | not checked, with reason>
Confidence:     <high | medium | low> — <reason tied to available evidence>
Limitations:    <coverage, sampling, baseline, symbolization or access gaps>
Next action:    <smallest missing evidence or handoff question | none>

コメント