概要
以前↓の独自メトリクスを作る方法を紹介しました。
これは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つがあります。
タイプ | いつ使うか |
---|---|
GaugeValue | ゲージの時 |
CounterValue | カウンタの時 |
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のメトリクスが取得できました。
direct instrumentationかカスタムコレクタか
direct instrumentationかカスタムコレクタか悩む時があります。
例えばグローバル変数など内部statsを用意して、外部statsやイベントのデータを内部statsに反映、それをカスタムコレクタとして公開することができます。
通常このアプローチはdirect instrumentationですが、上記のようにカスタムコレクタとしても実現できます。
しかし基本的に内部で値を保持するものに関してはdirect instrumentationが良いです。
というのもグローバル変数など内部で状態を持つ場合にカスタムコレクタを使うと、write(値の更新)とread(スクレイピング)を自前で実装するため、race conditionが発生しないようatomic packageを使うなど考慮が必要です。
direct instrumentationはクライアントライブラリで提供されているので、内部で値を持ちますが↑のような課題はライブラリ側でよろしくやってくれるので意識する必要がありません。
ref: Writing exporters | Prometheus
サンプルコード
今回のサンプルコードはこちらです。
まとめ
Prometheusでカスタムコレクタを作る方法を説明しました。