Go言語のトラブルシューティング機能

 Go言語のトラブルシューティング機能

An introduction to some debug features of golang

842515eaf8fbb2dfcc75197e7797dc15?s=128

Satoru Takeuchi

October 12, 2017
Tweet

Transcript

  1. 2.

    2 はじめに • 本書の目的 – Go言語のトラブルシューティング用機能を紹介 • 本書を執筆した動機 – Go言語のトラブルシューティング用機能(デバッガ等)はドキュメン

    トがあまりない – ドキュメントがあっても、様々な場所に情報が分散している – トラブルシューティング機能に特化した情報が一箇所に集約されて いると便利だと判断 • 調査対象にしたGo言語のバージョンは1.5.1 • 調査環境はfedora22/x86_64
  2. 3.
  3. 4.

    4 gdb: core dumpの採取 • 採取に必要な設定 – 以下の環境変数を設定 • GOTRACEBACK=crash

    # プログラムの異常終了時にcoreを生成 – (go固有ではないが) limit –c unlimited – (go固有ではないが) /proc/sys/kernel/core_patternの設定 • [tips]最適化によって変数が消えてしまわないようにする – go buildに`-gcflags “-N -l“`オプションを付加 • 採取方法 – SEGVなどでプログラムを異常終了させる – SIGABRTを送る
  4. 5.

    5 gdb: 解析 • go用の拡張コマンドを使用するために、以下のpythonスクリ プトをロード • (gdb) source $GOROOT/src/runtime/runtime-gdb.py

    • 参考) バージョン1.4.2の当該スクリプトはロード時にエラーが出るため 、動かない • 以下サイトに記載の拡張コマンドを使って解析 https://golang.org/doc/gdb – 注意: 現在のところ、解析できる場合、できない場合がある • 動作中のプロセス(gdb <プログラム名>で実行): 解析可能 • Core dump: 解析不能(理由は不明) – プロセスが死んだはずの箇所で止まっていない – まともにスタックが見られない – goroutineコマンドも正しく動作しない
  5. 6.

    6 gdb: 今後の展望 • 近いうちに改善する見込みはなし • 公式ドキュメント(https://golang.org/doc/gdb)に以下の 記載あり … As

    a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones. Moreover, it is not a priority for the Go project to address these issues, which are difficult. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success. In time, a more Go-centric debugging architecture may be required. …
  6. 7.

    7 Race Detector: 概要 • Race conditionに由来するバグ検出用のツール – https://golang.org/doc/articles/race_detector.html •

    プログラム内に競合が存在する場合に警告を出す • 使い方 – -raceオプションを付けてビルド、実行 • $ go run –race foo.go • $ go build –race && ./foo • 注意: この機能を使用すると、メモリ使用量が5-10倍、 実行時間が2-20倍になるらしい(上記リンクより)
  7. 8.

    8 Race Detector: 使い方 $ go run -race race.go ==================

    WARNING: DATA RACE Write by goroutine 4: main.func·001() /home/sat/gopath/src/test/race/race.go:7 +0x43 Previous write by main goroutine: main.main() /home/sat/gopath/src/test/race/race.go:10 +0x176 Goroutine 4 (running) created at: main.main() /home/sat/gopath/src/test/race/race.go:9 +0x166 ================== Found 1 data race(s) exit status 66 1 package main 2 3 func main() { 4 c := make(chan bool) 5 i := 0 6 go func() { 7 i = 1 8 c <- true 9 }() 10 i = 2 11 <- c 12} この時点でiが1か2か不定。 言い換えると、7行目と10行目の どちらが先に実行されるか不定 7行目と10行目の競合を検出
  8. 9.

    9 godebug: 概要 • Go専用のデバッガ – https://github.com/mailgun/godebug • 使い方は上記リンク先のREADME.mdに記載あり –

    “Installation”の欄にソース取得コマンドしか記載が無いが、実 際にgodebugプログラムを使用するには、以下コマンドの実 行が必要 1. go get github.com/mailgun/godebug 2. cd $GOPATH/src/github.com/mailgun/godebug 3. go install # これで$GOPATH/bin以下にgodebugがインストールされる • 本書執筆時点では機能が非常に少ない(gdb未満) • まだプロジェクトが新しいので、将来に期待か
  9. 10.

    10 godebug: gdbとの機能比較 godebug gdb Breakpointの設定 ▲ ソースに直接埋め込む必要あり ◦ Core

    dumpの解析 × × Goroutineの考慮 × ◦ Step実行/next実行 ◦ ◦ Continue ◦ ◦ ソース表示 ◦ ◦ 変数の表示 ◦ ◦
  10. 11.

    11 Heapdump • Runtime/debugのWriteHeapDump()関数 – https://golang.org/pkg/runtime/debug • プログラムのheapの中身を指定したファイルにダンプ • 将来的には役立ちそうだが、現在はまともに使えない

    – 出力はバイナリ形式だが、テキスト形式にして読むツールが存 在しない – Goのマイナーバージョンごとに形式が異なるものの、Go1.5のバ イナリ形式はドキュメント化されていない(ドキュメントが存 在するのは1.3, 1.4のみ) • https://github.com/golang/go/wiki/heapdump13 • https://github.com/golang/go/wiki/heapdump14
  11. 12.

    12 pprof: 概要 • go 言語プログラム用のプロファイラ • CPUとメモリのプロファイル情報を採取可能 • プロファイル用APIが含まれるパッケージ

    – https://golang.org/pkg/runtime/pprof/ • 使用例 – http://blog.golang.org/profiling-go-programs • 基本的な使いかた – プログラム内からプロファイル用APIを呼び出し、プロファイル情報を所 定ファイルに採取 • 所定のビルドオプション(gccの-pgのような)を付ければ勝手にプロファイル情報 を採取してくれるわけではないので面倒 – 採取したプロファイル情報を下記コマンドによって可視化し、分析 • $ go tool pprof <プログラム名> <手順1で採取したプロファイル用ファイル>
  12. 13.

    13 pprof: サンプルコード package main import ( "os" "log" "runtime/pprof"

    ) func bar() { for i := 0; i < 1000000; i++ { } } func foo() { for i := 0; i < 10000000; i++ { if i % 1000 == 0 { bar() } } } func main() { f, err := os.Create("cpu.pprof") if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() foo() } CPUプロファイル 情報の採取開始 main終了時に CPUプロファイル 情報の採取終了 prof.go: CPUプロファイル 情報採取用ファイルの作成
  13. 14.

    14 pprof: 実行&プロファイリング $ ls prof.go $ go build $

    ./pprof $ ls cpu.pprof prof prof.go # プロファイル用のファイル、cpu.pprofが生成されている $ go tool pprof prof cpu.pprof Entering interactive mode (type "help" for commands) (pprof) top 9.79s of 9.80s total (99.90%) Dropped 2 nodes (cum <= 0.05s) flat flat% sum% cum cum% 9.74s 99.39% 99.39% 9.74s 99.39% main.bar 0.05s 0.51% 99.90% 9.79s 99.90% main.foo 0 0% 99.90% 9.79s 99.90% main.main 0 0% 99.90% 9.79s 99.90% runtime.goexit 0 0% 99.90% 9.79s 99.90% runtime.main ほとんどの実行時間は (99.39%)はmain.bar()で消費 ほんのわずか(0.51%)、 main.foo()で消費 残りはゴミ みたいなもの
  14. 15.

    15 結論 • Go言語のトラブルシューティング機能はそれな りに充実している • ただし、Core dumpを解析できるデバッガが無 いのが痛い –

    個人的にはこれがGo言語の一番の弱点だと思う – 実運用でプロセスが異常終了した場合にどうやって 原因を特定するのか。スタックトレースだけでは心 もとない