From 5eb48c52fba9082968e4cd78dbb10d6f3f453240 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 8 Jan 2026 20:24:16 +0100 Subject: [PATCH 1/4] metric: add Enabled method to synchronous instruments --- CHANGELOG.md | 5 +++ internal/global/instruments.go | 56 +++++++++++++++++++++++++++++ internal/global/instruments_test.go | 8 +++++ metric/noop/noop.go | 24 +++++++++++++ metric/syncfloat64.go | 24 +++++++++++++ metric/syncint64.go | 24 +++++++++++++ sdk/metric/instrument.go | 3 -- sdk/metric/internal/x/README.md | 19 ---------- sdk/metric/internal/x/x.go | 12 ------- sdk/metric/meter_test.go | 21 ++++------- 10 files changed, 148 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b863c47c3a8..4318aae8d2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Add `Enabled` method to all synchronous instrument interfaces (`Float64Counter`, `Float64UpDownCounter`, `Float64Histogram`, `Float64Gauge`, `Int64Counter`, `Int64UpDownCounter`, `Int64Histogram`, `Int64Gauge`,) in `go.opentelemetry.io/otel/metric`. + This stabilizes the synchronous instrument enabled feature, allowing users to check if an instrument will process measurements before performing computationally expensive operations. (#7681) + ### Changed - `Exporter` in `go.opentelemetry.io/otel/exporter/prometheus` ignores metrics with the scope `go.opentelemetry.io/contrib/bridges/prometheus`. diff --git a/internal/global/instruments.go b/internal/global/instruments.go index ae92a425166..55255cddfc6 100644 --- a/internal/global/instruments.go +++ b/internal/global/instruments.go @@ -229,6 +229,13 @@ func (i *sfCounter) Add(ctx context.Context, incr float64, opts ...metric.AddOpt } } +func (i *sfCounter) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64Counter).Enabled(ctx) + } + return false +} + type sfUpDownCounter struct { embedded.Float64UpDownCounter @@ -255,6 +262,13 @@ func (i *sfUpDownCounter) Add(ctx context.Context, incr float64, opts ...metric. } } +func (i *sfUpDownCounter) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64UpDownCounter).Enabled(ctx) + } + return false +} + type sfHistogram struct { embedded.Float64Histogram @@ -281,6 +295,13 @@ func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...metric.Reco } } +func (i *sfHistogram) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64Histogram).Enabled(ctx) + } + return false +} + type sfGauge struct { embedded.Float64Gauge @@ -307,6 +328,13 @@ func (i *sfGauge) Record(ctx context.Context, x float64, opts ...metric.RecordOp } } +func (i *sfGauge) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Float64Gauge).Enabled(ctx) + } + return false +} + type siCounter struct { embedded.Int64Counter @@ -333,6 +361,13 @@ func (i *siCounter) Add(ctx context.Context, x int64, opts ...metric.AddOption) } } +func (i *siCounter) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64Counter).Enabled(ctx) + } + return false +} + type siUpDownCounter struct { embedded.Int64UpDownCounter @@ -359,6 +394,13 @@ func (i *siUpDownCounter) Add(ctx context.Context, x int64, opts ...metric.AddOp } } +func (i *siUpDownCounter) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64UpDownCounter).Enabled(ctx) + } + return false +} + type siHistogram struct { embedded.Int64Histogram @@ -385,6 +427,13 @@ func (i *siHistogram) Record(ctx context.Context, x int64, opts ...metric.Record } } +func (i *siHistogram) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64Histogram).Enabled(ctx) + } + return false +} + type siGauge struct { embedded.Int64Gauge @@ -410,3 +459,10 @@ func (i *siGauge) Record(ctx context.Context, x int64, opts ...metric.RecordOpti ctr.(metric.Int64Gauge).Record(ctx, x, opts...) } } + +func (i *siGauge) Enabled(ctx context.Context) bool { + if ctr := i.delegate.Load(); ctr != nil { + return ctr.(metric.Int64Gauge).Enabled(ctx) + } + return false +} diff --git a/internal/global/instruments_test.go b/internal/global/instruments_test.go index 15129ffa83d..863855a6da4 100644 --- a/internal/global/instruments_test.go +++ b/internal/global/instruments_test.go @@ -179,6 +179,10 @@ func (i *testCountingFloatInstrument) Record(context.Context, float64, ...metric i.count++ } +func (i *testCountingFloatInstrument) Enabled(context.Context) bool { + return true +} + type testCountingIntInstrument struct { count int @@ -203,3 +207,7 @@ func (i *testCountingIntInstrument) Add(context.Context, int64, ...metric.AddOpt func (i *testCountingIntInstrument) Record(context.Context, int64, ...metric.RecordOption) { i.count++ } + +func (i *testCountingIntInstrument) Enabled(context.Context) bool { + return true +} diff --git a/metric/noop/noop.go b/metric/noop/noop.go index 9afb69e583b..634e73aee0b 100644 --- a/metric/noop/noop.go +++ b/metric/noop/noop.go @@ -191,6 +191,9 @@ type Int64Counter struct{ embedded.Int64Counter } // Add performs no operation. func (Int64Counter) Add(context.Context, int64, ...metric.AddOption) {} +// Enabled performs no operation. +func (Int64Counter) Enabled(context.Context) bool { return false } + // Float64Counter is an OpenTelemetry Counter used to record float64 // measurements. It produces no telemetry. type Float64Counter struct{ embedded.Float64Counter } @@ -198,6 +201,9 @@ type Float64Counter struct{ embedded.Float64Counter } // Add performs no operation. func (Float64Counter) Add(context.Context, float64, ...metric.AddOption) {} +// Enabled performs no operation. +func (Float64Counter) Enabled(context.Context) bool { return false } + // Int64UpDownCounter is an OpenTelemetry UpDownCounter used to record int64 // measurements. It produces no telemetry. type Int64UpDownCounter struct{ embedded.Int64UpDownCounter } @@ -205,6 +211,9 @@ type Int64UpDownCounter struct{ embedded.Int64UpDownCounter } // Add performs no operation. func (Int64UpDownCounter) Add(context.Context, int64, ...metric.AddOption) {} +// Enabled performs no operation. +func (Int64UpDownCounter) Enabled(context.Context) bool { return false } + // Float64UpDownCounter is an OpenTelemetry UpDownCounter used to record // float64 measurements. It produces no telemetry. type Float64UpDownCounter struct{ embedded.Float64UpDownCounter } @@ -212,6 +221,9 @@ type Float64UpDownCounter struct{ embedded.Float64UpDownCounter } // Add performs no operation. func (Float64UpDownCounter) Add(context.Context, float64, ...metric.AddOption) {} +// Enabled performs no operation. +func (Float64UpDownCounter) Enabled(context.Context) bool { return false } + // Int64Histogram is an OpenTelemetry Histogram used to record int64 // measurements. It produces no telemetry. type Int64Histogram struct{ embedded.Int64Histogram } @@ -219,6 +231,9 @@ type Int64Histogram struct{ embedded.Int64Histogram } // Record performs no operation. func (Int64Histogram) Record(context.Context, int64, ...metric.RecordOption) {} +// Enabled performs no operation. +func (Int64Histogram) Enabled(context.Context) bool { return false } + // Float64Histogram is an OpenTelemetry Histogram used to record float64 // measurements. It produces no telemetry. type Float64Histogram struct{ embedded.Float64Histogram } @@ -226,6 +241,9 @@ type Float64Histogram struct{ embedded.Float64Histogram } // Record performs no operation. func (Float64Histogram) Record(context.Context, float64, ...metric.RecordOption) {} +// Enabled performs no operation. +func (Float64Histogram) Enabled(context.Context) bool { return false } + // Int64Gauge is an OpenTelemetry Gauge used to record instantaneous int64 // measurements. It produces no telemetry. type Int64Gauge struct{ embedded.Int64Gauge } @@ -233,6 +251,9 @@ type Int64Gauge struct{ embedded.Int64Gauge } // Record performs no operation. func (Int64Gauge) Record(context.Context, int64, ...metric.RecordOption) {} +// Enabled performs no operation. +func (Int64Gauge) Enabled(context.Context) bool { return false } + // Float64Gauge is an OpenTelemetry Gauge used to record instantaneous float64 // measurements. It produces no telemetry. type Float64Gauge struct{ embedded.Float64Gauge } @@ -240,6 +261,9 @@ type Float64Gauge struct{ embedded.Float64Gauge } // Record performs no operation. func (Float64Gauge) Record(context.Context, float64, ...metric.RecordOption) {} +// Enabled performs no operation. +func (Float64Gauge) Enabled(context.Context) bool { return false } + // Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record // int64 measurements. It produces no telemetry. type Int64ObservableCounter struct { diff --git a/metric/syncfloat64.go b/metric/syncfloat64.go index 8403a4bad2d..57a74c5e657 100644 --- a/metric/syncfloat64.go +++ b/metric/syncfloat64.go @@ -25,6 +25,12 @@ type Float64Counter interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Add(ctx context.Context, incr float64, options ...AddOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Float64CounterConfig contains options for synchronous counter instruments that @@ -78,6 +84,12 @@ type Float64UpDownCounter interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Add(ctx context.Context, incr float64, options ...AddOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Float64UpDownCounterConfig contains options for synchronous counter @@ -131,6 +143,12 @@ type Float64Histogram interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Record(ctx context.Context, incr float64, options ...RecordOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Float64HistogramConfig contains options for synchronous histogram @@ -189,6 +207,12 @@ type Float64Gauge interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Record(ctx context.Context, value float64, options ...RecordOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Float64GaugeConfig contains options for synchronous gauge instruments that diff --git a/metric/syncint64.go b/metric/syncint64.go index 783fdfba773..ac2d033ea6f 100644 --- a/metric/syncint64.go +++ b/metric/syncint64.go @@ -25,6 +25,12 @@ type Int64Counter interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Add(ctx context.Context, incr int64, options ...AddOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Int64CounterConfig contains options for synchronous counter instruments that @@ -78,6 +84,12 @@ type Int64UpDownCounter interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Add(ctx context.Context, incr int64, options ...AddOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Int64UpDownCounterConfig contains options for synchronous counter @@ -131,6 +143,12 @@ type Int64Histogram interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Record(ctx context.Context, incr int64, options ...RecordOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Int64HistogramConfig contains options for synchronous histogram instruments @@ -189,6 +207,12 @@ type Int64Gauge interface { // Use the WithAttributeSet (or, if performance is not a concern, // the WithAttributes) option to include measurement attributes. Record(ctx context.Context, value int64, options ...RecordOption) + + // Enabled reports whether the instrument will process measurements for the given context. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled(context.Context) bool } // Int64GaugeConfig contains options for synchronous gauge instruments that diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index 63cccc508f4..b0805255926 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -16,7 +16,6 @@ import ( "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" - "go.opentelemetry.io/otel/sdk/metric/internal/x" ) var zeroScope instrumentation.Scope @@ -191,7 +190,6 @@ var ( _ metric.Int64UpDownCounter = (*int64Inst)(nil) _ metric.Int64Histogram = (*int64Inst)(nil) _ metric.Int64Gauge = (*int64Inst)(nil) - _ x.EnabledInstrument = (*int64Inst)(nil) ) func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) { @@ -232,7 +230,6 @@ var ( _ metric.Float64UpDownCounter = (*float64Inst)(nil) _ metric.Float64Histogram = (*float64Inst)(nil) _ metric.Float64Gauge = (*float64Inst)(nil) - _ x.EnabledInstrument = (*float64Inst)(nil) ) func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) { diff --git a/sdk/metric/internal/x/README.md b/sdk/metric/internal/x/README.md index be0714a5f44..751a757c9ce 100644 --- a/sdk/metric/internal/x/README.md +++ b/sdk/metric/internal/x/README.md @@ -9,7 +9,6 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for ## Features - [Exemplars](#exemplars) -- [Instrument Enabled](#instrument-enabled) ### Exemplars @@ -72,24 +71,6 @@ Revert to the default exemplar filter (`"trace_based"`) unset OTEL_METRICS_EXEMPLAR_FILTER ``` -### Instrument Enabled - -To help users avoid performing computationally expensive operations when recording measurements, synchronous instruments provide an `Enabled` method. - -#### Examples - -The following code shows an example of how to check if an instrument implements the `EnabledInstrument` interface before using the `Enabled` function to avoid doing an expensive computation: - -```go -type enabledInstrument interface { Enabled(context.Context) bool } - -ctr, err := m.Int64Counter("expensive-counter") -c, ok := ctr.(enabledInstrument) -if !ok || c.Enabled(context.Background()) { - c.Add(expensiveComputation()) -} -``` - ## Compatibility and Stability Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md). diff --git a/sdk/metric/internal/x/x.go b/sdk/metric/internal/x/x.go index 294dcf8469e..43ecb72e26c 100644 --- a/sdk/metric/internal/x/x.go +++ b/sdk/metric/internal/x/x.go @@ -8,7 +8,6 @@ package x // import "go.opentelemetry.io/otel/sdk/metric/internal/x" import ( - "context" "os" ) @@ -52,14 +51,3 @@ func (f Feature[T]) Enabled() bool { _, ok := f.Lookup() return ok } - -// EnabledInstrument informs whether the instrument is enabled. -// -// EnabledInstrument interface is implemented by synchronous instruments. -type EnabledInstrument interface { - // Enabled reports whether the instrument will process measurements for the given context. - // - // This function can be used in places where measuring an instrument - // would result in computationally expensive operations. - Enabled(context.Context) bool -} diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index baf61f4e22a..9322f34c9d8 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -24,7 +24,6 @@ import ( "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/exemplar" - "go.opentelemetry.io/otel/sdk/metric/internal/x" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/resource" @@ -389,9 +388,7 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Int64Counter("sint") assert.NoError(t, err) - c, ok := ctr.(x.EnabledInstrument) - require.True(t, ok) - assert.True(t, c.Enabled(t.Context())) + assert.True(t, ctr.Enabled(t.Context())) ctr.Add(ctx, 3) }, want: metricdata.Metrics{ @@ -411,9 +408,7 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Int64UpDownCounter("sint") assert.NoError(t, err) - c, ok := ctr.(x.EnabledInstrument) - require.True(t, ok) - assert.True(t, c.Enabled(t.Context())) + assert.True(t, ctr.Enabled(t.Context())) ctr.Add(ctx, 11) }, want: metricdata.Metrics{ @@ -462,9 +457,7 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Float64Counter("sfloat") assert.NoError(t, err) - c, ok := ctr.(x.EnabledInstrument) - require.True(t, ok) - assert.True(t, c.Enabled(t.Context())) + assert.True(t, ctr.Enabled(t.Context())) ctr.Add(ctx, 3) }, want: metricdata.Metrics{ @@ -484,9 +477,7 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Float64UpDownCounter("sfloat") assert.NoError(t, err) - c, ok := ctr.(x.EnabledInstrument) - require.True(t, ok) - assert.True(t, c.Enabled(t.Context())) + assert.True(t, ctr.Enabled(t.Context())) ctr.Add(ctx, 11) }, want: metricdata.Metrics{ @@ -616,7 +607,9 @@ func TestMeterWithDropView(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := tt.fn(t) require.NoError(t, err) - c, ok := got.(x.EnabledInstrument) + c, ok := got.(interface { + Enabled(context.Context) bool + }) require.True(t, ok) assert.False(t, c.Enabled(t.Context())) }) From f7002c9c4b80cacfa4d18eb258d06e2a43410dc0 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 8 Jan 2026 20:25:06 +0100 Subject: [PATCH 2/4] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4318aae8d2b..38753db3263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `Enabled` method to all synchronous instrument interfaces (`Float64Counter`, `Float64UpDownCounter`, `Float64Histogram`, `Float64Gauge`, `Int64Counter`, `Int64UpDownCounter`, `Int64Histogram`, `Int64Gauge`,) in `go.opentelemetry.io/otel/metric`. - This stabilizes the synchronous instrument enabled feature, allowing users to check if an instrument will process measurements before performing computationally expensive operations. (#7681) + This stabilizes the synchronous instrument enabled feature, allowing users to check if an instrument will process measurements before performing computationally expensive operations. (#7763) ### Changed From 7353f5bcbe014f99016d98da0ee442ec687f3548 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 8 Jan 2026 20:27:34 +0100 Subject: [PATCH 3/4] lint --- internal/global/instruments_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/global/instruments_test.go b/internal/global/instruments_test.go index 863855a6da4..0683e5875cf 100644 --- a/internal/global/instruments_test.go +++ b/internal/global/instruments_test.go @@ -179,7 +179,7 @@ func (i *testCountingFloatInstrument) Record(context.Context, float64, ...metric i.count++ } -func (i *testCountingFloatInstrument) Enabled(context.Context) bool { +func (_ *testCountingFloatInstrument) Enabled(context.Context) bool { return true } @@ -208,6 +208,6 @@ func (i *testCountingIntInstrument) Record(context.Context, int64, ...metric.Rec i.count++ } -func (i *testCountingIntInstrument) Enabled(context.Context) bool { +func (_ *testCountingIntInstrument) Enabled(context.Context) bool { return true } From 8d485160a00511d9ce8c8308b610702e4da49503 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 8 Jan 2026 20:29:21 +0100 Subject: [PATCH 4/4] lint --- internal/global/instruments_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/global/instruments_test.go b/internal/global/instruments_test.go index 0683e5875cf..9eeac7d1074 100644 --- a/internal/global/instruments_test.go +++ b/internal/global/instruments_test.go @@ -179,7 +179,7 @@ func (i *testCountingFloatInstrument) Record(context.Context, float64, ...metric i.count++ } -func (_ *testCountingFloatInstrument) Enabled(context.Context) bool { +func (*testCountingFloatInstrument) Enabled(context.Context) bool { return true } @@ -208,6 +208,6 @@ func (i *testCountingIntInstrument) Record(context.Context, int64, ...metric.Rec i.count++ } -func (_ *testCountingIntInstrument) Enabled(context.Context) bool { +func (*testCountingIntInstrument) Enabled(context.Context) bool { return true }