YAMAGUCHI::weblog

噛み付き地蔵に憧れて、この神の世界にやってきました。マドンナみたいな男の子、コッペです。

OpenTelemetryでSpanのレイテンシーを取得する

はじめに

分散トレースでリクエスト全体の中でのボトルネックを発見すると同時に、ユーザーに対するSLOとしてトレース全体のレイテンシーを取得することもあると思います。その場合OpenTelemetryではすんなりとルートSpanのレイテンシーを取得できないため、その方法をメモしておきます。

バージョン

通常の計装

まずこういう典型的な計装があったとします。

func sleepHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("handler.sleep")
    ctx := context.Background()
    ctx, span := tracer.Start(ctx, "request.sleep")
    defer span.End()
    ...(なにかする)...
}

この span にかかったレイテンシーを取得したいとします。しかし go.opentelemetry.io/otel/trace.Span には開始時刻と終了時刻のタイムスタンプを取得するメソッドが定義されていません。

しかし直感でも分かる通り、また仕様でも定義されている通り、Spanはその開始時刻と終了時刻を保持しているので、アクセスするためのインターフェースがどこかにあるはずです。

github.com

tracesdk.ReadOnlySpanに変換する

で、よくコードを読んでみると go.opentelemetry.io/otel/sdk/trace.ReadOnlySpan にはそのためのインターフェースが定義されています。

pkg.go.dev

したがって、上記計装から次のようにすれば spanレイテンシーを取得できます。

func sleepHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("handler.sleep")
    ctx := context.Background()
    ctx, span := tracer.Start(ctx, "request.sleep")
    ...(なにかする)...
    span.End()
    ro := span.(tracesdk.ReadOnlySpan)
    start, end := ro.StartTime(), ro.EndTime()
    duration := end - start
    ...(durationをメトリクスとしてバックエンドに送る)...
}

注意することとしては span.End() を先に呼んでいないとspanのendtimeが打刻されないので、defer span.End() にしていた部分は何かしら書き換えないといけないということです。

他の言語でも同様

上記の例はすべてGoで書いていたけれど、OpenTelemetryは仕様が全言語でインターフェースレベルで共通で実装されているので、おおよそこういった実装は他の言語でも共通になっています。

2022.06.15 追記

OpenTelemetryの仕様 によると、No-op Tracerの場合はそもそもReadOnlySpanにキャストできない様子なので、その場合の対応は考える必要がありそうです。