Carpe Diem

備忘録

Prometheus でカスタムコレクタを用意する

概要

以前↓の独自メトリクスを作る方法を紹介しました。

christina04.hatenablog.com

これはdirect instrumentation(直接計装)というやり方で、アプリケーションサーバ自体がstatsを持ちprometheusにデータを渡すやり方です。

今回はそうではなく別プロセスや別サーバ、ホストOSのstatsを取り込んでコレクタとして登録する方法を紹介します。

イメージ的には各stats情報をprometheusフォーマットに変換するproxyを作る感じです。

環境

  • prometheus/client_golang 1.5.0

Collectorインタフェース

GoではCollector interfaceを実装することでCustom Collectorを作れます。

Collector interfaceは以下の2つのメソッドを持っています。

  • Describe(chan<- *Desc)
  • Collect(chan<- Metric)

Describeメソッド

Describeメソッドは自分が生成するメトリクスを説明する記述を返します。
具体的には、メトリクスの名前・ラベル名・ヘルプ文字列です。
Describeは登録時に呼び出され、メトリクスの重複登録を避けるために使われます。

Collectメソッド

Collectメソッドはターゲットのアプリケーションインスタンスから必要なデータを取り出し、クライアントライブラリにメトリクスを送り返します。
CollectはPrometheusサーバがスクレイピングする際に呼び出されます。

実装

それでは具体的な実装をしていきます。今回は

  • goroutineの数
  • threadの数

をゲージとして取得できるコレクタを用意します。

collectorの構造体

計測したい値を*prometheus.Desc型のフィールドで用意します。
prometheus.NewDesc()で生成可能です。

type collector struct {
        goroutinesDesc *prometheus.Desc
        threadsDesc    *prometheus.Desc
}

func newCollector() *collector {
        return &collector{
                goroutinesDesc: prometheus.NewDesc(
                        "goroutines",
                        "Number of goroutines that currently exist.",
                        nil, nil),
                threadsDesc: prometheus.NewDesc(
                        "threads",
                        "Number of OS threads created.",
                        nil, nil),
        }
}

Collectorインタフェースの実装

Describeの方は簡単で、フィールドの値をchannelにただ渡すだけです。

func (c *collector) Describe(ch chan<- *prometheus.Desc) {
        ch <- c.goroutinesDesc
        ch <- c.threadsDesc
}

Collect()の方ではメトリクスのためのデータを揃え、時には加工してフィールドに入れます。
prometheus.MustNewConstMetric()を使って渡します。

func (c *collector) Collect(ch chan<- prometheus.Metric) {
        ch <- prometheus.MustNewConstMetric(c.goroutinesDesc, prometheus.GaugeValue, float64(runtime.NumGoroutine()))
        n, _ := runtime.ThreadCreateProfile(nil)
        ch <- prometheus.MustNewConstMetric(c.threadsDesc, prometheus.GaugeValue, float64(n))
}

引数に入れる値のタイプとしては以下の3つがあります。

タイプ いつ使うか
GaugeType ゲージの時
CounterType カウンタの時
UntypedValue カウンタかゲージかがはっきりしない時

expose

Prometheusが監視対象のメトリクスをPullできるよう/metricsのエンドポイントを用意します。

func main() {
        reg := prometheus.NewRegistry()
        reg.MustRegister(newCollector())

        http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
        log.Fatal(http.ListenAndServe(":8080", nil))
}

動作検証

起動してhttp://localhost:8080/metricsにアクセスすると、以下のようにgoroutineとthreadのメトリクスが取得できました。

f:id:quoll00:20200307024319p:plain

サンプルコード

今回のサンプルコードはこちらです。

github.com

まとめ

Prometheusでカスタムコレクタを作る方法を説明しました。

参考