tcpdpのprobeモードで、CPUが100%に張り付くという問題(結果、原因としてはしょうもないミス)がありました。
機能としては正しく動いているので、単純にテストケースを増やしてもどうにもならず、pprofを使って原因となっている箇所を絞り込んでみました。
本エントリはその記録です。なお、原因特定の問題としてはおそらく初級レベルだと思います(わからない)。
現状把握
tcpdp probe
は tcpdump のようにインターフェースに流れるパケットをキャプチャするコマンドです。
ところが、以下のように実行したら、まだパケットを流していないのにCPUが100%に張り付きます。
$ sudo tcpdp probe -c t.toml Password: 2018-09-30T00:40:42.199+0900 info Starting probe. interface: lo0, target: 33306 2018-09-30T00:40:42.201+0900 info Select dumper mysql.
$ sudo ps au USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND root 69915 100.3 0.2 4402600 36692 s003 R+ 12:14AM 5:17.36 ./tcpdp probe -c t.toml root 43796 0.3 0.1 4405812 8708 s002 S+ 10:44PM 0:25.13 ./tcpdp probe -c t.toml [...]
net/http/pprof を設置する
とりあえず net/http/pprof を設置します。
$ git diff diff --git a/main.go b/main.go index bc38d4c..2a0119b 100644 --- a/main.go +++ b/main.go @@ -20,8 +20,19 @@ package main -import "github.com/k1LoW/tcpdp/cmd" +import ( + "log" + "net/http" + _ "net/http/pprof" + "runtime" + + "github.com/k1LoW/tcpdp/cmd" +) func main() { + runtime.SetBlockProfileRate(1) + go func() { + log.Println(http.ListenAndServe("0.0.0.0:6060", nil)) + }() cmd.Execute() }
参考URL
golang の net/http/pprof を触ってみたメモ - sonots:blog
go tool pprof でCPUのプロファイルを見る
net/http/pprof
を設置したので、もう一度 tcpdp probe
を実行します。そうすると http://localhost:6060/debug/pprof/
でプロファイル情報のページが生成されます。
そのプロファイルページに go tool pprof
でアタッチして、CPUのプロファイルを見てみます。
localhost:6060
で起動している pprof のプロファイルページの /debug/pprof/profile
に go tool pprof
でアタッチして、top
で確認してみます。
$ go tool pprof -seconds 5 http://localhost:6060/debug/pprof/profile Fetching profile over HTTP from http://localhost:6060/debug/pprof/profile?seconds=5 Please wait... (5s) Saved profile in /Users/k1low/pprof/pprof.samples.cpu.007.pb.gz Type: cpu Time: Sep 29, 2018 at 12:54am (JST) Duration: 5.14s, Total samples = 4.40s (85.59%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 3870ms, 87.95% of 4400ms total Dropped 10 nodes (cum <= 22ms) Showing top 10 nodes out of 21 flat flat% sum% cum cum% 1440ms 32.73% 32.73% 3550ms 80.68% runtime.selectgo 👈 480ms 10.91% 43.64% 480ms 10.91% runtime.unlock 450ms 10.23% 53.86% 450ms 10.23% runtime.lock 360ms 8.18% 62.05% 360ms 8.18% runtime.cputicks 240ms 5.45% 67.50% 720ms 16.36% runtime.selunlock 240ms 5.45% 72.95% 240ms 5.45% sync.(*Mutex).Unlock 220ms 5.00% 77.95% 220ms 5.00% sync.(*Mutex).Lock 160ms 3.64% 81.59% 160ms 3.64% runtime.(*hchan).sortkey (inline) 140ms 3.18% 84.77% 4350ms 98.86% github.com/k1LoW/tcpdp/reader.(*PacketReader).ReadAndDump 👈 140ms 3.18% 87.95% 140ms 3.18% runtime.duffzero
あああ、、、もう特定できてしましましたね。
runtime.selectgo
が実行時間 (flat)では多いですが、関数内の呼び出しを含めた実行時間 (cum) をみると github.com/k1LoW/tcpdp/reader.(*PacketReader).ReadAndDump
が支配的です。
この関数の中で何かをやらかしているのは明白です。
参考URL
Go pprof 入門編 (CPU Profile とコマンドラインツール) : KLabGames Tech Blog
go tool pprof -http を使ってWebUI上でグラフで確認する
ここで終わってもそっけないので、今度は pprof の WebUIを使ってグラフで確認してみます。
localhost:6060
で起動している pprof のプロファイルページにアタッチして 8888
で pprof の WebUIを立ち上げます。
$ go tool pprof -http=":8888" localhost:6060
するとWebページが開いて、グラフを確認することができます。
あああ、ReadAndDumpさん。。。
参考URL
Go 1.10でGolangのプロファイリングツール pprof のWebUIが入った話 - Keepdata Blog
go-torch を使ってFlame Graphで確認する
懲りずにさらに、go-torch を使ってFlame Graphで確認してみます。
まず、準備として go-torch と FlameGraph を落としておきます。
$ go get github.com/uber/go-torch $ git clone https://github.com/brendangregg/FlameGraph
localhost:6060
で起動している pprof のプロファイルページにアタッチしてFlame Graphのsvgファイルを生成します。
$ PATH=$PATH:/path/to/FlameGraph go-torch --time 5 --url http://localhost:6060/debug/pprof/profile
。。。。ReadAndDumpで決定ですね。
参考URL
原因特定
原因はReadAndDumpで実行しているforの中のselect句で、「何もしないdefault:
が存在している」ことで、ビジーループになっていたことでした。しょうもないバグ。
default:
を取り除き、最終的にpprof のプロファイル結果も以下のようにスッキリしました。
$ go tool pprof -seconds 5 http://localhost:6060/debug/pprof/profile Fetching profile over HTTP from http://localhost:6060/debug/pprof/profile?seconds=5 Please wait... (5s) Saved profile in /Users/k1low/pprof/pprof.samples.cpu.013.pb.gz Type: cpu Time: Sep 29, 2018 at 2:27am (JST) Duration: 5s, Total samples = 20ms ( 0.4%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 20ms, 100% of 20ms total flat flat% sum% cum cum% 20ms 100% 100% 20ms 100% github.com/k1LoW/tcpdp/vendor/github.com/google/gopacket/pcap.(*Handle).getNextBufPtrLocked.func1 0 0% 100% 20ms 100% github.com/k1LoW/tcpdp/vendor/github.com/google/gopacket.(*PacketSource).NextPacket 0 0% 100% 20ms 100% github.com/k1LoW/tcpdp/vendor/github.com/google/gopacket.(*PacketSource).packetsToChannel 0 0% 100% 20ms 100% github.com/k1LoW/tcpdp/vendor/github.com/google/gopacket/pcap.(*Handle).ReadPacketData 0 0% 100% 20ms 100% github.com/k1LoW/tcpdp/vendor/github.com/google/gopacket/pcap.(*Handle).getNextBufPtrLocked
該当の修正Pull Requestはこちら。
まとめ
今回はpprofを使ってCPU100%張り付きの原因特定をしてみました。
自分は、こういう「計測」には慣れていないので、これからも素振りをしていこうと思います。