寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

goのプロファイラpprofを利用してパフォーマンスを計測する (GAE + ginもあり)

f:id:chidakiyo:20191031154054j:plain

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ステップが必要です。

  1. "github.com/gin-contrib/pprof" をimportする
  2. 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つのケースがあります。

  1. pprofのパスからファイルを取得してからローカルで見る
  2. 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 にアクセスすると以下のようなサマリが確認できます。

f:id:chidakiyo:20191031153812p:plain

ローカルからサーバのプロファイルを確認する

ローカルからサーバのプロファイルを確認する際には、閲覧する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を起動するという意味合いになります。

コマンドを実行ししばらくすると、自動的に適当なブラウザが起動すると思います。

以下のようなグラフを確認することができます。

f:id:chidakiyo:20191031153836p:plain f:id:chidakiyo:20191031153846p:plain f:id:chidakiyo:20191031153855p:plain

heapを確認する

同様にheapを確認したい場合には

go tool pprof -http=":8888" http://localhost:8080/debug/pprof/heap

のように接続先のURLのパスをheapに変更します。

f:id:chidakiyo:20191031153936p:plain

appengineにデプロイする

pprof自体はappengineにデプロイする際でも特に実装上の変更はありません。
注意点としては、app.yamlでgoにマッピングをしているパスに /debug で始まるパスがgoに対してマッピングされているかを確認しましょう。

また、SourceのViewでheapを見た際、ローカルではgoのコードが表示されますが、
appengine上ではgoのコードが表示されないようです。

まとめ

プロファイラを適用した際のオーバヘッドはゼロではないのですが、かなりオーバヘッドが少なくなるように設計されているようなので、もし本番環境で問題などが起きた場合にはこのようなツールを使って一部トラフィックを流すなどして確認ができそうです。

今回は各種Viewの見方などは特に説明しませんでしたが、
パフォーマンスの問題を解決するには必須のツールだと思います。
便利に使って楽しいgoライフを!

参考

Go言語のプロファイリングツール、pprofのWeb UIがめちゃくちゃ便利なので紹介する - Eureka Engineering - Medium

Golangのpprofの使い方【基礎編】 - Carpe Diem