@@ -16,6 +16,7 @@ Status: **Work-In-Progress**
1616 instruments] ( #reporting-measurements-via-synchronous-instruments )
1717 * [ Reporting measurements via asynchronous
1818 instruments] ( #reporting-measurements-via-asynchronous-instruments )
19+ * [ Bound Instruments (Experimental)] ( #bound-instruments-experimental )
1920* [ MeterProvider Management] ( #meterprovider-management )
2021* [ Memory Management] ( #memory-management )
2122 * [ Example] ( #example )
@@ -29,7 +30,7 @@ Status: **Work-In-Progress**
2930* [ Metrics Correlation] ( #metrics-correlation )
3031* [ Modelling Metric Attributes] ( #modelling-metric-attributes )
3132* [ Common Issues Leading to Missing
32- Metrics] ( #common-issues-that-lead-to-missing-metrics )
33+ Metrics] ( #common-issues-that-lead-to-missing-metrics ) a
3334
3435</details >
3536
@@ -241,6 +242,95 @@ fn setup_metrics(meter: &opentelemetry::metrics::Meter) {
241242> [ !NOTE] The callbacks in the Observable instruments are invoked by the SDK
242243> during each export cycle.
243244
245+ ### Bound Instruments (Experimental)
246+
247+ > [ !IMPORTANT] Bound instruments are experimental. Enable
248+ > ` experimental_metrics_bound_instruments ` on ** both** ` opentelemetry ` and
249+ > ` opentelemetry_sdk ` — if only the API crate has it enabled, ` bind() ` calls
250+ > compile but measurements are silently dropped. Enabling it on
251+ > ` opentelemetry_sdk ` transitively enables it on ` opentelemetry ` , so for typical
252+ > apps using the SDK, enabling it once on ` opentelemetry_sdk ` is sufficient.
253+ > The API may change in future releases.
254+
255+ When reporting measurements, the SDK must resolve the provided attributes to
256+ the internal aggregation state (the "tracker") for that attribute combination
257+ on every call. For hot paths that repeatedly emit measurements with the * same*
258+ attribute set, this per-call lookup can be avoided by pre-binding the
259+ attributes once and reusing the resulting bound handle.
260+
261+ Bound instruments are currently supported on ` Counter ` and ` Histogram ` via the
262+ ` bind ` method, which returns a ` BoundCounter<T> ` or ` BoundHistogram<T> `
263+ respectively. Subsequent ` add ` / ` record ` calls on the bound handle skip
264+ attribute resolution and write directly to the pre-resolved tracker.
265+
266+ :heavy_check_mark : Use bound instruments on hot paths where the same attribute
267+ set is used repeatedly, and store the bound handle for reuse.
268+
269+ ``` rust
270+ // Requires the `experimental_metrics_bound_instruments` feature on the
271+ // `opentelemetry_sdk` crate (which transitively enables it on `opentelemetry`).
272+ use opentelemetry :: KeyValue ;
273+ use opentelemetry :: global;
274+
275+ let meter = global :: meter (" my_company.my_product.my_library" );
276+ let packets = meter . u64_counter (" packets_processed" ). build ();
277+
278+ // Bind once at initialization, for each well-known protocol.
279+ let tcp_packets = packets . bind (& [KeyValue :: new (" protocol" , " tcp" )]);
280+ let udp_packets = packets . bind (& [KeyValue :: new (" protocol" , " udp" )]);
281+
282+ // On the hot path, the call site already knows which handle to use —
283+ // no attribute lookup is performed.
284+ tcp_packets . add (1 );
285+ udp_packets . add (1 );
286+ ```
287+
288+ ` BoundCounter ` and ` BoundHistogram ` are cheap to clone, so a single bound
289+ handle can be shared across threads or modules without re-binding. The
290+ underlying tracker is reclaimed when the last clone is dropped.
291+
292+ For reference, the SDK's ` bound_instruments ` benchmark on an Apple M4 Max with
293+ 3 attributes (` method ` , ` status ` , ` path ` ) reports:
294+
295+ | Operation | Unbound | Bound | Speedup |
296+ | ----------------------------| --------- | --------- | ------- |
297+ | ` Counter::add ` (Delta) | ~ 50 ns | ~ 1.8 ns | ~ 28x |
298+ | ` Histogram::record ` (Delta) | ~ 58 ns | ~ 6.5 ns | ~ 9x |
299+
300+ The exact win depends on attribute count, contention, and the cost of the
301+ lookup being skipped. See
302+ [ ` opentelemetry-sdk/benches/bound_instruments.rs ` ] ( ../opentelemetry-sdk/benches/bound_instruments.rs )
303+ to reproduce.
304+
305+ :stop_sign : Do NOT call ` bind ` on the hot path. A single ` bind ` + ` add ` is
306+ * more* expensive than a plain ` add ` — it does the same attribute lookup, plus
307+ extra allocations for the bound handle. The performance win comes only from
308+ binding once and reusing the returned handle for many measurements.
309+
310+ :stop_sign : Do NOT use bound instruments when the attribute set varies per
311+ measurement. Each unique attribute set requires its own bound handle, and
312+ holding many rarely-used handles wastes memory without any throughput benefit.
313+
314+ :stop_sign : Do NOT bind everything by default. Bound instruments are intended
315+ for a small number of well-known, high-frequency attribute combinations on hot
316+ paths. Binding broadly — especially storing handles in a user-managed map
317+ keyed by attributes — recreates the SDK's per-call lookup in your own code and
318+ just shifts the problem from the SDK to the application, often with worse
319+ results. Stick to the unbound ` add ` / ` record ` API unless you have a clear
320+ hot-path case backed by measurements.
321+
322+ > [ !NOTE] Many common web-app metrics (RED/SLO style: request count, latency,
323+ > errors broken down by route, method, customer, etc.) involve highly variable
324+ > attribute values and are ** not** a good fit for bound instruments. Bound
325+ > instruments shine when each call site already knows its full attribute set
326+ > at compile time / startup, so handles can be stored directly alongside the
327+ > code that uses them — no per-call map lookup needed.
328+
329+ > [ !NOTE] Bound instruments do not change the SDK's [ cardinality
330+ > limits] ( #cardinality-limits ) or pre-aggregation behavior. They are purely a
331+ > performance optimization that caches the resolved tracker for a fixed
332+ > attribute set.
333+
244334## MeterProvider Management
245335
246336Most use-cases require you to create ONLY one instance of MeterProvider. You
0 commit comments