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.)
and the element type of the list given to
Meter.register_callback/5.
Unified API + SDK struct
A single struct shared by both API and SDK layers rather
than an API-handle + SDK-record split. The meter field
carries the {module, config} dispatcher so the
instrument is a self-sufficient handle; recording
resolves the SDK module from instrument.meter without
an auxiliary 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 its instruments_tab
ETS table. Stateless helpers that pure-pattern-match on
kind or name (downcased_name/1,
default_temporality_mapping/0, 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 diverges
in two places:
monotonic?/1on Histogram — erlangis_monotonic(#instrument{kind=histogram}) -> true; we returnfalse. Our callsite isOtel.SDK.Metrics.MetricReader, which forwards the value to OTLP'sSum.is_monotonicfield (metrics.proto).is_monotonicis 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.- Sync Gauge support — erlang's API was written
before sync Gauge was added to
metrics/api.md, so itstemporality/1equivalent omits:gauge. Ourdefault_temporality_mapping/0includes it mapping to:cumulative(the natural temporality for absolute-value readings).
Public API
| Function | Role |
|---|---|
downcased_name/1 | SDK (SDK helper) — case-insensitive comparison key (sdk.md L945-L958 MUST) |
default_temporality_mapping/0 | SDK (SDK helper) — OTLP default export temporality preference |
monotonic?/1 | SDK (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.mdL178-L278 - OTel Metrics API §Synchronous Instrument API:
opentelemetry-specification/specification/metrics/api.mdL302-L348 - OTel Metrics API §Enabled (no required parameters):
opentelemetry-specification/specification/metrics/api.mdL485-L487 - OTel Metrics SDK §Duplicate instrument registration:
opentelemetry-specification/specification/metrics/sdk.mdL904-L958 - OTel Metrics Data Model §Temporality:
opentelemetry-specification/specification/metrics/data-model.mdL400-L465 - 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).
Options accepted by Meter.create_counter/3,
create_histogram/3, create_gauge/3,
create_updown_counter/3, and the three observable
create_*/3 variants. Keys follow
metrics/api.md §Synchronous Instrument API L302-L348
(and the equivalent §Asynchronous Instrument API
L350-L472).
Options accepted by per-instrument enabled?/2 and by
Meter.enabled?/2.
Instrument kind. Enumerated per metrics/api.md
§Synchronous and Asynchronous instruments (L279-L300).
Options accepted by Meter.register_callback/5. The
spec does not define required keys; kept as an open
keyword list for future SDK-specific extensions.
An Instrument struct (spec metrics/api.md §Instrument,
L178-L198).
Aggregation temporality per metrics/data-model.md
§Temporality (L400-L465).
Functions
SDK (SDK helper) — default export-time temporality preference per instrument kind.
SDK (SDK helper) — case-insensitive comparison key.
SDK (SDK helper) — whether the given instrument
kind's Sum aggregation is monotonic.
Types
@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.
Options accepted by Meter.create_counter/3,
create_histogram/3, create_gauge/3,
create_updown_counter/3, and the three observable
create_*/3 variants. Keys follow
metrics/api.md §Synchronous Instrument API L302-L348
(and the equivalent §Asynchronous Instrument API
L350-L472).
@type enabled_opts() :: keyword()
Options accepted by per-instrument enabled?/2 and by
Meter.enabled?/2.
Spec metrics/api.md L485-L487 declares "There are
currently no required parameters for this API.
Parameters can be added in the future, therefore, the
API MUST be structured in a way for parameters to be
added." — this type is therefore kept open as
keyword() per .claude/rules/code-conventions.md
§Layer independence, which forbids enumerating
speculative SDK keys at the API layer when the spec
leaves the set unspecified.
@type kind() ::
:counter
| :histogram
| :gauge
| :updown_counter
| :observable_counter
| :observable_gauge
| :observable_updown_counter
Instrument kind. Enumerated per metrics/api.md
§Synchronous and Asynchronous instruments (L279-L300).
@type primitive_any() :: primitive() | [primitive_any()] | %{required(String.t()) => primitive_any()}
@type register_callback_opts() :: keyword()
Options accepted by Meter.register_callback/5. The
spec does not define required keys; kept as an open
keyword list for future SDK-specific extensions.
@type t() :: %Otel.API.Metrics.Instrument{ advisory: advisory(), description: String.t(), kind: kind(), meter: Otel.API.Metrics.Meter.t(), name: String.t(), scope: Otel.API.InstrumentationScope.t(), unit: String.t() }
An Instrument struct (spec metrics/api.md §Instrument,
L178-L198).
Fields:
meter— the{module, config}dispatcher tuple returned byMeterProvider.get_meter/1. TypedMeter.t() | nilto allow thedefstructdefault; callers flowing throughMeter.create_*always receive a struct with the meter populated, and downstream recording relies on this.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— theInstrumentationScopethe 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.
@type temporality() :: :cumulative | :delta
Aggregation temporality per metrics/data-model.md
§Temporality (L400-L465).
:cumulative— running total from the start of the recording session:delta— increments since the last export cycle
See default_temporality_mapping/0 for the per-kind
OTLP default. The SDK may convert between temporalities
at export time per the reader's preference
(data-model.md §Sums: Delta-to-Cumulative).
Functions
@spec default_temporality_mapping() :: %{required(kind()) => temporality()}
SDK (SDK helper) — default export-time temporality preference per instrument kind.
Returns %{kind() => :cumulative} for every kind —
matching the OTLP Exporter default preference
(TemporalityPreference::Cumulative). Individual
MetricReaders MAY override this mapping via their
own configuration.
Not to be confused with the natural temporality from
data-model.md §Temporality (synchronous Counter,
Histogram, UpDownCounter are naturally delta at the
aggregation step). The export-time preference is what
hits the wire; the SDK converts delta-aggregated state
to cumulative at export if the reader prefers
cumulative.
SDK (SDK helper) — case-insensitive comparison key.
Returns the lowercased instrument name. Used by
Otel.SDK.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."
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
and Observable Counter aggregate as a monotonic Sum;
other kinds either do not aggregate as Sum at all
(Histogram, Gauge) or allow decrements (UpDownCounter,
Observable UpDownCounter).
See ## Divergences from opentelemetry-erlang in the
module docs for why this is narrower than erlang's
is_monotonic.