診断
はじめに
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 では、perf ツールを使用して Go プログラムをプロファイリングできます。Perf は cgo/SWIG コードとカーネルのプロファイリングとアンワインドを行うことができるため、ネイティブ/カーネルのパフォーマンスボトルネックに関する洞察を得るのに役立ちます。macOS では、Instruments スイートを使用して Go プログラムをプロファイリングできます。
本番サービスをプロファイリングできますか?
はい。本番環境でプログラムをプロファイリングすることは安全ですが、一部のプロファイル(例:CPU プロファイル)を有効にするとコストが増加します。パフォーマンスの低下が見込まれます。パフォーマンスのペナルティは、本番環境で有効にする前にプロファイラのオーバーヘッドを測定することで推定できます。
本番サービスを定期的にプロファイリングすることをお勧めします。特に単一プロセスの多くのレプリカを持つシステムでは、ランダムにレプリカを選択するのが安全なオプションです。本番プロセスを選択し、Y 秒ごとに X 秒間プロファイリングして結果を可視化と分析のために保存します。次に、定期的に繰り返します。結果は手動で、または自動的にレビューして問題を見つけることができます。プロファイルの収集は互いに干渉する可能性があるため、一度に 1 つのプロファイルのみを収集することをお勧めします。
プロファイリングデータを可視化する最良の方法は何ですか?
Go ツールは、go tool pprof
を使用して、プロファイルデータのテキスト、グラフ、および callgrind の可視化を提供します。Go プログラムのプロファイリングを読んで、実際に使用している様子を確認してください。
最も高コストの呼び出しのテキストによるリスト。
最も高コストの呼び出しのグラフによる可視化。
Webリストビューは、ソースコードの行単位で高コストの部分を HTML ページに表示します。次の例では、runtime.concatstrings
に 530ms が費やされ、各行のコストがリストに表示されます。
最も高コストの呼び出しの Web リストによる可視化。
プロファイルデータを可視化する別の方法は、フレイムグラフです。フレイムグラフを使用すると、特定の祖先パスを移動できるため、コードの特定のセクションを拡大/縮小できます。アップストリーム pprofは、フレイムグラフをサポートしています。
フレイムグラフは、最も高コストのコードパスを見つけるための可視化を提供します。
組み込みのプロファイルに制限されますか?
ランタイムによって提供されるものに加えて、Go ユーザーは pprof.Profile を介してカスタムプロファイルを作成し、既存のツールを使用してそれらを調べることができます。
プロファイラハンドラ(/debug/pprof/…)を別のパスとポートで提供できますか?
はい。net/http/pprof
パッケージはデフォルトでデフォルトの mux にハンドラを登録しますが、パッケージからエクスポートされたハンドラを使用して自分で登録することもできます。
たとえば、次の例では、pprof.Profile ハンドラを :7777 の /custom_debug_path/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 プロセスでアプリケーションのレイテンシをインストルメント化して分析します。
- 長い呼び出しチェーン内の特定の呼び出しのコストを測定します。
- 利用率とパフォーマンスの改善を明らかにします。トレースデータがないと、ボトルネックは必ずしも明らかではありません。
モノリシックシステムでは、プログラムの構成要素から診断データを収集することは比較的容易です。すべてのモジュールは 1 つのプロセス内に存在し、ログ、エラー、その他の診断情報を報告するための共通のリソースを共有します。システムが単一のプロセスを超えて成長し、分散型になるにつれて、フロントエンドの 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などの命令セット拡張の英小文字名です。