Skip to content

[Tracking] Experimental Bound Instrument Support #8759

Description

@dashpole

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

  • Define the *Binder interfaces in metric/x/bound.go.

Stage 2: Aggregator Bound Handles in sdk/metric/internal/aggregate

  • Add Bind(lazy) support across all aggregation types (sum, histogram, lastvalue, precomputed).
  • Add unit tests and microbenchmarks in sdk/metric/internal/aggregate.

Stage 3: SDK Pipeline Fan-Out, Benchmarks, & Examples

  • Implement *Binder interfaces on SDK instruments (int64Counter, float64Histogram, etc.).
  • Fan out Bind() across all registered pipelines/readers to return composite bound instruments.
  • Add end-to-end benchmarks in sdk/metric/benchmark_test.go comparing unbound vs bound paths.
  • Add usage examples in metric/x/example_test.go.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions