Goプロセスのランタイム統計を可視化する

本記事は Go 2 Advent Calendar 2020 の1日目の記事です。

Click here for English version

Goにはpprofをはじめとしたプロファイリングエコシステムが揃っています。しかしリソース使用量のトレンドをリアルタイムで監視したいだけのケースで手軽に扱えるツールが見つからなかったので、gosivyというシンプルなTUIツールを作りました。

見つからなかったと書きましたが、正確にはstatsvizというWebベースの可視化ツールがarl氏により先月公開されています。 非常に優れたツールで、対象アプリケーションに数行のコードを追加するだけでチャートを描画することができます。 仕組みがシンプルでUIも見やすくてとても良ツールなのですが、何点か気になる点がありました:

総じて、メトリクスを取りたい対象プロセスの負担が少し大きすぎるなと感じていました。

そこでstatsvizの手軽さを残しつつ、監視対象プロセスの負担をなるべく減らしたツールを作ることにしました。

使い方

基本的な使い方をご紹介します。

まずはREADMEに従って、お好みの方法で gosivy バイナリをインストールします。

nakabonne/gosivy - GitHub

次にメトリクスを可視化したいアプリケーションに github.com/nakabonne/gosivy/agent をimportし、以下のようにエージェントを起動するための処理を追加します。保存したらそのアプリケーションを実行してください。

package main

import "github.com/nakabonne/gosivy/agent"

func main() {
	if err := agent.Listen(agent.Options{}); err != nil {
		panic(err)
	}
	defer agent.Close()
}

最後にgosivyプロセスを起動すれば、対象アプリケーションのメトリクスがリアルタイムで描画されます。gosivyプロセスは監視対象プロセスと同じホスト・ユーザーで実行する必要があります。

$ gosivy

しれっと引数無しでの実行を紹介しましたが、gosivyはエージェントが実行されているプロセスを自動で検出し、最初に見つけたプロセスを引数として実行します。そのため、エージェントが複数実行されている場合はまず -l オプションでPIDを確認します。

$ gosivy -l
PID   Exec Path
15788 foo  /path/to/foo
16841 bar  /path/to/bar

そしてそれを引数として与える必要があります。

$ gosivy 15788

リモートモード

対象プロセスのアドレスさえ分かれば、簡単に別ホストから監視することができます。

エージェントがリッスンするアドレスを指定します。

package main

import "github.com/nakabonne/gosivy/agent"

func main() {
	err := agent.Listen(agent.Options{
		Addr: ":9090",
	})
	if err != nil {
		panic(err)
	}
	defer agent.Close()
}

gosivyプロセスからアクセス可能なアドレスを引数として与えると、ローカルモードと同じように描画が始まるはずです。

$ gosivy host.xz:9090

可視化までの流れ

全体的なアーキテクチャはgopsを参考にしています。

gosivyプロセスがエージェントに対してスクレイピングする形でメトリクスを収集しているため、gosivyプロセスを立ち上げない限り対象プロセスは何もしません。 つまりエージェントは、チャートを閲覧している時のみランタイム統計を計算するようになっています。

また、プロセス間はTCPソケットを介して通信しているため、ローカルホストもリモートホストも同じインターフェイスで通信することが可能です。

今後の展望

もともとこのツールはaliというツールのデバッグが捗るようにと作った背景があり、正直必要な機能は揃っています。 しかし以前Gophers Slackでstatsvizの作者と少し話したところ、可視化とメトリクス収集を分けていることに将来性を感じてくれているようでした。 最近はgo-echartsなどのパワフルな可視化ライブラリが続々と登場していて、せっかく可視化処理を分離しているので様々な場所にプロット出来るようにする予定です。