Otel.Metrics.Instrument (otel v0.4.1)

Copy Markdown View Source

Instrument handle (OTel metrics/api.md §Instrument, Status: Stable, L178-L278).

Carries the meter dispatcher plus all Instrument fields defined by the spec (name, kind, unit, description, advisory) plus scope (the InstrumentationScope the instrument was created under). Per spec L190-L191 the identifying fields are exactly {name, kind, unit, description}; advisory is a field on the Instrument but not part of the identity set, and scope is carried for downstream pipeline association rather than identity.

The instrument is the handle users pass to recording functions (Counter.add/3, Histogram.record/3, etc.).

Unified API + SDK struct

A single struct shared by both API and SDK layers rather than an API-handle + SDK-record split. Resolution fields (aggregation_module, aggregation_opts, cardinality_limit, exemplar_reservoir) are filled at registration time from :kind and :advisory so the recording hot path needs no further lookup. Spec metrics/api.md L190-L191 treats Instrument as a single concept; erlang's reference implementation likewise defines one #instrument{} record shared across its API and SDK.

The SDK stores the same struct in Otel.Metrics.InstrumentsStorage. Stateless helpers that pure-pattern-match on kind or name (downcased_name/1, monotonic?/1) colocate here because they are pure data transformations with no runtime SDK coupling — matching the erlang reference, which places the same helpers on its API-layer otel_instrument.

Divergences from opentelemetry-erlang

opentelemetry-erlang's otel_instrument.erl defines is_monotonic(#instrument{kind=histogram}) -> true; we return false. Our callsite is Otel.Metrics.MetricExporter.collect/1, which forwards the value to OTLP's Sum.is_monotonic field (metrics.proto). is_monotonic is a Sum-aggregation predicate; Histogram datapoints are not Sum datapoints, so the narrower definition matches that OTLP context. Erlang's wider reading is spec-ambiguous and not directly harmful in its own callsites, but would be wrong at ours.

Public API

FunctionRole
downcased_name/1SDK (SDK helper) — case-insensitive comparison key (sdk.md L945-L958 MUST)
monotonic?/1SDK (SDK helper) — OTLP Sum is_monotonic for a given kind

All functions are safe for concurrent use.

References

  • OTel Metrics API §Instrument: opentelemetry-specification/specification/metrics/api.md L178-L278
  • OTel Metrics API §Synchronous Instrument API: opentelemetry-specification/specification/metrics/api.md L302-L348
  • OTel Metrics API §Enabled (no required parameters): opentelemetry-specification/specification/metrics/api.md L485-L487
  • OTel Metrics SDK §Duplicate instrument registration: opentelemetry-specification/specification/metrics/sdk.md L904-L958
  • Reference impl: opentelemetry-erlang/apps/opentelemetry_api_experimental/src/otel_instrument.erl

Summary

Types

Advisory parameters accepted by Meter.create_*, per metrics/api.md §Instrument advisory parameters (L245-L277).

Aggregation module backing the instrument. Set to one of the project's three sync aggregation implementations, derived from :kind at construction time.

Options forwarded to aggregation_module.aggregate/4 and aggregation_module.collect/3. Only :boundaries (for histogram) is recognised; Sum / LastValue read nothing.

Options accepted by Meter.create_counter/3, create_histogram/3, create_gauge/3, and create_updown_counter/3. Keys follow metrics/api.md §Synchronous Instrument API L302-L348.

Instrument kind. Enumerated per metrics/api.md §Synchronous instruments (L279-L300). Asynchronous (Observable) instrument kinds are intentionally absent — minikube delegates poll-based measurements to the BEAM-native :telemetry ecosystem (planned via a telemetry-handler bridge).

t()

An Instrument struct (spec metrics/api.md §Instrument, L178-L198).

Aggregation temporality per metrics/data-model.md §Temporality (L400-L465).

Functions

SDK (SDK helper) — case-insensitive comparison key.

SDK (SDK helper) — whether the given instrument kind's Sum aggregation is monotonic.

SDK — Construct an Instrument. Caller provides at least :name and (optionally) :kind / :unit / :description / :advisory; the resolution fields (aggregation_module, aggregation_opts, cardinality_limit) derive from :kind and :advisory. :scope defaults to the SDK identity. Exemplar reservoir choice lives on the aggregation module itself (aggregation_module.exemplar_reservoir/0) — there is no separate field on the instrument.

Types

advisory()

@type advisory() :: [{:explicit_bucket_boundaries, [number()]}]

Advisory parameters accepted by Meter.create_*, per metrics/api.md §Instrument advisory parameters (L245-L277).

Spec-defined Stable keys:

  • :explicit_bucket_boundaries[number()] sorted boundary list. Applies to :histogram (spec §ExplicitBucketBoundaries L260-L268, Status: Stable).

Deferred (per the project's Stable-only policy):

  • :attributes — spec §Attributes L270-L277 is Status: Development.

aggregation_module()

@type aggregation_module() ::
  Otel.Metrics.Aggregation.Sum
  | Otel.Metrics.Aggregation.LastValue
  | Otel.Metrics.Aggregation.ExplicitBucketHistogram

Aggregation module backing the instrument. Set to one of the project's three sync aggregation implementations, derived from :kind at construction time.

aggregation_opts()

@type aggregation_opts() :: %{optional(:boundaries) => [number()]}

Options forwarded to aggregation_module.aggregate/4 and aggregation_module.collect/3. Only :boundaries (for histogram) is recognised; Sum / LastValue read nothing.

create_opts()

@type create_opts() :: [
  unit: String.t(),
  description: String.t(),
  advisory: advisory()
]

Options accepted by Meter.create_counter/3, create_histogram/3, create_gauge/3, and create_updown_counter/3. Keys follow metrics/api.md §Synchronous Instrument API L302-L348.

kind()

@type kind() :: :counter | :histogram | :gauge | :updown_counter

Instrument kind. Enumerated per metrics/api.md §Synchronous instruments (L279-L300). Asynchronous (Observable) instrument kinds are intentionally absent — minikube delegates poll-based measurements to the BEAM-native :telemetry ecosystem (planned via a telemetry-handler bridge).

primitive()

@type primitive() ::
  String.t() | {:bytes, binary()} | boolean() | integer() | float() | nil

primitive_any()

@type primitive_any() ::
  primitive() | [primitive_any()] | %{required(String.t()) => primitive_any()}

t()

@type t() :: %Otel.Metrics.Instrument{
  advisory: advisory(),
  aggregation_module: aggregation_module(),
  aggregation_opts: aggregation_opts(),
  cardinality_limit: pos_integer(),
  description: String.t(),
  kind: kind(),
  name: String.t(),
  scope: Otel.InstrumentationScope.t(),
  unit: String.t()
}

An Instrument struct (spec metrics/api.md §Instrument, L178-L198).

Fields:

  • name — spec §Instrument name syntax (L201-L218). Identifying.
  • kind — spec §Synchronous and Asynchronous instruments (L279-L300). Identifying.
  • unit — spec §Instrument unit (L220-L230). Identifying.
  • description — spec §Instrument description (L232-L243). Identifying.
  • advisory — spec §Instrument advisory parameters (L245-L277). Not part of identity.
  • scope — the InstrumentationScope the instrument was created under. Not part of identity; carried for downstream pipeline association.

Per spec L190-L191 "Instruments are identified by the name, kind, unit, and description" — identifying fields are exactly those four.

temporality()

@type temporality() :: :cumulative | :delta

Aggregation temporality per metrics/data-model.md §Temporality (L400-L465).

Minikube hardcodes :cumulative for every kind (matching the OTLP exporter default TemporalityPreference::Cumulative). The :delta member of the union remains so SDK consumers that read OTLP-derived Sum.aggregation_temporality can type-match the spec, but no aggregator emits delta datapoints internally.

Functions

downcased_name(name)

@spec downcased_name(name :: String.t()) :: String.t()

SDK (SDK helper) — case-insensitive comparison key.

Returns the lowercased instrument name. Used by Otel.Metrics.Meter to key the ETS instruments and streams tables so spec metrics/sdk.md L945-L958 MUST is observed: "The name of an Instrument is defined to be case-insensitive. If an SDK uses a case-sensitive encoding to represent this name, a duplicate instrument registration will occur when a user passes multiple casings of the same name."

monotonic?(arg1)

@spec monotonic?(kind :: kind()) :: boolean()

SDK (SDK helper) — whether the given instrument kind's Sum aggregation is monotonic.

Maps to OTLP's Sum.is_monotonic field (opentelemetry-proto metrics.proto). Only Counter aggregates as a monotonic Sum; other kinds either do not aggregate as Sum at all (Histogram, Gauge) or allow decrements (UpDownCounter).

See ## Divergences from opentelemetry-erlang in the module docs for why this is narrower than erlang's is_monotonic.

new(opts \\ %{})

@spec new(opts :: map()) :: t()

SDK — Construct an Instrument. Caller provides at least :name and (optionally) :kind / :unit / :description / :advisory; the resolution fields (aggregation_module, aggregation_opts, cardinality_limit) derive from :kind and :advisory. :scope defaults to the SDK identity. Exemplar reservoir choice lives on the aggregation module itself (aggregation_module.exemplar_reservoir/0) — there is no separate field on the instrument.