Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

OpenTelemetryメトリックにObserverとResourceが追加されました

以前の記事で、OpenTelemetryでメトリックを記録するを書きましたが、現在いくつか変更が入っています。細かい型名やメソッド名が変わったものは除いて、大きめの変更点をまとめました。

Observerの追加

以前までは、メトリックの種類は

  • Measure - 複数の値を記録するもの(例: HTTPハンドラのレイテンシ)
  • Gauge - 最新の値だけ分かればいいもの(例: メモリ利用率)
  • Count - カウンタ(例: GC回数)

の3種類でしたが、ここからGaugeがなくなって、代わりにObserverが追加されました。ドキュメントによると、Gaugeが使われるケースはOSやインフラなどの値を取得することが多く、この処理は(単純な計算に比べて)コストが高いので、非同期に行えるようにしたようです。

使い方は今までのものと少し異なり、事前に関数を登録しておきます。

import (
    "runtime"

    "go.opentelemetry.io/otel/api/global"
    "go.opentelemetry.io/otel/api/metric"
    "go.opentelemetry.io/otel/api/unit"
)

meter := global.MeterProvider().Meter("example/ping")
meter.RegisterInt64Observer("runtime.memory.alloc", func(result metric.Int64ObserverResult) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    result.Observe(int64(m.Alloc), labels...)
}, metric.WithUnit(unit.Bytes))

こうしておくと、OpenTelemetryのSDKはCheckpoint*1に到達するたびに、登録しておいた関数を暗黙的に実行して、result.Observeで返した値をExporterへ渡してくれるようになります。result.Observeはラベルが異なれば別の値として扱うため、1つの関数で何回呼び出しても構いません。これらの使い方以外は、Observerは使い方以外はGaugeと同じで、最終値しか取れません。また、登録した関数を解除する方法はありません。

ところで、上で挙げた例のruntime.MemStatsは1回の取得で複数の値を持っています。例えばAlloc, HeapAllocなどメモリの値をそれぞれ別のメトリックで扱いたい場合は、メトリックの数だけReadMemStatsが呼ばれてしまって効率が悪くなります。この点について現在issueが上がっているので、おそらく近いうちにまた変更が入るでしょう。

リソースがラベルから分離

今までは、メトリックにラベルをつける場合、

counter := meter.NewInt64Counter(...)
counter.Add(ctx, 1, labels)

のように、計測するときに全て渡すことしかできませんでした。Bindで設定しておくことは可能ですが、それでも一括で設定しかできませんでした。ですが一般的に、インスタンスIDやホスト名などのラベルは、メトリックにかかわらず全て一定で変化がありません。こういった、リソースを示すためのラベルを一括して設定できるようになりました。

以下はexporters/metric/stdoutの場合ですが、リソースに対応しているExporterは、Exporterの初期化を行う前後になんらかの方法でリソースを渡す方法があるんじゃないかなと思います。

import (
    "go.opentelemetry.io/otel/api/key"
    "go.opentelemetry.io/otel/exporters/metric/stdout"
    "go.opentelemetry.io/otel/sdk/metric/controller/push"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/resource/resourcekeys"
)

pusher, err := stdout.InstallNewPipeline(stdout.Config{}, push.WithResource(resource.New(
    key.String(resourcekeys.HostKeyID, "1-2-3-4"),
    key.String(resourcekeys.HostKeyName, "localhost"),
)))

今まで通り、メトリックを記録する時にもラベルを設定することができます。リソースまたはメトリックで設定したラベルは、Exporterには

  1. リソースとして扱うラベル
  2. メトリックに紐づくラベル

のように分かれて渡されます。これら2種類のラベルをどのように扱うかはExporterの実装依存となりますが、おそらく多くの実装ではこれらをマージして扱うんじゃないかなと思います。

*1:Exporterがバックエンドに送る周期