mackerel-agent-pluginsにコントリビュートした話

久保田です。

最近、mackerel-agent-pluginsにプラグインを作り、コントリビュートしました。

人生初のossへのコントリビュートだったので、その話を忘れないうちに書いておきます。

今回、僕はGoogle Cloud PlatformのCompute Engineに入れることでcustom metric を収集することのできるプラグインを書きました。

すでに存在していた、mackerel-plugin-aws-ec2のGCPバージョン、という位置付けで作りました。

僕がgcpを使っているということは正直なく、ただOSSに貢献したかったから作った、というものになります。

そして無事マージしていただきました。mackerel-agent-pluginsの方々、ありがとうございます。mm

github.com

というわけでマージまで至った流れと反省をつらつらと書いていきたいと思います。

大まかな流れ

マージまでの流れは以下のような感じでした。

  • 実装
  • READMEを書く
  • CIでテスト
  • プルリク
  • レビューしていただく
  • マージ

実装

今回、goで何かを作るのも初めてだったため、実装の前にいろいろな方のpluginを参考にしました。

mackerel-agent-pluginを作る時は、だいたい以下のような構成になります。 (mackerel-agent-plugin-gcp-compute-engineの場合)

|----- README.md
|----- main.go
|----- lib
     |------ gcp-compute-engine.go
     |------ gcp-compute-engine_test.go

かなり端折っていますが、パッケージの実装の構成は以下のような感じです。

  • gcp-compute-engine.go
package gcpce // パッケージ名を決める

import (
  mp "github.com/mackerelio/go-mackerel-plugin-helper"
   // importするパッケージを指定
)


// プラグインで使用する構造体。
// この構造体に必要なメソッドを作っていく。
type ComputeEnginePlugin struct {
    Project           string
    InstanceName      string
    MonitoringService *monitoring.Service
    Option            *Option
    Tempfile          string
}

// graphの定義をmap[string]に入れていく。
// それぞれが1つのグラフになる。
var graphdef = map[string]mp.Graphs{
    "Firewall.DroppedBytesCount": mp.Graphs{
        Label: "FireWall Dropped Bytes Count",
        Unit:  "bytes",  // 型の指定
        Metrics: []mp.Metrics{
            {Name: "dropped_bytes_count", Label: "Dropped Bytes Count", Type: "uint64"},
        },
    },
    "Firewall.DroppedPacketsCount": mp.Graphs{
        Label: "FireWall Dropped Packets Count",
        Unit:  "integer",
        Metrics: []mp.Metrics{
            {Name: "dropped_packets_count", Label: "Dropped Packets Count", Type: "uint64"},
        },
    }, 

        ....
}

// グラフの定義を返す構造体のメソッド。
// グラフを動的に変化させたい場合はここで処理する。
func (p ComputeEnginePlugin) GraphDefinition() map[string]mp.Graphs {
    return graphdef
}

// 最新の値を取得する関数。
// gcp-compute-engineの場合は、gcpのAPIコールをして値を取得する。
func getLatestValue() (interface{}, error) {
       ....
}

// mackerelに送信する値を取得し、まとめるメソッド。
// getLatestValueを必要に応じて呼び出し、メトリックを集める。
func (p ComputeEnginePlugin) FetchMetrics() (map[string]interface{}, error) {
       ....
}

// pluginのメイン処理。
func Do() {
    // コマンドライン引数を処理したり、メタデータを集める。
        ....

    var computeEngine = ComputeEnginePlugin{
        MonitoringService: service,
        Project:           "projects/" + projectID,
        InstanceName:      instanceName,
        Option:            &Option{Key: *optAPIKey},
    }

    // 構造体をmackerel-agent-plugin-helperに渡す。
    helper := mp.NewMackerelPlugin(computeEngine)

    if *optTempfile != "" {
        helper.Tempfile = *optTempfile
    } else {
        helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-gcp-compute-engine-%s", computeEngine.InstanceName)
    }


    helper.Run() // 渡した構造体のFetchMetricsメソッドなどを実行してくれる。 https://github.com/mackerelio/go-mackerel-plugin-helper/blob/master/mackerel-plugin.go#L310

}

次に最終的に動かすことになるmain.goです。 - main.go

package main

import (
    "github.com/mackerelio/mackerel-agent-plugins/mackerel-plugin-gcp-compute-engine/lib" // パッケージ本体をインポート
)

func main() {
    gcpce.Do()
}

プラグインで使用し、makerel-agent-plugin-helperに渡す構造体を作り、
graphdefにグラフ定義を書き、
グラフを返すGraphDefinitionメソッドを定義し、
値を収集するFetchMetricsメソッドを定義し、
Do関数で引数を処理したりし、構造体をmackerel-agent-plugin-helperに渡して、helper.Run()で実行

という流れです。

その他必要に応じてメソッドや構造体を実装します。

READMEを書く

READMEを書く、と言うと簡単じゃん、と思うかもしれません。僕もそう思っていました。
しかし実際書くとREADMEがシステムの行く末を決める、と言っても過言ではないなと思いました。

使う人にとってはREADMEの情報がほぼ全てだと思いますし、
(ほとんどの人は使い方がわからないときは、ソースの中まで見ずに他の解決法を探すと思います。)
よって、ここにどれだけわかりやすく簡潔に書くか、ということが求められると感じました。
システムのインターフェイス、と言ってしまってもいいかもしれません。

そして、このREADMEを英語で書かなければなりません。。。
割と読める方だとは思っていたのですが、書くとなるとどうしていいかわからないものでした。

なので、僕はちょくちょくブログも書いてくれる後輩であるアメリカ帰りの湯浅君に英語を教わりながらREADMEを書いていきました。
大感謝です。湯浅君がすごくカッコよく見えました。

もしかしたらREADMEが今回一番苦戦し、一番勉強になった箇所かもしれません。。。笑

CIでテスト

mackerel-agent-pluginsの場合は、

https://github.com/mackerelio/mackerel-agent-plugins/blob/master/CONTRIBUTING.md

こちらにコントリビュートへの手順がありました。
こちらに書いてある通り、CIでテストをパスする必要があります。
travisCIにforkしたリポジトリを登録し、 テストをします。

僕の場合は、golintでかなりテストを失敗していました。。。
goのお作法を知らなかったので、なかなか苦戦しました。

プルリク

CIでテストが通ったら、プルリクです。
人生初のプルリクということでかなりドキドキしていました。

この時、プルリクに残すメッセージに何が適切かわからず、今も何が適切かわかりません。。。
次は他の方のを参考にしたいと思います。

レビューをしていただく

ありがたいことにレビューをしていただきました。

astjさん、ありがとうございました。

僕自身goを書くのが初だったので、拙いコードを読ませてしまうことになり、大変心苦しかったです。。。

次はもっと綺麗なコードでプルリクできるようにします。
(レビューしていただいた後にタイポでテストが通らない、などしょうもないミスがありました。大変申し訳ありません。。。)

マージ

レビューしていただき、直し、テストも通したらあとは待つのみ、です。

僕は幸せなことにマージしていただいたので、現在は晴れてmackerel-agent-pluginsの一員になっています。

初のプルリク、ということで、反省がかなり多くなってしまいました。
どう使われるのが一番なのか、英語でREADMEを書くことなど、しかし普段の開発ではわからないことや初めてのことも多く、大変勉強になりました。

次はもっとスムーズにできるようにしたいなと思います。

改めまして、mackerelioの方々、ありがとうございました。