Requirements
This issue tracks implementing the OpenTelemetry Metrics Specification: Bind API and Metrics SDK: Bound Instruments.
API Requirements
"The Bind API is an optimization for synchronous instruments. It allows a user to bind an instrument to a specific set of attributes, returning an object that can be used to record measurements with those attributes."
"The API MUST accept the following parameters:
- A set of Attributes to associate with every measurement recorded using the returned bound instrument."
"The API MUST return an instrument bound to the specified attributes. The returned bound instrument MUST provide the same recording methods as the unbound instrument."
"If the language allows the recording methods on the bound instrument to accept attributes, the API documentation MUST explain that passing attributes to these methods negates the performance benefits of using a bound instrument."
SDK Requirements
"A bound instrument MUST behave identically to calling the equivalent recording operation with the bound attributes."
"Attribute processing and cardinality limit evaluation MUST be performed at bind time."
"Each call to Bind MUST be independently evaluated against the cardinality state at the time of the call."
Interfaces
API Interfaces in go.opentelemetry.io/otel/metric/x
Callers use standard Go type assertions to detect if an instrument supports binding:
if b, ok := counter.(x.Int64CounterBinder); ok {
boundCounter := b.Bind(attrs...)
boundCounter.Add(ctx, 1)
}
The 8 binder interfaces:
package x
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
type Int64CounterBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Int64Counter
}
type Float64CounterBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Float64Counter
}
type Int64UpDownCounterBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Int64UpDownCounter
}
type Float64UpDownCounterBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Float64UpDownCounter
}
type Int64HistogramBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Int64Histogram
}
type Float64HistogramBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Float64Histogram
}
type Int64GaugeBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Int64Gauge
}
type Float64GaugeBinder interface {
Bind(attrs ...attribute.KeyValue) metric.Float64Gauge
}
2. Internal Aggregator Bound Handles (sdk/metric/internal/aggregate)
Aggregators resolve attributes once during Bind(lazy) and return direct measurement handles:
- Cumulative Aggregators: Retain a direct pointer to the atomic accumulator (
*sumValue[N], *histogramPoint[N], *lastValuePoint[N]). Recording is a single atomic operation with zero map lookups.
- Delta Aggregators: Retain pre-allocated pointers to both maps (
[2]*sumValue[N]). Recording queries the active index via start() and writes directly to handle.vals[hotIdx], skipping map lookups entirely while preserving lock-free double buffering.
Implementation Roadmap & Stages
3-Stage Roadmap
Stage 1: Define Binder Interfaces in metric/x
Stage 2: Aggregator Bound Handles in sdk/metric/internal/aggregate
Stage 3: SDK Pipeline Fan-Out, Benchmarks, & Examples
Requirements
This issue tracks implementing the OpenTelemetry Metrics Specification: Bind API and Metrics SDK: Bound Instruments.
API Requirements
SDK Requirements
Interfaces
API Interfaces in
go.opentelemetry.io/otel/metric/xCallers use standard Go type assertions to detect if an instrument supports binding:
The 8 binder interfaces:
2. Internal Aggregator Bound Handles (
sdk/metric/internal/aggregate)Aggregators resolve attributes once during
Bind(lazy)and return direct measurement handles:*sumValue[N],*histogramPoint[N],*lastValuePoint[N]). Recording is a single atomic operation with zero map lookups.[2]*sumValue[N]). Recording queries the active index viastart()and writes directly tohandle.vals[hotIdx], skipping map lookups entirely while preserving lock-free double buffering.Implementation Roadmap & Stages
3-Stage Roadmap
Stage 1: Define Binder Interfaces in
metric/x*Binderinterfaces inmetric/x/bound.go.Stage 2: Aggregator Bound Handles in
sdk/metric/internal/aggregateBind(lazy)support across all aggregation types (sum,histogram,lastvalue,precomputed).sdk/metric/internal/aggregate.Stage 3: SDK Pipeline Fan-Out, Benchmarks, & Examples
*Binderinterfaces on SDK instruments (int64Counter,float64Histogram, etc.).Bind()across all registered pipelines/readers to return composite bound instruments.sdk/metric/benchmark_test.gocomparing unbound vs bound paths.metric/x/example_test.go.