Skip to content

PoC: Add experimental support for bound sum instruments#8278

Closed
dashpole wants to merge 1 commit into
open-telemetry:mainfrom
dashpole:new_bound_instruments_poc
Closed

PoC: Add experimental support for bound sum instruments#8278
dashpole wants to merge 1 commit into
open-telemetry:mainfrom
dashpole:new_bound_instruments_poc

Conversation

@dashpole
Copy link
Copy Markdown
Collaborator

@dashpole dashpole commented Apr 29, 2026

Supersedes #7790

This implements a PoC for open-telemetry/opentelemetry-specification#5050, with some divergences (to try and make this more ergonomic):

  • Returns a metric.Float64Counter, which has an Add function that also accepts attributes. The current proposal does not allow the Add function for a bound instrument to also accept attributes.

Differences compared to the previous prototype:

  • The interface is defined in metric/x instead of in metric to avoid public API changes.
  • This now fully supports delta metrics, and includes benchmarks for delta metrics as well.

This prototype does not implement bound instruments for all instrument types (only for counters).

Benchmarks:

Note: I removed Filtered, Naive, and WithAttributes, and the 5 attribute case to make the benchmarks legible.

Overall, Bind() and Add() have zero memory allocations as long as there is a single measurement function (i.e. you don't have multiple views defined for the same instrument).

The Precomputed case, in which the attributes are known ahead of time, the runtime is very close to the underlying performance of incrementing an atomic counter on my machine (3 ns with no contention, ~25 ns) with 24 cores. In the Dynamic case, where attributes need to be computed for each call, the additional runtime cost is from hashing the incoming attributes, and performing the map lookup.

goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/metric
cpu: AMD EPYC 7B12
                                                                                     │ out_parallel.txt │
                                                                                     │      sec/op      │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound-24                   28.19n ± 14%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24        53.70n ±  8%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound-24                       56.46n ± 28%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24            128.3n ± 20%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound-24                  28.71n ± 10%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24       50.87n ±  4%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound-24                      76.17n ± 18%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24           860.0n ± 10%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound-24                        18.52n ± 16%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24             102.5n ±  8%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound-24                            70.47n ±  6%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24                 213.3n ± 11%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound-24                       18.40n ± 11%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24            97.88n ± 12%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound-24                           92.74n ±  5%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24                862.8n ± 10%
geomean                                                                                    82.16n

                                                                                     │ out_parallel.txt │
                                                                                     │       B/op       │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound-24                   0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24        0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound-24                       0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24            64.00 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound-24                  0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24       0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound-24                      0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24           708.0 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound-24                        0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24             0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound-24                            0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24                 64.00 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound-24                       0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24            0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound-24                           0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24                708.0 ± 0%
geomean                                                                                               ¹
¹ summaries must be >0 to compute geomean

                                                                                     │ out_parallel.txt │
                                                                                     │    allocs/op     │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound-24                   0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24        0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound-24                       0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24            1.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound-24                  0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24       0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound-24                      0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24           1.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound-24                        0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet-24             0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound-24                            0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24                 1.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound-24                       0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet-24            0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound-24                           0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24                1.000 ± 0%
geomean                                                                                               ¹
¹ summaries must be >0 to compute geomean

Serial Benchmarks:

goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/sdk/metric
cpu: AMD EPYC 7B12
                                                                                  │ out_serial.txt │
                                                                                  │     sec/op     │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound                 3.498n ±  2%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet      41.79n ±  8%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound                     100.4n ±  5%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet          229.6n ± 15%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound                3.482n ±  2%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet     41.00n ±  1%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound                    644.1n ±  9%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet         1.573µ ± 11%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound                      3.969n ±  9%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet           46.96n ±  7%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound                          105.9n ±  4%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet               286.5n ± 13%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound                     3.768n ±  2%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet          47.15n ±  8%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound                         645.6n ± 10%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet              1.420µ ± 12%
geomean                                                                               71.32n

                                                                                  │ out_serial.txt │
                                                                                  │      B/op      │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound                 0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet      0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound                     0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet          64.00 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound                0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet     0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound                    0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet         705.0 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound                      0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet           0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound                          0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet               64.00 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound                     0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet          0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound                         0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet              704.0 ± 0%
geomean                                                                                          ¹
¹ summaries must be >0 to compute geomean

                                                                                  │ out_serial.txt │
                                                                                  │   allocs/op    │
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/Bound                 0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Precomputed/WithAttributeSet      0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/Bound                     0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/1/Dynamic/WithAttributeSet          1.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/Bound                0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Precomputed/WithAttributeSet     0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/Bound                    0.000 ± 0%
EndToEndCounterAdd/Cumulative/NoFilter/Attributes/10/Dynamic/WithAttributeSet         1.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/Bound                      0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Precomputed/WithAttributeSet           0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/Bound                          0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/1/Dynamic/WithAttributeSet               1.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/Bound                     0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Precomputed/WithAttributeSet          0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/Bound                         0.000 ± 0%
EndToEndCounterAdd/Delta/NoFilter/Attributes/10/Dynamic/WithAttributeSet              1.000 ± 0%
geomean                                                                                          ¹
¹ summaries must be >0 to compute geomean

Most of this was written by Gemini.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 78.54985% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.8%. Comparing base (468c62d) to head (47b702c).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
sdk/metric/internal/aggregate/sum.go 72.5% 44 Missing and 10 partials ⚠️
sdk/metric/instrument.go 58.8% 14 Missing ⚠️
attribute/set.go 89.4% 1 Missing and 1 partial ⚠️
sdk/metric/pipeline.go 97.2% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##            main   #8278     +/-   ##
=======================================
- Coverage   82.9%   82.8%   -0.1%     
=======================================
  Files        314     314             
  Lines      24911   25209    +298     
=======================================
+ Hits       20661   20889    +228     
- Misses      3878    3937     +59     
- Partials     372     383     +11     
Files with missing lines Coverage Δ
sdk/metric/internal/aggregate/aggregate.go 100.0% <100.0%> (ø)
sdk/metric/internal/aggregate/atomic.go 91.0% <100.0%> (+0.1%) ⬆️
...metric/internal/aggregate/exponential_histogram.go 100.0% <100.0%> (ø)
sdk/metric/internal/aggregate/histogram.go 100.0% <100.0%> (ø)
sdk/metric/internal/aggregate/lastvalue.go 100.0% <100.0%> (ø)
sdk/metric/meter.go 92.3% <100.0%> (ø)
sdk/metric/pipeline.go 90.6% <97.2%> (+0.3%) ⬆️
attribute/set.go 81.1% <89.4%> (+0.9%) ⬆️
sdk/metric/instrument.go 86.3% <58.8%> (-11.3%) ⬇️
sdk/metric/internal/aggregate/sum.go 83.6% <72.5%> (-16.4%) ⬇️

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@NesterovYehor
Copy link
Copy Markdown
Contributor

The documentation on Delta aggregation temporality mentions that the SDK should be able to 'forget about things that are no longer needed' to control memory usage.
Could you clarify if an aggregator can be fully eliminated (deleted or evicted) from the internal state if it isn't used for a while? If that happens, what is the impact on the direct pointer held by a bound instruments?

@dashpole
Copy link
Copy Markdown
Collaborator Author

dashpole commented May 7, 2026

Could you clarify if an aggregator can be fully eliminated (deleted or evicted) from the internal state if it isn't used for a while? If that happens, what is the impact on the direct pointer held by a bound instruments?

In this PoC, it can be completely eliminated. But in the current specification PR for bound instruments, that is not the case.

cijothomas pushed a commit to cijothomas/opentelemetry-specification that referenced this pull request May 8, 2026
Resolves open-telemetry#4126.

See issue for full details, but some of the key bits:

- Adds new optional "bind" capability to synchronous instruments
- Marked as "in development"
- SDK implementation requirements phrase it as "A bound instrument MUST
behave identically to calling the equivalent unbound recording operation
with the pre-bound Attributes on each measurement". That is, the same
behavior with respect to cardinality limits, exemplars, and anything
else relevant. Since attributes are pre-bound, this excludes attributes
processing from views, which is done at bind time.

There have been a few prototypes built:

- Java: open-telemetry/opentelemetry-java#8314
- Rust: open-telemetry/opentelemetry-rust#3421
- Go: open-telemetry/opentelemetry-go#8278

I'm particularly interested to here from @dashpole and @cijothomas if
this aligns with their prototypes and ideas.

Other interest has been expressed in .NET and Erlang, but no prototypes
that I'm aware of.
@dashpole dashpole closed this May 10, 2026
@dashpole
Copy link
Copy Markdown
Collaborator Author

I'll work on a new implementation that is spec compliant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants