この記事はMackerelアドベントカレンダー2018 の18日目です。
Mackerelはサーバ管理・監視サービスですが、取得する数値はサーバに限ったものではなく、例えば体重など、数値なら比較的なんでも記録することができて、記録した値の推移を眺めることができます。個人的にGitHub を使っていて積極的に参加していきたいと思っているので、活動した数値を可視化するプラグイン を作ってみました。
作ったグラフ
この記事では、担当したイシューの残っている数と閉じた数を扱っていますが、GitHub API v3 で取得できる値ならなんでも良いと思います。
プラグイン は、mackerel-agent から1分ごとに呼ばれるコマンドです。Goが一番馴染んでいるのでGoを使ってプラグイン を書きますが、ただのコマンドなので何で書いても良いと思います。
Goで書く場合、現在、プラグイン 用の公式パッケージは2種類あります。
go-mackerel-plugin-helper のREADMEに、
We recommend to use go-mackerel-plugin instead of go-mackerel-plugin-helper to create mackerel agent plugin.
とあるので、今は go-mackerel-plugin を使う方が良さそうです。
go-mackerel-plugin を使う場合は以下のインターフェイス どちらかを実装する必要があります。MetricKeyPrefix()
があればユーザが設定ファイルでプラグイン の名前を変更できるようになるので、新しく作る場合はPluginWithPrefix
を実装する方が良いと思います。
package mackerelplugin
type Plugin inteface {
GraphDefinition() map [string ]Graphs
FetchMetrics() (map [string ]float64 , error )
}
type PluginWithPrefix interface {
Plugin
MetricKeyPrefix() string
}
例えばGitHub のイシューをopenとclosedで分けて収集したい場合、プラグイン は以下のようなメトリクスを返すように書きます。ここで、github -issues は MetricKeyPrefix()
で返した値となり、1545103883 はメトリックを取得した時刻です。中央の数値は FetchMetrics()
が返す値です。
custom.github-issues.open 20 1545103883
custom.github-issues.closed 40 1545103883
go-mackerel-plugin で書く場合、メトリック名は以下の要素が.
で連結されたものです。
custom (固定)
MetricKeyPrefix()
の値
GraphDefinition()
で返したマップのキー名
GraphDefinition()
で返したマップのMetrics[].Name
そのため、上の例と同じメトリック定義を返す場合は以下のような実装になります。
import mp "github.com/mackerelio/go-mackerel-plugin"
func (g *GitHubPlugin) GraphDefinition() map [string ]mp.Graphs {
return map [string ]mp.Graphs{
"" : {
Metrics: []mp.Metrics{
{Name: "open" , Label: "Open" , Stacked: true },
{Name: "closed" , Label: "Closed" , Stacked: true },
},
},
}
}
リポジトリ ごとにメトリクスを分けたい場合
上の例では、custom.github -issues.open と custom.github -issues.closed の2つしか値を返していませんが、GitHub は複数のリポジトリ を持っているので、リポジトリ 単位で分けられたらいいな、と思いました。イメージとしては以下のようなメトリックです。
custom.github-issues.repos.taskfs.open 20 1545103883
custom.github-issues.repos.taskfs.closed 40 1545103883
custom.github-issues.repos.plan9port.open 1 1545103883
custom.github-issues.repos.plan9port.closed 2 1545103883
しかしGitHub 上のリポジトリ は増えたり減ったりするので、最初のGraphDefinition()
では決まった名前を返すことができません。この場合、メトリック名に1箇所だけワイルドカード (#
または *
)を含めることができるので、リポジトリ 名の部分をワイルドカード にすると対応できるようです。
リポジトリ 名の部分にワイルドカード を使ったGraphDefinition()
です。
import mp "github.com/mackerelio/go-mackerel-plugin"
func (g *GitHubPlugin) GraphDefinition() map [string ]mp.Graphs {
return map [string ]mp.Graphs{
"repos.#" : {
Metrics: []mp.Metrics{
{Name: "open" , Label: "Open" , Stacked: true },
{Name: "closed" , Label: "Closed" , Stacked: true },
},
},
}
}
ただし、ホストメトリック#グラフ定義の投稿 によるとワイルドカード は1箇所だけしか使えません。
またワイルドカード #
は一つまでしか使えません。
メトリック名全体は ^custom(\.([-a-zA-Z0-9_]+|[*#]))+$
のようになります。
メトリックの値を収集する
これはGitHub API を使って収集するだけなので簡単ですね。
func (g *GitHubPlugin) FetchMetrics() (map [string ]float64 , error ) {
metrics := make (map [string ]float64 )
var opt github.IssueListOptions
opt.State = "all"
for {
a, resp, err := g.c.Issues.List(g.ctx, true , &opt)
if err != nil {
return nil , err
}
for _, p := range a {
metrics["repos." +*p.Repository.Name+"." +*p.State]++
}
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return metrics, nil
}
アクセストーク ンなどの管理
Mackerelプラグイン でアクセストーク ンなどのシークレットを扱う場合、どうするのが正しいのかわかりませんでしたが、環境変数 でプラグイン に渡すのが良さそうです。
s := os.Getenv("GITHUB_ACCESS_TOKEN" )
token := &oauth2.Token{AccessToken: s}
ts := oauth2.StaticTokenSource(token)
c := github.NewClient(oauth2.NewClient(ctx, ts))
動作確認
一通り実装したらメトリックが取れているか確認しましょう。go-mackerel-plugin を使っているならそのまま実行すれば取得したメトリックを標準出力に書き出すので、これで確認することができます。ここで出力されない場合、GraphDefinition()
のメトリック名とメトリック値の名前が食い違っていることが多いです。
$ go run path/to/plugin/main.go
github-issues.repos.zipcode.open 4 1545117743
github-issues.repos.taskfs.open 1 1545117743
github-issues.repos.pin.closed 1 1545117743
また、MACKEREL_AGENT_PLUGIN_META 環境変数 に何かセットすると、グラフ定義をJSON で確認することができます。(以下の例は整形しています)
$ MACKEREL_AGENT_PLUGIN_META=1 go run path/to/plugin/main.go
# mackerel-agent-plugin
{
"graphs": {
"github-issues.repos.#": {
"label": "GitHub Issues",
"unit": "integer",
"metrics": [
{
"name": "open",
"label": "Open",
"stacked": true
},
{
"name": "closed",
"label": "Closed",
"stacked": true
}
]
}
}
}
mackerel-agent.conf にプラグイン の実行コマンドを追加してエージェントを再起動すればメトリックが収集されるようになります。下ではテストのためにgo run
していますが、通常はビルドしたコマンドを使いましょう。
[plugin.metrics.github]
command = "go run path/to/plugin/main.go"
他のサンプル
mackerel-agent-plugins にいっぱいあるので参考になりました。
グラフの調整
上のプラグイン でopen, closedのイシューをリポジトリ 単位で取れるようになりましたが、このままだとopen/closedが全部積み重なって表示されるため少し読みづらいです。
オープン・クローズドが混ざったグラフ
終わったものと残っているものの推移を知りたいので、式を使ったグラフで対応しました。
カスタムダッシュ ボードでグラフを追加
グラフのタイプを 式グラフ に変更
式を書く
stack(
group(
alias(sum(host(3u5u9mHFmFS, custom.github-issues.repos.*.closed)), 'closed issues'),
alias(sum(host(3u5u9mHFmFS, custom.github-issues.repos.*.open)), 'open issues')
)
)
最終的にオープン・クローズドを分けてどれだけ消化したのかを見られるようになりました。上の式では全部のリポジトリ をまとめて集計していますが、特定のリポジトリ だけ取り出すことも簡単にできそうですね。
最終的なダッシュ ボード
式はカスタマイズしたグラフを表示する が分かりやすかったです。
悩んだところ
グラフ定義を変更したらエージェント再起動が必要?
正確には分かってませんが、開発中にグラフ定義をよく変更していました。このとき、エージェントを起動したままプラグイン から返すグラフ定義を変更すると、変更した後に取得したメトリックの単位がfloat
になっていたり、ワイルドカード を使ってもまとまらなかったりしました。
何かおかしいなと思ったらエージェントを再起動してみましょう。
グラフ定義を削除したい
上のように、間違ったグラフ定義が作られてしまった場合、不要な定義がいっぱい作られてしまうので、不要ならhttps://mackerel.io/my/graph-defs を開くと不要なグラフ定義を削除できるようです。