pprof とは
Go言語のプロファイリングツール。
Web UIでフレームグラフや、関数の実行時間のグラフを見ることができる
通常のpprof利用方法
pprofの使い方として2つのパターンがあります。
runtime/pprof
: ファイルにプロファイル情報を出力するnet/http/pprof
: httpアクセスでプロファイル情報を取得する事ができる
ちょっと分かりづらいですね
runtime/pprof
の場合には、go製のツールなどで利用することが想定されていると思います。
mainの実行時間が短く、mainのdeferでファイルを出力するような記述をします。
net/http/pprof
の場合には、httpサーバとして動作することが想定されており、
pprofのimportを行うことでプロファイルを取得するためのハンドラが自動的に実装されます。
実装されたハンドラに対して外部からリクエストを行うことで、動作しているwebアプリケーションのプロファイル情報を見ることができます。
net/http/pprof の適用方法(net/http 利用時)
net/http
利用時には "net/http/pprof"
をimportするだけでpprofを実行する準備ができます。
その場合のmain関数は以下のようになります
package main import ( "fmt" "log" "net/http" _ "net/http/pprof" ) func main() { http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusOK) fmt.Fprint(writer, "hello hogehoge.") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
net/http/pprof の適用方法(gin 利用時)
gin
を利用している場合には net/http のときのような _ "net/http/pprof"
をimportするだけでは動作しません。
ginで利用する場合には以下の2ステップが必要です。
"github.com/gin-contrib/pprof"
をimportするpprof.Register(route)
を実行する
です。
main関数は以下のようになります。
package main import ( "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" "net/http" ) func main() { route := gin.Default() pprof.Register(route) route.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "hello hogehoge.")}) http.Handle("/", route) route.Run(":8080") }
プロファイルの確認方法
プロファイルの確認方法には2つのケースがあります。
- pprofのパスからファイルを取得してからローカルで見る
- goのツールで直接パスを指定してweb UI上で見る
今回は 2 の方法をメインに確認してみます
登録されたハンドラ一覧
gin をデプロイした際に登録されたハンドラがログとして出力されたものが以下です
[GIN-debug] GET /debug/pprof/ --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/cmdline --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/profile --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] POST /debug/pprof/symbol --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/symbol --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/trace --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/allocs --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/block --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/goroutine --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/heap --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/mutex --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET /debug/pprof/threadcreate --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers) [GIN-debug] GET / --> main.main.func1 (3 handlers)
結構な数のハンドラが利用できるようですが、 /debug/pprof
にアクセスすると以下のようなサマリが確認できます。
ローカルからサーバのプロファイルを確認する
ローカルからサーバのプロファイルを確認する際には、閲覧するPCにgoと、graphvizがインストールされている必要があります
それぞれMacであれば
brew install go brew install graphviz
のような形でインストールが可能です。
profileを確認する
まずはprofileを確認してみます
go tool pprof -http=":8888" http://localhost:8080/debug/pprof/profile
のようなコマンドをMac上で実行します。
コマンドの意味としては、 http://localhost:8080/debug/pprof/profile
から情報を出力し、ローカルの 8888
ポートでプロファイラのWeb UIを起動するという意味合いになります。
コマンドを実行ししばらくすると、自動的に適当なブラウザが起動すると思います。
以下のようなグラフを確認することができます。
heapを確認する
同様にheapを確認したい場合には
go tool pprof -http=":8888" http://localhost:8080/debug/pprof/heap
のように接続先のURLのパスをheapに変更します。
appengineにデプロイする
pprof自体はappengineにデプロイする際でも特に実装上の変更はありません。
注意点としては、app.yamlでgoにマッピングをしているパスに /debug
で始まるパスがgoに対してマッピングされているかを確認しましょう。
また、SourceのViewでheapを見た際、ローカルではgoのコードが表示されますが、
appengine上ではgoのコードが表示されないようです。
まとめ
プロファイラを適用した際のオーバヘッドはゼロではないのですが、かなりオーバヘッドが少なくなるように設計されているようなので、もし本番環境で問題などが起きた場合にはこのようなツールを使って一部トラフィックを流すなどして確認ができそうです。
今回は各種Viewの見方などは特に説明しませんでしたが、
パフォーマンスの問題を解決するには必須のツールだと思います。
便利に使って楽しいgoライフを!
参考
Go言語のプロファイリングツール、pprofのWeb UIがめちゃくちゃ便利なので紹介する - Eureka Engineering - Medium