Go 1.10でGolangのプロファイリングツール pprof のWebUIが入った話

開発Div 喜村です。
2/18にGo 1.10がリリースされましたね!
Go 1.10 is released - The Go Blog

個人的には Go 1.11 で入るvgoのほうが楽しみなんですが、
それはそれとして、

  • Pprof WebUIの go tool pprofへの採用
  • encoding/json/#Decoder.DisallowUnknownFields
  • strings/#Builder

あたりが Go1.10の変更点でテンションが上がる(よく使うので嬉しい)ポイントでした。 というわけで、今回はpprofの話をします。

そもそも pprof とは?

pprof is a tool for visualization and analysis of profiling data

https://github.com/google/pprof:title:w300

プロファイリングデータを可視化・分析するツールです。
「この処理にこれくらいの時間がかかっていて、これくらいのメモリを使用しており〜」のような プロファイリングデータを程よく visualization してくれるツールになります。

集められるデータは以下に記載されている通り、

  • heap profile
    • /debug/pprof/heap
  • 30-second CPU profile
    • /debug/pprof/profile
  • goroutine blocking profile, after calling runtime.SetBlockProfileRate in your program
    • /debug/pprof/block
  • collect a 5-second execution trace
    • /debug/pprof/trace?seconds=5
  • holders of contended mutexes, after calling runtime.SetMutexProfileFraction in your program
    • /debug/pprof/mutex

と、よく問題になりそうなパターンのデータはだいたい集められます。

pprof - The Go Programming Language

データを集めるための方法も、
「"net/http/pprof"をimportして、main関数で適当なポートをListenAndServeするのみ」
と大変お手軽です。

WebUIって?

pprofのオプションには、20種類以上の Output formatsが指定できます。
その中でも使いやすいのが、今回 Go 1.10において go tool pprofに取り込まれた -http になります。
( github.com/google/pprof を go getすることで、以前からWebUIを使用することは可能でした。)

インタラクティブにプロファイリングデータを閲覧できるのは、他の Output formatにはない魅力です。
実際に出力したものは以下のようになります。

f:id:jkimura:20180418110321p:plain

手順

では実際に、検証用プログラムを用いてpprofのWebUIを使ってみましょう。 今回検証で用いるプログラムは以下です。

client/main.go

package main

import (
    "fmt"
    "net"
    "net/http"
    _ "net/http/pprof"
)

func fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
    l, err := net.Listen("tcp", ":52362")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Listening on %s\n", l.Addr())
    go http.Serve(l, nil)

    for {
        fibonacci(30)
        fibonacci(40)
        fibonacci(50)
    }
}

フィボナッチ数を求める処理を無限ループすることによって、CPUに負荷をかけます。

ちなみに、

l, err := net.Listen("tcp", ":0")

とすれば、空いているランダムなポートでListenすることができます。 *1

実際に go tool pprof を使っていきます。

# localhost:8888を使って、localhost:52362のCPUプロファイリング結果を表示する
# {$PPROF_TMPDIR}は $HOME/pprof(default)を指す
$ go tool pprof -http=":8888" localhost:52362
Fetching profile over HTTP from http://localhost:54419/debug/pprof/profile
Saved profile in {$PPROF_TMPDIR}/pprof.samples.cpu.010.pb.gz
Failed to execute dot. Is Graphviz installed?
exec: "dot": executable file not found in $PATH

# 怒られるので pprofの前提条件になっている graphviz を installする
# (Macで検証したのでbrew install、Linux, Windowsでもインストール可能)
$ brew install graphviz

#再度チャレンジ
$ go tool pprof -http=: localhost:52362
Fetching profile over HTTP from http://localhost:60477/debug/pprof/profile
Saved profile in {$PPROF_TMPDIR}/pprof.samples.cpu.011.pb.gz

ブラウザが立ち上がり、以下のような画面が表示されます。 f:id:jkimura:20180418110121p:plain:w300

左上「View」の「Graph」画面が初期表示ですので、「View」を選択することによって表示形式を切り替えることができます。
その中でも便利なのが「Source」で、以下のように実装のどの部分がボトルネックになっているかを見ることができます。 f:id:jkimura:20180419093556p:plain

「Search regexp」の入力欄を使えば、どの画面でも正規表現での検索が可能です。

まとめ

他言語では一手間かかりそうなプロファイリングをここまで手軽にでき、かつWebUIというインタラクティブな仕組みで閲覧できるのはGolangの強みですね。
go tool pprofは、github.com/google/pprof の 2017-11-08 までの更新分しか取り込まれていないので、github.com/google/pprofを go get して使うことにより更に機能が追加されたpprofを使うことができます。 ( -http Optionにポート番号を指定しなくてよくなったり、Graphより使いやすいFlame Graphが見れたり などなど...)
Golangを使っている場合は、pprof用にポートをListenしておくと割と便利です。
Golangのサーバで問題が起こって実装が怪しいけどソースコードからでは原因が特定できない時や、無茶なパフォーマンス改善を求められた時は本当に強い味方になってくれますので、これを機にぜひ使ってみてください!

*1:If the port in the address parameter is empty or "0", as in "127.0.0.1:" or "[::1]:0", a port number is automatically chosen.

net - The Go Programming Language