シリーズ「コロンボ」のフレームgo-to-pprofパッケージは多くの場合、プロセッサまたはメモリのプロファイルに使用されますが、独自のカスタムプロファイルを作成する機能について誰もが知っているわけではありません。 これらは、リソースリークの検出や、困難なコールの不正使用の追跡に役立ちます。
プロフィール
pprofドキュメントから:
プロファイルは、イベントにつながった呼び出しの順序を示すスタックトレースのセットです。 たとえば、メモリ割り当て。 パッケージは、独自のプロファイルを作成および管理できます。 最も一般的な理由は、明示的な閉鎖を必要とするリソース(ファイル、ネットワーク接続)を追跡することです。
プロファイルのもう1つの潜在的な用途は、明示的に閉じる必要があるものではなく、呼び出しの実行をブロックできる関数またはリソースを追跡することです。そのような呼び出しの場所、数などを理解する必要があります。 プロファイルは、スタックトレースが役立つ場合に特に役立ちます。主な利点は、goツールpprofと簡単に統合できることです。
プロファイルの注意事項
プロファイルパッケージは、ほとんどすべての誤用でパニックになります。 例:
- 既に存在するプロファイルを作成する
- プロファイルを同じオブジェクトに2回バインドする
- 以前に追加されていないスタックを削除する
- すでに削除されたスタックを削除する
そのような状況は避けてください。
プロファイル作成
var libProfile *pprof.Profile func init() { profName := "my_experiment_thing" libProfile = pprof.Lookup(profName) if libProfile == nil { libProfile = pprof.NewProfile(profName) } }
同じ名前の2つの異なるプロファイルを作成することはできないので、それらをinit()で作成することは理にかなっています。 単一のラインプロファイルを作成することもできます。
// Warning: /vendor panic possibilities var panicProfile = pprof.NewProfile("this_unique_name")
しかし、そのような使用には、選択した名前がすでに使用できるという事実があふれています。 たとえそれが一意であると確信していても、ライブラリが数回
ベンダーである場合(まったく可能です)、アプリケーションは起動時にパニックに陥ります。 なぜなら プロファイルはスレッドセーフであり、init()-関数は1回実行されます。チェックと作成のアプローチが正しいアプローチです。
Godocは、一意の名前を作成する一般的な方法は「
インポート/パス」プレフィックスを使用することであると述べて
いますが、アドバイスに従うとcmd / pprofの既知のバグに遭遇することになります 。 そのため、パッケージのパスと名前を使用しますが、次の文字のみを使用してください[a-zA-Z0-9_]。
プロファイルの使用
type someResource struct { *os.File } func MustResource() *someResource { f, err := os.Create(...) if err != nil { panic(err) } r := &someResource{f} libProfile.Add(r, 1) return r } func (r *someResource) Close() error { libProfile.Remove(r) return r.File.Close() }
パッケージの主な機能は、
追加と
削除です。 この例では、閉じられていないすべての作成済みリソースを監視するため、リソースを作成するときにスタックトレースを追加し、閉じるときに削除します。 Add関数には、呼び出しごとに一意のオブジェクトが必要なので、リソース自体をキーとして使用できます。 適切なキーがない場合もあります。その場合、ダミーバイトを作成してそのアドレスを使用できます。
func usesAResource() { pprofKey := new(byte) libProfile.Add(pprofKey, 1) defer libProfile.Remove(pprofKey) // .... }
pprofで新しいプロファイルをエクスポートする
http pprofライブラリを
有効にすると 、Goはプロファイルパッケージのhttpハンドラーを登録します。 これは通常、main.goファイルに「空の」インポートを追加することによって行われます。
import _ "net/http/pprof"
pprofハンドラーを手動で登録することにより、同じことを実現できます。
httpMux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
pprofを使用する
私が話しているすべてを実証するために、テストアプリケーションを作成しました。
package main import ( "fmt" "log" "net/http" _ "net/http/pprof" "os" "runtime/pprof" "sync/atomic" "time" ) var libProfile *pprof.Profile func init() { profName := "my_experiment_thing" libProfile = pprof.Lookup(profName) if libProfile == nil { libProfile = pprof.NewProfile(profName) } } type someResource struct { *os.File } var fileIndex = int64(0) func MustResource() *someResource { f, err := os.Create(fmt.Sprintf("/tmp/%d.txt", atomic.AddInt64(&fileIndex, 1))) if err != nil { panic(err) } r := &someResource{f} libProfile.Add(r, 1) return r } func (r *someResource) Close() error { libProfile.Remove(r) return r.File.Close() } func trackAFunction() { tracked := new(byte) libProfile.Add(tracked, 1) defer libProfile.Remove(tracked) time.Sleep(time.Second) } func usesAResource() { res := MustResource() defer res.Close() for i := 0; i < 10; i++ { time.Sleep(time.Second) } } func main() { http.HandleFunc("/nonblock", func(rw http.ResponseWriter, req *http.Request) { go usesAResource() }) http.HandleFunc("/functiontrack", func(rw http.ResponseWriter, req *http.Request) { trackAFunction() }) http.HandleFunc("/block", func(rw http.ResponseWriter, req *http.Request) { usesAResource() }) log.Println("Running!") log.Println(http.ListenAndServe("localhost:6060", nil)) }
このプログラムを実行すると、
http:// localhost:6060 / debug / pprof /にアクセスして、使用可能なすべてのプロファイルを表示できます。
/ nonblockおよび/ blockにトラフィックを送り、my_example_thingリンクをクリックしてプロファイルを表示します。
my_experiment_thing profile: total 6 4 @ 0x2245 0x5d961 # 0x2244 main.usesAResource+0x64 /Users/.../pproftest.go:64 2 @ 0x2245 0x2574 0x9c184 0x9d56f 0x9df7d 0x9aa07 0x5d961 # 0x2244 main.usesAResource+0x64 /Users/.../pproftest.go:64 # 0x2573 main.main.func3+0x13 /Users/.../pproftest.go:79 # 0x9c183 net/http.HandlerFunc.ServeHTTP+0x43 /usr/local/Cellar/go/1.7.1/libexec/src/net/http/server.go:1726 # 0x9d56e net/http.(*ServeMux).ServeHTTP+0x7e /usr/local/Cellar/go/1.7.1/libexec/src/net/http/server.go:2022 # 0x9df7c net/http.serverHandler.ServeHTTP+0x7c /usr/local/Cellar/go/1.7.1/libexec/src/net/http/server.go:2202 # 0x9aa06 net/http.(*conn).serve+0x4b6 /usr/local/Cellar/go/1.7.1/libexec/src/net/http/server.go:1579
コールグラフ
brewを使用してMacに
Graphvizをインストールしました。pprofがpngイメージを作成できるようにするために必要です。
brew install Graphviz
graphvizをインストールした後、pprofを使用して、コールグラフを含むpng画像を生成できます。
go tool pprof -png /tmp/mybinary 'localhost:6060/debug/pprof/my_experiment_thing?debug=1' > /tmp/exp.png
この記事に簡単に挿入できるようにPNGを使用しましたが、通常、ブラウザーで表示するにはSVGの方が便利です。 pprofコマンドが呼び出されたときに-pngの代わりに-svgを追加して、pngの代わりにsvgを生成します。
完成した写真は以下です。
この写真は、閉じられていないリソースの作成のスタックトレースを示しています。 この画像を生成したときに、2倍の数の非ブロッキング要求を送信しましたが、これはトレースで確認できます。 すべてのスタックトレースはMustResourceで終わります。 これが気に入らない場合は、
Profile.Addを呼び出すときに整数を渡すことができます。
ターミナルからpprofを起動するときに使用できる対話型コンソールを使用することもできます。 以下では、pprofを起動し、topコマンドを使用して、すべてのスタックトレースの中でより一般的な呼び出しを確認します。
> go tool pprof 'localhost:6060/debug/pprof/my_experiment_thing?debug=1' Fetching profile from http://localhost:6060/debug/pprof/my_experiment_thing?debug=1 Saved profile in /Users/.../pprof/pprof.localhost:6060.my_experiment_thing.007.pb.gz Entering interactive mode (type "help" for commands) (pprof) top30 6 of 6 total ( 100%) flat flat% sum% cum cum% 6 100% 100% 6 100% main.usesAResource 0 0% 100% 2 33.33% main.main.func3 0 0% 100% 2 33.33% net/http.(*ServeMux).ServeHTTP 0 0% 100% 2 33.33% net/http.(*conn).serve 0 0% 100% 2 33.33% net/http.HandlerFunc.ServeHTTP 0 0% 100% 2 33.33% net/http.serverHandler.ServeHTTP 0 0% 100% 6 100% runtime.goexit (pprof)
おわりに
プロセッサまたはメモリのプロファイリングに使用されるすべての機能がpprof APIを介して利用できるわけではありませんが、必要なコードが非常に少ないため、非常にクールな視覚化が得られます。 次回ライブラリを作成するときに、スタックトレースが問題を明確に解消するのに役立つかもしれません。