Diagnostics
はじめに
Go エコシステムは、Go プログラムにおけるロジックやパフォーマンスの問題を診断するための、幅広い API とツールを提供しています。このページでは、利用可能なツールをまとめ、Go ユーザーが特定の問題に最適なツールを選択するのに役立ちます。
診断ソリューションは、以下のグループに分類できます。
- プロファイリング: プロファイリングツールは、Go プログラムのメモリ使用量や頻繁に呼び出される関数など、その複雑さとコストを分析し、Go プログラムの負荷の高いセクションを特定します。
- トレーシング: トレーシングは、呼び出しやユーザーリクエストのライフサイクル全体にわたるレイテンシを分析するために、コードを計測する方法です。トレースは、各コンポーネントがシステム全体のレイテンシにどれだけ貢献しているかの概要を提供します。トレースは複数の Go プロセスにまたがることができます。
- デバッグ: デバッグにより、Go プログラムを一時停止し、その実行を調べることができます。プログラムの状態とフローはデバッグで検証できます。
- ランタイム統計とイベント: ランタイム統計とイベントの収集と分析は、Go プログラムの健全性の高レベルな概要を提供します。メトリクスの急増/急落は、スループット、使用率、パフォーマンスの変化を特定するのに役立ちます。
注:一部の診断ツールは相互に干渉する可能性があります。たとえば、正確なメモリプロファイリングは CPU プロファイルを歪ませ、ゴルーチンブロックプロファイリングはスケジューラトレースに影響を与えます。より正確な情報を得るには、ツールを個別に使用してください。
プロファイリング
プロファイリングは、コストの高いコードや頻繁に呼び出されるコードのセクションを特定するのに役立ちます。Go ランタイムは、pprof 可視化ツールで期待される形式のプロファイリングデータを提供します。プロファイリングデータは、go test を介したテスト中、または net/http/pprof パッケージから利用できるエンドポイントを介して収集できます。ユーザーは、プロファイリングデータを収集し、pprof ツールを使用して上位のコードパスをフィルタリングおよび可視化する必要があります。
runtime/pprof パッケージが提供する定義済みプロファイル
- cpu: CPU プロファイルは、プログラムが CPU サイクルを積極的に消費している間(スリープ中や I/O 待機中とは対照的に)にどこで時間を使っているかを決定します。
- heap: ヒーププロファイルはメモリ割り当てサンプルを報告します。現在のメモリ使用量と過去のメモリ使用量を監視し、メモリリークをチェックするために使用されます。
- threadcreate: スレッド作成プロファイルは、新しい OS スレッドの作成につながるプログラムのセクションを報告します。
- goroutine: ゴルーチンプロファイルは、現在のすべてのゴルーチンのスタックトレースを報告します。
-
block: ブロックプロファイルは、ゴルーチンが同期プリミティブ(タイマーチャネルを含む)で待機中にブロックされている場所を示します。ブロックプロファイルはデフォルトでは有効になっていません。
runtime.SetBlockProfileRateを使用して有効にします。 -
mutex: ミューテックスプロファイルはロック競合を報告します。ミューテックス競合が原因で CPU が完全に活用されていないと思われる場合は、このプロファイルを使用します。ミューテックスプロファイルはデフォルトでは有効になっていません。
runtime.SetMutexProfileFractionを参照して有効にします。
Go プログラムのプロファイリングには他にどのようなプロファイラーを使用できますか?
Linux では、Go プログラムのプロファイリングに perf ツールを使用できます。Perf は cgo/SWIG コードとカーネルをプロファイルしてアンワインドできるため、ネイティブ/カーネルのパフォーマンスボトルネックに関する洞察を得るのに役立ちます。macOS では、Go プログラムのプロファイルに Instruments スイートを使用できます。
本番サービスをプロファイリングできますか?
はい。本番環境でプログラムをプロファイリングするのは安全ですが、一部のプロファイル(CPU プロファイルなど)を有効にするとコストがかかります。パフォーマンスの低下が見られることが予想されます。パフォーマンスのペナルティは、本番環境で有効にする前にプロファイラーのオーバーヘッドを測定することで推定できます。
本番サービスを定期的にプロファイリングすることをお勧めします。特に、単一プロセスの多くのレプリカを持つシステムでは、定期的にランダムなレプリカを選択することが安全な選択肢です。本番プロセスを選択し、Y 秒ごとに X 秒間プロファイルし、その結果を可視化と分析のために保存し、その後定期的に繰り返します。問題を見つけるために、結果は手動または自動でレビューされる場合があります。プロファイルの収集は相互に干渉する可能性があるため、一度に単一のプロファイルのみを収集することをお勧めします。
プロファイリングデータを可視化する最良の方法は何ですか?
Go ツールは、go tool pprof を使用して、プロファイルデータのテキスト、グラフ、および callgrind 可視化を提供します。Go プログラムのプロファイリング を読んで、それらが実際に動作している様子をご覧ください。
最もコストのかかる呼び出しをテキストでリスト表示します。
最もコストのかかる呼び出しをグラフとして可視化します。
Weblist ビューは、ソースコードのコストの高い部分を HTML ページに行ごとに表示します。次の例では、runtime.concatstrings に 530ms が費やされ、各行のコストがリストに表示されています。
最もコストのかかる呼び出しを weblist として可視化します。
プロファイルデータを可視化するもう一つの方法は、フレームグラフです。フレームグラフを使用すると、特定の祖先パスを移動できるため、コードの特定のセクションを拡大/縮小できます。上流の pprof はフレームグラフをサポートしています。
フレームグラフは、最もコストのかかるコードパスを特定するための視覚化を提供します。
組み込みプロファイルに限定されますか?
ランタイムによって提供されるものに加えて、Go ユーザーは pprof.Profile を介してカスタムプロファイルを作成し、既存のツールを使用してそれらを調べることができます。
プロファイラーハンドラ (/debug/pprof/...) を別のパスとポートで提供できますか?
はい。net/http/pprof パッケージはデフォルトでハンドラをデフォルトの mux に登録しますが、パッケージからエクスポートされたハンドラを使用して自分で登録することもできます。
たとえば、次の例では、:7777 の /custom_debug_path/profile で pprof.Profile ハンドラを提供します。
package main
import (
"log"
"net/http"
"net/http/pprof"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
log.Fatal(http.ListenAndServe(":7777", mux))
}
トレーシング
トレーシングは、一連の呼び出しのライフサイクル全体にわたるレイテンシを分析するためにコードを計測する方法です。Go は、Go ノードごとの最小限のトレーシングバックエンドとして golang.org/x/net/trace パッケージを提供し、シンプルなダッシュボードを備えた最小限の計測ライブラリを提供します。Go は、一定期間内のランタイムイベントをトレースするための実行トレーサーも提供します。
トレーシングにより、次のことが可能になります。
- Go プロセスにおけるアプリケーションのレイテンシを計測し、分析する。
- 長い一連の呼び出しにおける特定の呼び出しのコストを測定する。
- 使用率とパフォーマンスの改善点を把握する。トレーシングデータがなければボトルネックが常に明確になるとは限らない。
モノリシックシステムでは、プログラムの構成要素から診断データを収集することは比較的簡単です。すべてのモジュールは単一のプロセス内で動作し、ログ、エラー、その他の診断情報を報告するために共通のリソースを共有します。システムが単一のプロセスを超えて分散し始めると、フロントエンドの Web サーバーからすべてのバックエンドまで、応答がユーザーに返されるまで呼び出しを追跡することが難しくなります。ここで、分散トレーシングが本番システムの計測と分析に大きな役割を果たします。
分散トレーシングは、ユーザーリクエストのライフサイクル全体にわたるレイテンシを分析するためにコードを計測する方法です。システムが分散され、従来のプロファイリングツールやデバッグツールがスケーリングしない場合、分散トレーシングツールを使用してユーザーリクエストや RPC のパフォーマンスを分析したいと思うかもしれません。
分散トレーシングにより、次のことが可能になります。
- 大規模なシステムにおけるアプリケーションのレイテンシを計測し、プロファイリングする。
- ユーザーリクエストのライフサイクル内のすべての RPC を追跡し、本番環境でしか見られない統合の問題を特定する。
- システムに適用できるパフォーマンス改善策を把握する。多くのボトルネックは、トレーシングデータを収集するまで明確にならない。
Go エコシステムは、トレーシングシステムごとのさまざまな分散トレーシングライブラリと、バックエンドに依存しないライブラリを提供しています。
各関数呼び出しを自動的にインターセプトしてトレースを作成する方法はありますか?
Go は、すべての関数呼び出しを自動的にインターセプトしてトレーススパンを作成する方法を提供していません。スパンを作成、終了、注釈付けするには、コードを手動で計測する必要があります。
Go ライブラリでトレースヘッダーを伝播するにはどうすればよいですか?
context.Context でトレース識別子とタグを伝播できます。業界ではまだ標準的なトレースキーや共通のトレースヘッダー表現はありません。各トレーシングプロバイダーは、Go ライブラリで伝播ユーティリティを提供する必要があります。
標準ライブラリやランタイムからの他の低レベルイベントをトレースに含めることはできますか?
標準ライブラリとランタイムは、低レベルの内部イベントを通知するための追加 API をいくつか公開しようとしています。たとえば、httptrace.ClientTrace は、送信リクエストのライフサイクルにおける低レベルイベントを追跡するための API を提供します。ランタイム実行トレーサーから低レベルのランタイムイベントを取得し、ユーザーが独自のユーザーイベントを定義して記録できるようにする取り組みが進行中です。
デバッグ
デバッグは、プログラムが誤動作する理由を特定するプロセスです。デバッガーを使用すると、プログラムの実行フローと現在の状態を理解できます。デバッグにはいくつかのスタイルがありますが、このセクションでは、プログラムへのデバッガーのアタッチとコアダンプデバッグにのみ焦点を当てます。
Go ユーザーは主に以下のデバッガーを使用します
- Delve: Delve は Go プログラミング言語用のデバッガーです。Go のランタイム概念と組み込み型をサポートしています。Delve は、Go プログラムのための完全な機能を持つ信頼性の高いデバッガーを目指しています。
- GDB: Go は、標準の Go コンパイラと Gccgo を介して GDB サポートを提供します。スタック管理、スレッド処理、およびランタイムには、GDB が想定する実行モデルとは十分に異なる側面が含まれており、プログラムが gccgo でコンパイルされている場合でもデバッガーを混乱させる可能性があります。GDB を使用して Go プログラムをデバッグすることはできますが、理想的ではなく、混乱を招く可能性があります。
デバッガーは Go プログラムでどの程度うまく機能しますか?
gc コンパイラは、関数インライン化や変数レジスタ化などの最適化を実行します。これらの最適化により、デバッガーを使用したデバッグが困難になる場合があります。最適化されたバイナリ用に生成される DWARF 情報の品質を向上させるための取り組みが進行中です。これらの改善が利用可能になるまで、デバッグ対象のコードをビルドする際には最適化を無効にすることをお勧めします。次のコマンドは、コンパイラの最適化なしでパッケージをビルドします
$ go build -gcflags=all="-N -l"改善の取り組みの一環として、Go 1.10 では新しいコンパイラフラグ
-dwarflocationlists が導入されました。このフラグにより、コンパイラはロケーションリストを追加し、デバッガーが最適化されたバイナリで動作するのを助けます。次のコマンドは、最適化は行うが DWARF ロケーションリストを含むパッケージをビルドします。
$ go build -gcflags="-dwarflocationlists=true"
推奨されるデバッガーユーザーインターフェースは何ですか?
delve と gdb の両方が CLI を提供していますが、ほとんどのエディタ統合と IDE はデバッグ固有のユーザーインターフェースを提供しています。
Go プログラムで事後デバッグを行うことは可能ですか?
コアダンプファイルとは、実行中のプロセスのメモリダンプとプロセスステータスを含むファイルです。主にプログラムの事後デバッグや、実行中のプログラムの状態を理解するために使用されます。これらの2つのケースにより、コアダンプのデバッグは、事後分析や本番サービスの診断に役立つツールとなります。Go プログラムからコアファイルを取得し、delve または gdb を使用してデバッグすることができます。詳細な手順については、コアダンプデバッグのページを参照してください。
ランタイム統計とイベント
ランタイムは、ユーザーがランタイムレベルでパフォーマンスと利用率の問題を診断するために、統計と内部イベントのレポートを提供します。
ユーザーはこれらの統計を監視して、Go プログラムの全体的な健全性とパフォーマンスをよりよく理解することができます。頻繁に監視される統計と状態の一部を以下に示します。
runtime.ReadMemStatsは、ヒープ割り当てとガベージコレクションに関連するメトリクスを報告します。メモリ統計は、プロセスがどれだけのメモリリソースを消費しているか、プロセスがメモリをうまく利用できるか、メモリリークを検出するのに役立ちます。debug.ReadGCStatsは、ガベージコレクションに関する統計を読み取ります。GC ポーズにどれだけのリソースが費やされているかを確認するのに役立ちます。また、ガベージコレクターのポーズのタイムラインとポーズ時間のパーセンタイルも報告します。debug.Stackは現在のスタックトレースを返します。スタックトレースは、現在どれだけのゴルーチンが実行されているか、何をしているか、ブロックされているかどうかを確認するのに役立ちます。debug.WriteHeapDumpは、すべてのゴルーチンの実行を一時停止し、ヒープをファイルにダンプすることを可能にします。ヒープダンプは、特定の時点での Go プロセスのメモリのスナップショットです。割り当てられたすべてのオブジェクト、ゴルーチン、ファイナライザーなどが含まれます。runtime.NumGoroutineは、現在のゴルーチンの数を返します。この値を監視して、十分なゴルーチンが利用されているかどうか、またはゴルーチンリークを検出することができます。
実行トレーサー
Go には、幅広いランタイムイベントをキャプチャするランタイム実行トレーサーが付属しています。スケジューリング、システムコール、ガベージコレクション、ヒープサイズ、その他のイベントはランタイムによって収集され、go tool trace による可視化に利用できます。実行トレーサーは、レイテンシと利用率の問題を検出するためのツールです。CPU がどの程度利用されているか、およびネットワークまたはシステムコールがゴルーチンにとってプリエンプションの原因となっている時期を調べることができます。
トレーサーは次の用途に役立ちます。
- ゴルーチンがどのように実行されるかを理解する。
- GC 実行などのコアランタイムイベントの一部を理解する。
- 並列処理が不十分な実行を特定する。
ただし、過剰なメモリ使用量や CPU 使用量の原因を分析するなど、ホットスポットを特定するのにはあまり適していません。それらに対処するには、まずプロファイリングツールを使用してください。
上記では、go tool trace の可視化により、実行は順調に開始された後、逐次化されたことが示されています。これは、ボトルネックを引き起こす共有リソースに対するロック競合がある可能性を示唆しています。
ランタイムトレースを収集して分析するには、go tool trace を参照してください。
GODEBUG
GODEBUG 環境変数が適切に設定されている場合、ランタイムもイベントと情報を出力します。
- GODEBUG=gctrace=1 は、各コレクションでガベージコレクタイベントを出力し、収集されたメモリ量とポーズの長さを要約します。
- GODEBUG=inittrace=1 は、完了したパッケージ初期化作業の実行時間とメモリ割り当て情報の要約を出力します。
- GODEBUG=schedtrace=X は、X ミリ秒ごとにスケジューリングイベントを出力します。
GODEBUG 環境変数を使用すると、標準ライブラリとランタイムでの命令セット拡張の使用を無効にできます。
- GODEBUG=cpu.all=off は、すべてのオプションの命令セット拡張の使用を無効にします。
- GODEBUG=cpu.extension=off は、指定された命令セット拡張からの命令の使用を無効にします。
extension は、sse41 や avx のような命令セット拡張の小文字の名前です。