blog.anqou.net
rss
author
tags

ocaml-opentelemetry で attribute つきのメトリクスを出す

以前の記事で紹介したとおり ocaml-opentelemetry を使うと OCaml で OpenTelemetry のメトリクスを出力することができます。

OpenTelemetry ではメトリクスに attribute を付与することができ[1]、Prometheus のラベルのように使えます。 ocaml-opentelemetry で用意されている Opentelemetry.Instrument.Int_gauge などの定義済み instrument には attribute のサポートがありません。そのため attribute を出力したい場合は自分で instrument を定義する必要があります。

前回同様、ocaml-opentelemetry の v0.91 と Eio 0.3 を対象とし、attribute を出力できる Int_gauge を作ってみます。カスタムの instrument を作るには Opentelemetry.Instrument.Make ファンクタを使います:

module Int_gauge = struct
  open Opentelemetry

  include Instrument.Make (struct
    type data = key_value list * int option
    type state = { mtx : Eio.Mutex.t; data : (key_value list, int) Hashtbl.t }

    let kind = "gauge"
    let init () = { mtx = Eio.Mutex.create (); data = Hashtbl.create 0 }

    let update { mtx; data } (k, v) =
      Eio.Mutex.use_rw ~protect:true mtx @@ fun () ->
      match v with Some v -> Hashtbl.replace data k v | None -> Hashtbl.remove data k

    let to_metrics { mtx; data } ~name ?description ?unit_ ~clock () =
      let now = Clock.now clock in
      match
        Eio.Mutex.use_ro mtx @@ fun () ->
        Hashtbl.to_seq data |> Seq.map (fun (attrs, value) -> Metrics.int ~attrs ~now value) |> List.of_seq
      with
      | [] -> []
      | datapoints -> [ Metrics.gauge ~name ?description ?unit_ datapoints ]
  end)

  let record (instrument : (key_value list * int option) Instrument.t) k v = instrument.update (k, Some v)
  let unrecord (instrument : (key_value list * int option) Instrument.t) k = instrument.update (k, None)
end

メトリクスの定義は通常の instrument と同様です:

let your_first_metric =
  Int_gauge.create
    ~name:"your_first_metric"
    ~description:"Your first metric description"
    ()

メトリクスを出力する際も通常と同様に Int_guage.record を使いますが、このときに attribute の内容を指定します。attribute の型は Opentelemetry.key_value list になっていて、これは(定義を展開すると)以下のようになっています(参考):

type key_value =
  (string * [
    | `Int of int
    | `String of string
    | `Bool of bool
    | `Float of float
    | `None
  ])

例えば key1"foo" が紐づく attribute を出力する場合は以下のように書きます:

Int_gauge.record your_first_metric [("key1", `String "foo")] 1;

また、特定の attribute を持つメトリクスを削除するための Int_gauge.unrecord も定義されています:

Int_gauge.unrecord your_first_metric [("key1", `String "foo")];

例によって、動作確認は Docker Compose で Opentelemetry Collector と Prometheus を立ち上げて行うと便利です。以下のように compose.yaml を書きます:

services:
  your-system:
    image: your-system
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318

  otel-collector:
    image: otel/opentelemetry-collector
    volumes:
      - otel-collector-config.yaml:/etc/otelcol/config.yaml

  prometheus:
    image: prom/prometheus:v3.11.3-distroless
    command:
      - --web.enable-otlp-receiver
    ports:
      - "59090:9090"

otel-collector-config.yaml は以下のように書いておきます:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed
  otlphttp/prometheus:
    endpoint: "http://prometheus:9090/api/v1/otlp"

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]
    metrics:
      receivers: [otlp]
      exporters: [debug, otlphttp/prometheus]
    logs:
      receivers: [otlp]
      exporters: [debug]

http://localhost:59090 にブラウザでアクセスすると Prometheus の Web UI が開くので、メトリクスが出力されていることを確認できます。

注釈

  1. さっぱり詳しくないのですが OpenTelemetry の仕様を読むと attribute はメトリクスに限らず Span などの他のものにも使える共通の仕組みっぽいです。