えいのうにっき

a-knowの日記です

Golang製アプリケーションの性能監視 〜 mackerel-plugin-gostats を読み解く

この記事は、Mackerel アドベントカレンダー(全部CRE)の17日目の記事です。今回も、『プラグイン読み解きシリーズ』でいきたいと思います。題材は、mackerel-plugin-gostats!

github.com

当記事が前提とするバージョン

この記事を書いている今の最新版である、mackerel-agent-plugins の v0.53.0 を前提としています。

Releases · mackerelio/mackerel-agent-plugins · GitHub

また、このプラグインが利用しているパッケージ、fukata/golang-stats-api-handlerのバージョンはv1.0.0を前提としています。

github.com

どんなプラグイン?

このプラグインは、Goで書かれたアプリケーションが消費しているCPUやメモリリソースの状況、GC回数、goroutineの起動数などの情報を取得し可視化するためのプラグインです。といっても、それらの情報を取得に関しては、fukata/golang-stats-api-handlerを利用していることを前提としています。

f:id:a-know:20181214140502p:plain

インストールと設定手順

mackerel-plugin-gostats プラグインは、プラグイン集として提供しているパッケージ、 mackerel-agent-plugins に含まれています。以下のようなコマンドでインストールすることができます。

$ yum install mackerel-agent-plugins
$ apt-get install mackerel-agent-plugins

詳細は下記ヘルプページも参照してください。

mackerel.io

インストール先は /usr/bin/mackerel-plugin-gostats です(他のプラグインも同じ場所にインストールされています)。

プラグイン自体のインストールはこれだけでいいのですが、先述の通り、各種メトリックの取得にfukata/golang-stats-api-handlerを利用しているため、監視対象のアプリケーションにも少しコードを追加する必要があります。詳細はREADMEにも記載があるのですが、追加するコードのサンプルとしては以下のようなものになります。

import (
    "net/http"
    "log"
    "github.com/fukata/golang-stats-api-handler"
)
func main() {
    http.HandleFunc("/api/stats", stats_api.Handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

/api/stats から以下のようなレスポンスが得られるようになっていれば、パッケージの追加は問題ないです。

$ curl http://localhost:8080/api/stats
{"time":1544756661431593124,"go_version":"go1.9.4","go_os":"linux","go_arch":"amd64","cpu_num":2,"goroutine_num":4,"gomaxprocs":2,"cgo_call_num":0,"memory_alloc":3888912,"memory_total_alloc":9742251744,"memory_sys":72845560,"memory_lookups":555080,"memory_mallocs":20806672,"memory_frees":20789933,"memory_stack":557056,"heap_alloc":3888912,"heap_sys":67600384,"heap_idle":62078976,"heap_inuse":5521408,"heap_released":61718528,"heap_objects":16739,"gc_next":7712576,"gc_last":1544756580235402219,"gc_num":20495,"gc_per_second":0,"gc_pause_per_second":0,"gc_pause":[]}

この状態で、mackerel-plugin-gostats を手動実行してみましょう。このプラグインに限らず、Mackerelのプラグインは要はコマンドですので、手動でも実行する事ができます。

$ mackerel-plugin-gostats
gostats.runtime.goroutine_num   4  1544756156
gostats.runtime.cgo_call_num    0  1544756156
gostats.memory.memory_alloc 3919360    1544756156
gostats.memory.memory_sys   72845560   1544756156
gostats.memory.memory_stack 557056 1544756156
gostats.operation.memory_lookups    40 1544756156
gostats.operation.memory_mallocs    1040   1544756156
gostats.operation.memory_frees  40 1544756156
gostats.heap.heap_sys   67600384   1544756156
gostats.heap.heap_idle  62062592   1544756156
gostats.heap.heap_inuse 5537792    1544756156
gostats.heap.heap_released  0  1544756156
gostats.gc.gc_num   0  1544756156
gostats.gc.gc_per_second    0  1544756156
gostats.gc.gc_pause_per_second  0  1544756156

設定ファイルである mackerel-agent.conf は、デフォルトでは /etc/mackerel-agent/mackerel-agent.conf にインストールされます。mackerel-plugin-gostats を使う際の conf への追記内容などは、README.md に記載されていますので、ご確認ください。

mackerel-agent-plugins/README.md at master · mackerelio/mackerel-agent-plugins · GitHub

conf への追記ができたら、mackerel-agentを再起動することで設定完了です。

取得されるメトリック

このプラグインにより取得され、Mackerelにグラフ描画されるメトリックについて、以下に各グラフ定義ごとに説明します。 また表中の diff とは、プラグイン側で差分値計算をするかどうかについて示しており、 となっている項目はプラグインの処理として「前回の実行時の値と差分」を計算し出力しています(データソースがいわゆるカウンターメトリックで単調増加する値)。diff となっている値に関しては差分値ですが、それ以外の値はすべて、プラグイン実行時・そのときその瞬間の値となります。

Runtime

ラベル名 メトリック名 diff 説明
Goroutine Num custom.gostats.runtime.goroutine_num - 起動しているgoroutineの数。 runtime.NumGoroutine() で取得できる値。
CGO Call Num custom.gostats.runtime.cgo_call_num cgoによるCのコードの呼び出し回数。 runtime.NumCgoCall() で取得できる値。

Memory

ラベル名 メトリック名 diff 説明
Alloc custom.gostats.runtime.memory_alloc - allocateされていて未開放のメモリ領域。単位はbyte。 runtime.MemStats.Alloc で取得できる値。
Sys custom.gostats.runtime.memory_sys - システムが確保しているメモリ領域。単位はbyte。 runtime.MemStats.Sys で取得できる値。
Stack In Use custom.gostats.runtime.memory_stack - スタック領域として確保されているメモリ領域。単位はbyte。 runtime.StackInuse で取得できる値。

Operation

ラベル名 メトリック名 diff 説明
Pointer Lookups custom.gostats.runtime.memory_lookups ポインタルックアップの回数。 runtime.MemStats.Lookups で取得できる値。
Mallocs custom.gostats.runtime.memory_mallocs メモリ確保(malloc)の回数。 runtime.MemStats.Mallocs で取得できる値。
Frees custom.gostats.runtime.memory_frees メモリ解放(free)の回数。 runtime.Frees で取得できる値。

Heap

ラベル名 メトリック名 diff 説明
Sys custom.gostats.runtime.heap_sys - システムが確保しているメモリ領域。gostats.runtime.memory_sys とはまた別。単位はbyte。 runtime.MemStats.HeapSys で取得できる値。
Idle custom.gostats.runtime.heap_idle - アイドル状態のメモリ領域。単位はbyte。 runtime.MemStats.HeapIdle で取得できる値。
In Use custom.gostats.runtime.heap_inuse - 非アイドル状態のメモリ領域。単位はbyte。 runtime.MemStats.HeapInuse で取得できる値。
Released custom.gostats.runtime.heap_released 開放されたメモリ領域。単位はbyte。 runtime.MemStats.HeapReleased で取得できる値。

GC

ラベル名 メトリック名 diff 説明
GC Num custom.gostats.runtime.gc_num GCが走った回数。 runtime.MemStats.NumGC で取得できる値。
GC Per Second custom.gostats.runtime.gc_per_second - 1秒あたりのGC回数。 runtime.MemStats.NumGC で取得できる値とその前回値との差を、計測間隔の秒数で割った値。
GC Pause Per Second custom.gostats.runtime.gc_pause_per_second - 1秒あたりの、GCによる停止期間。 runtime.MemStats.PauseTotalNs で取得できる値とその前回値との差を、計測間隔の秒数で割った値。

利用可能なオプション

-scheme

fukata/golang-stats-api-handler により取得できる gostats の値を返す API エンドポイントへのリクエストの際のプロトコルを指定できます。デフォルト値は http

-host

fukata/golang-stats-api-handler により取得できる gostats の値を返す API エンドポイントのホスト部を指定できます。デフォルト値は localhost

-port

fukata/golang-stats-api-handler により取得できる gostats の値を返す API エンドポイントのポートを指定できます。デフォルト値は 8080

-path

fukata/golang-stats-api-handler により取得できる gostats の値を返す API エンドポイントのパスを指定できます。デフォルト値は /api/stats

-uri

fukata/golang-stats-api-handler により取得できる gostats の値を返す API エンドポイント全体を指定できます。デフォルト値なし。以下、使用例。

[plugin.metrics.gostats]
command = "mackerel-plugin-gostats -uri=https://foobar:9999/hoge/fuga"

このオプションを指定した場合、上述の -scheme -host -port -path については無視されます。

-metric-key-prefix

このプラグインで取得されるメトリック、描画されるグラフのラベルなどに prefix を付与することができます。これを活用することにより、ひとつのホスト内で複数のGoアプリケーションが稼働していたとしても、それぞれを個別にモニタリングすることが可能です。こんな↓かんじ。

[plugin.metrics.gostats]
command = "mackerel-plugin-gostats"

[plugin.metrics.gostats_foobar]
command = "mackerel-plugin-gostats -uri=https://foobar:9999/hoge/fuga -metric-key-prefix=foobar"

まとめ

Golang製アプリケーションのモニタリングに有用なプラグイン、mackerel-plugin-gostats を解説しました。私もGoで書いた趣味のWebアプリケーションはいくつか運用していて、このプラグインもすでに利用していたのですが、今回この記事を書いて改めて勉強になりました。runtime パッケージすごい。

ちなみに、いろいろググっていたら以下のようなブログエントリを発見しました。

golang プロセスのモニタリングってみんなどうしてるんですかね、という話 - tokuhirom's blog

猛者たち(Mackerel のプロダクトマネージャーである id:Songmu の姿も!)のslack?での会話ログのようですね。みんな気にして取ってるのは gc count ぐらいなのかな goroutineがリークしてないか一番気をつけてる といった知見が詰まっていそうでしたので、ご興味ありましたらこちらもぜひ!