はじめに

Goエコシステムは、Goプログラムの論理およびパフォーマンスの問題を診断するための大規模なAPIおよびツールのスイートを提供します。このページでは、利用可能なツールを要約し、Goユーザーが特定の問題に対して適切なツールを選択するのを助けます。

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

  • プロファイリング: プロファイリングツールは、Goプログラムの複雑さやコスト(メモリ使用量や頻繁に呼び出される関数など)を分析し、Goプログラムの高コストなセクションを特定します。
  • トレース: トレースは、呼び出しやユーザーリクエストのライフサイクル全体にわたるレイテンシを分析するためにコードを計測する方法です。トレースは、システム全体のレイテンシに対して各コンポーネントがどれだけ寄与しているかの概要を提供します。トレースは複数のGoプロセスにまたがることがあります。
  • デバッグ: デバッグは、Goプログラムを一時停止し、その実行を調べることを可能にします。プログラムの状態とフローはデバッグによって確認できます。
  • ランタイム統計とイベント: ランタイム統計とイベントの収集と分析は、Goプログラムの健康状態の高レベルの概要を提供します。メトリクスのスパイク/ディップは、スループット、利用率、およびパフォーマンスの変化を特定するのに役立ちます。

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

プロファイリング

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

runtime/pprofパッケージが提供する事前定義されたプロファイル:

  • cpu: CPUプロファイルは、プログラムがCPUサイクルを積極的に消費している間にどこで時間を費やしているかを決定します(スリープ中やI/O待機中ではなく)。
  • heap: ヒーププロファイルは、メモリ割り当てサンプルを報告します。現在および過去のメモリ使用量を監視し、メモリリークをチェックするために使用されます。
  • threadcreate: スレッド作成プロファイルは、新しいOSスレッドの作成につながるプログラムのセクションを報告します。
  • goroutine: goroutineプロファイルは、すべての現在のgoroutineのスタックトレースを報告します。
  • block: ブロックプロファイルは、goroutineが同期プリミティブ(タイマーチャンネルを含む)を待っている間にどこでブロックされるかを示します。ブロックプロファイルはデフォルトでは有効になっていません。runtime.SetBlockProfileRateを使用して有効にしてください。
  • mutex: ミューテックスプロファイルは、ロック競合を報告します。ミューテックス競合のためにCPUが完全に利用されていないと思われる場合は、このプロファイルを使用してください。ミューテックスプロファイルはデフォルトでは有効になっていません。runtime.SetMutexProfileFractionを参照して有効にしてください。

Goプログラムをプロファイリングするために使用できる他のプロファイラは何ですか?

Linuxでは、perfツールを使用してGoプログラムをプロファイリングできます。Perfはcgo/SWIGコードやカーネルをプロファイリングおよびアンワインドできるため、ネイティブ/カーネルのパフォーマンスボトルネックに関する洞察を得るのに役立ちます。macOSでは、Instrumentsスイートを使用してGoプログラムをプロファイリングできます。

本番サービスをプロファイリングできますか?

はい。本番環境でプログラムをプロファイリングすることは安全ですが、一部のプロファイル(例: CPUプロファイル)を有効にするとコストがかかります。パフォーマンスの低下が見られることを期待する必要があります。パフォーマンスペナルティは、本番環境でプロファイラーをオンにする前に、プロファイラーのオーバーヘッドを測定することで推定できます。

定期的に本番サービスをプロファイリングしたい場合があります。特に、単一プロセスの多くのレプリカがあるシステムでは、定期的にランダムなレプリカを選択することが安全なオプションです。生産プロセスを選択し、X秒間プロファイリングし、Y秒ごとに結果を保存して可視化および分析し、その後定期的に繰り返します。結果は手動および/または自動でレビューされ、問題を見つけることができます。プロファイルの収集は互いに干渉する可能性があるため、一度に単一のプロファイルのみを収集することをお勧めします。

プロファイリングデータを可視化するための最良の方法は何ですか?

Goツールは、go tool pprofを使用してプロファイルデータのテキスト、グラフ、およびcallgrindの可視化を提供します。Goプログラムのプロファイリングを読んで、実際にそれらを確認してください。

診断(Diagnostics) - img1

最も高コストな呼び出しのリストをテキストとして表示。

診断(Diagnostics) - img2

最も高コストな呼び出しの可視化をグラフとして表示。

Webリストビューは、HTMLページでソースの高コストな部分を行ごとに表示します。次の例では、530msがruntime.concatstringsに費やされ、各行のコストがリストに表示されます。

診断(Diagnostics) - img3

最も高コストな呼び出しの可視化をウェブリストとして表示。

プロファイルデータを可視化する別の方法は、フレームグラフです。フレームグラフを使用すると、特定の系譜パスに移動できるため、特定のコードセクションをズームイン/アウトできます。upstream pprofはフレームグラフをサポートしています。

診断(Diagnostics) - img4

フレームグラフは、最も高コストなコードパスを特定するための可視化を提供します。

組み込みプロファイルに制限されますか?

ランタイムが提供するものに加えて、Goユーザーはpprof.Profileを介してカスタムプロファイルを作成し、既存のツールを使用してそれらを調べることができます。

プロファイラハンドラ(/debug/pprof/…)を別のパスとポートで提供できますか?

はい。net/http/pprofパッケージはデフォルトでそのハンドラをデフォルトのマルチプレクサに登録しますが、パッケージからエクスポートされたハンドラを使用して自分で登録することもできます。

たとえば、次の例では、:7777で/custome_debug_path/profileでpprof.Profileハンドラを提供します:

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "net/http/pprof"
  6. )
  7. func main() {
  8. mux := http.NewServeMux()
  9. mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
  10. log.Fatal(http.ListenAndServe(":7777", mux))
  11. }

トレース

トレースは、呼び出しのチェーンのライフサイクル全体にわたるレイテンシを分析するためにコードを計測する方法です。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プログラムでどの程度機能しますか?

  1. ``````bash
  2. $ go build -gcflags=all="-N -l"
  3. `

改善の一環として、Go 1.10では新しいコンパイラフラグ-dwarflocationlistsが導入されました。このフラグは、デバッガが最適化されたバイナリで機能するのを助ける位置リストを追加するようにコンパイラに指示します。次のコマンドは、最適化を行いながらDWARF位置リストを持つパッケージをビルドします:

  1. $ go build -gcflags="-dwarflocationlists=true"

推奨されるデバッガユーザーインターフェースは何ですか?

Delveとgdbの両方がCLIを提供していますが、ほとんどのエディタ統合やIDEはデバッグ専用のユーザーインターフェースを提供しています。

Goプログラムでポストモーテムデバッグを行うことは可能ですか?

コアダンプファイルは、実行中のプロセスのメモリダンプとそのプロセスの状態を含むファイルです。これは主にプログラムのポストモーテムデバッグと、実行中の状態を理解するために使用されます。これらの2つのケースは、コアダンプのデバッグをポストモーテムおよび本番サービスの分析に役立つ診断ツールにします。Goプログラムからコアファイルを取得し、DelveまたはGDBを使用してデバッグすることが可能です。手順についてはコアダンプデバッグページを参照してください。

ランタイム統計とイベント

ランタイムは、ユーザーがランタイムレベルでパフォーマンスと利用率の問題を診断するための内部イベントの統計と報告を提供します。

ユーザーはこれらの統計を監視して、Goプログラムの全体的な健康状態とパフォーマンスをよりよく理解できます。頻繁に監視される統計と状態のいくつか:

  • runtime.ReadMemStatsは、ヒープ割り当てとガベージコレクションに関連するメトリクスを報告します。メモリ統計は、プロセスが消費しているメモリリソースの量、プロセスがメモリをうまく利用できるかどうか、メモリリークをキャッチするために役立ちます。
  • debug.ReadGCStatsは、ガベージコレクションに関する統計を読み取ります。GCの一時停止にどれだけのリソースが費やされているかを確認するのに役立ちます。また、ガベージコレクタの一時停止のタイムラインと一時停止時間のパーセンタイルを報告します。
  • debug.Stackは、現在のスタックトレースを返します。スタックトレースは、現在実行中のgoroutineの数、何をしているか、ブロックされているかどうかを確認するのに役立ちます。
  • debug.WriteHeapDumpは、すべてのgoroutineの実行を一時停止し、ヒープをファイルにダンプすることを許可します。ヒープダンプは、特定の時点でのGoプロセスのメモリのスナップショットです。すべての割り当てられたオブジェクトやgoroutine、ファイナライザなどが含まれます。
  • runtime.NumGoroutineは、現在のgoroutineの数を返します。この値は、十分なgoroutineが利用されているかどうか、またはgoroutineリークを検出するために監視できます。

実行トレーサー

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

トレーサーは次のことに役立ちます:

  • goroutineがどのように実行されるかを理解する。
  • GCの実行など、いくつかのコアランタイムイベントを理解する。
  • 不十分に並列化された実行を特定する。

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

診断(Diagnostics) - img5

上記の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は、指定された命令セット拡張からの命令の使用を無効にします。
    拡張は、sse41やavxなどの命令セット拡張の小文字名です。