Diagnostics

はじめに

Go エコシステムは、Go プログラムにおけるロジックやパフォーマンスの問題を診断するための、幅広い API とツールを提供しています。このページでは、利用可能なツールをまとめ、Go ユーザーが特定の問題に最適なツールを選択するのに役立ちます。

診断ソリューションは、以下のグループに分類できます。

注:一部の診断ツールは相互に干渉する可能性があります。たとえば、正確なメモリプロファイリングは CPU プロファイルを歪ませ、ゴルーチンブロックプロファイリングはスケジューラトレースに影響を与えます。より正確な情報を得るには、ツールを個別に使用してください。

プロファイリング

プロファイリングは、コストの高いコードや頻繁に呼び出されるコードのセクションを特定するのに役立ちます。Go ランタイムは、pprof 可視化ツールで期待される形式のプロファイリングデータを提供します。プロファイリングデータは、go test を介したテスト中、または net/http/pprof パッケージから利用できるエンドポイントを介して収集できます。ユーザーは、プロファイリングデータを収集し、pprof ツールを使用して上位のコードパスをフィルタリングおよび可視化する必要があります。

runtime/pprof パッケージが提供する定義済みプロファイル

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 は、一定期間内のランタイムイベントをトレースするための実行トレーサーも提供します。

トレーシングにより、次のことが可能になります。

モノリシックシステムでは、プログラムの構成要素から診断データを収集することは比較的簡単です。すべてのモジュールは単一のプロセス内で動作し、ログ、エラー、その他の診断情報を報告するために共通のリソースを共有します。システムが単一のプロセスを超えて分散し始めると、フロントエンドの Web サーバーからすべてのバックエンドまで、応答がユーザーに返されるまで呼び出しを追跡することが難しくなります。ここで、分散トレーシングが本番システムの計測と分析に大きな役割を果たします。

分散トレーシングは、ユーザーリクエストのライフサイクル全体にわたるレイテンシを分析するためにコードを計測する方法です。システムが分散され、従来のプロファイリングツールやデバッグツールがスケーリングしない場合、分散トレーシングツールを使用してユーザーリクエストや RPC のパフォーマンスを分析したいと思うかもしれません。

分散トレーシングにより、次のことが可能になります。

Go エコシステムは、トレーシングシステムごとのさまざまな分散トレーシングライブラリと、バックエンドに依存しないライブラリを提供しています。

各関数呼び出しを自動的にインターセプトしてトレースを作成する方法はありますか?

Go は、すべての関数呼び出しを自動的にインターセプトしてトレーススパンを作成する方法を提供していません。スパンを作成、終了、注釈付けするには、コードを手動で計測する必要があります。

Go ライブラリでトレースヘッダーを伝播するにはどうすればよいですか?

context.Context でトレース識別子とタグを伝播できます。業界ではまだ標準的なトレースキーや共通のトレースヘッダー表現はありません。各トレーシングプロバイダーは、Go ライブラリで伝播ユーティリティを提供する必要があります。

標準ライブラリやランタイムからの他の低レベルイベントをトレースに含めることはできますか?

標準ライブラリとランタイムは、低レベルの内部イベントを通知するための追加 API をいくつか公開しようとしています。たとえば、httptrace.ClientTrace は、送信リクエストのライフサイクルにおける低レベルイベントを追跡するための API を提供します。ランタイム実行トレーサーから低レベルのランタイムイベントを取得し、ユーザーが独自のユーザーイベントを定義して記録できるようにする取り組みが進行中です。

デバッグ

デバッグは、プログラムが誤動作する理由を特定するプロセスです。デバッガーを使用すると、プログラムの実行フローと現在の状態を理解できます。デバッグにはいくつかのスタイルがありますが、このセクションでは、プログラムへのデバッガーのアタッチとコアダンプデバッグにのみ焦点を当てます。

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 プログラムの全体的な健全性とパフォーマンスをよりよく理解することができます。頻繁に監視される統計と状態の一部を以下に示します。

実行トレーサー

Go には、幅広いランタイムイベントをキャプチャするランタイム実行トレーサーが付属しています。スケジューリング、システムコール、ガベージコレクション、ヒープサイズ、その他のイベントはランタイムによって収集され、go tool trace による可視化に利用できます。実行トレーサーは、レイテンシと利用率の問題を検出するためのツールです。CPU がどの程度利用されているか、およびネットワークまたはシステムコールがゴルーチンにとってプリエンプションの原因となっている時期を調べることができます。

トレーサーは次の用途に役立ちます。

ただし、過剰なメモリ使用量や CPU 使用量の原因を分析するなど、ホットスポットを特定するのにはあまり適していません。それらに対処するには、まずプロファイリングツールを使用してください。

上記では、go tool trace の可視化により、実行は順調に開始された後、逐次化されたことが示されています。これは、ボトルネックを引き起こす共有リソースに対するロック競合がある可能性を示唆しています。

ランタイムトレースを収集して分析するには、go tool trace を参照してください。

GODEBUG

GODEBUG 環境変数が適切に設定されている場合、ランタイムもイベントと情報を出力します。

GODEBUG 環境変数を使用すると、標準ライブラリとランタイムでの命令セット拡張の使用を無効にできます。