概要
サイオステクノロジーの中島です。アプリの性能解析を実施する際にプロファイラをと呼ばれるアプリがあるのでPyroscope(https://pyroscope.io/)と呼ばれるアプリを使用して、go言語の性能解析を実施してみます。
プロファイリングツールについて
goによるビルドツールによる確認
アプリケーションの性能解析(プロファイル)を実施するには、そのアプリケーションのCPU使用率やメモリ使用量を調べる必要があります。その測定を実施するためプロファイルツールをアプリケーションに組み込む必要があります。例えば、go言語で性能解析をする場合はデフォルトで「runtime/pprof」や「net/http/pprof」と呼ばれるライブラリがあるので、それを使用することで、プログラム関数単位でCPU使用率やメモリ使用量を調べることができることや、視覚化するためのツールもあります。
例として、「net/http/pprof」を使用してwebサーバーを起動しながら、メモリとCPUに負荷をかけるプログラムを用意して、プロファイル解析を実施します。
なお、実行するためにはgraphvizが必要なためあらかじめ以下のコマンドでインストールします。
sudo apt install graphviz
main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
)
func runserver() *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", r.URL.Path)
})
return &http.Server{
Addr: ":8080",
Handler: mux,
}
}
func loop() {
tmp := []string{}
for i := 0; i < 10000; i++ {
tmp = append(tmp, "test input data")
}
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
s := runserver()
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
go s.ListenAndServe()
go func(ctx context.Context) {
loop:
for {
select {
case <-ctx.Done():
break loop
default:
loop()
}
time.Sleep(time.Microsecond * 500)
}
}(ctx)
<-ctx.Done()
stop()
if err := s.Shutdown(ctx); err != nil {
log.Panic(err)
}
}
上記起動後以下のコマンドを実行すると、webサーバが起動し、ブラウザ内でCPUの計測状態を視覚確認することが可能になります。ただ、profileデータを取得するのにはデフォルトでは30秒ほどかかります。
go tool pprof -http=:4000 http://indigonote:6060/debug/pprof/profile
図1 CPU負荷のプロファイル状態
サーバツールを使用した確認
go言語のツールは期間を区切って計測したりすることは便利なのですが、運用しながら解析する場合は、別途ツールを使用した方が便利です。
ここで、紹介するのはでPyroscopeというツールを紹介します。このツールはアプリケーションから送信されたプロファイル情報を格納し、その情報をブラウザから確認することができます。また、このツールはGo言語以外にも、python、rubyなど様々な言語(https://pyroscope.io/docs/)にも対応しています。実施の際にgo言語に組み込んで動作確認を実施してみます。
Pyroscopeの動作について
Pyroscopeはコンテナアプリ(https://hub.docker.com/r/pyroscope/pyroscope)があるので以下のコマンドを使用してDockerでサーバを立ててみます。
docker run -it -p 4040:4040 pyroscope/pyroscope server
立ち上げ後ブラウザでhttp://localhost:4040にアクセスすると以下の図の画面を確認できます。
図2 Pyroscopeのブラウザ画面表示
ブラウザ内の画面は、「アプリ名.cpu」でCPU負荷、「アプリ名.alloc_objects」でメモリ使用量を確認することができます。下半分の図を切り替えることで、関数リストと数値に応じた図、関数の状態遷移を確認することができます。
goによるサンプル作成
Pyroscopeへプロファイル送信するクライアントをGo言語に組み込む際にはhttps://pyroscope.io/docs/golang/のサイトを参考にしてメモリとCPUに負荷をかけたプログラムを作成してみます。
main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/pyroscope-io/client/pyroscope"
)
func runserver() *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", r.URL.Path)
})
return &http.Server{
Addr: ":8080",
Handler: mux,
}
}
func loop() {
tmp := []string{}
for i := 0; i < 10000; i++ {
tmp = append(tmp, "test input data")
}
}
func main() {
pyroscope.Start(pyroscope.Config{
ApplicationName: "sample",
ServerAddress: "http://localhost:4040",
})
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
s := runserver()
go s.ListenAndServe()
go func(ctx context.Context) {
loop:
for {
select {
case <-ctx.Done():
break loop
default:
loop()
}
time.Sleep(time.Microsecond * 500)
}
}(ctx)
<-ctx.Done()
stop()
if err := s.Shutdown(ctx); err != nil {
log.Panic(err)
}
}
実行するとApplicationのリストに「sample」が追加されるので選択してCPUの図を表すと以下図の通りになります。
図3 PyroscopeによるCPUの性能評価
上記図で、図面の横幅が大きいほど、CPUを占有している関数が視覚的にわかります。
まとめ
Pyroscopeを使用して動作中アプリの性能解析ができることを確認できました。このアプリを使用することで、CPUやメモリ占有率が視覚的にわかるので改善すべきアプリが視覚的にわかったりするので、性能改善をしやすくなります。