diff --git a/CHANGELOG.md b/CHANGELOG.md index 566829ae1e2..9140f8558c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `Reset` method to `SpanRecorder` in `go.opentelemetry.io/otel/sdk/trace/tracetest`. (#5994) +- Experimental `EnabledInstrument` interface added to the metric SDK. + See [metric documentation](./sdk/metric/internal/x/README.md#instrument-enabled) for more information about this feature and how to use it. (#) ### Changed diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index 48b723a7b3b..680113890d5 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -16,6 +16,7 @@ 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 @@ -183,6 +184,7 @@ type int64Inst struct { embedded.Int64UpDownCounter embedded.Int64Histogram embedded.Int64Gauge + x.EnabledInstrument } var ( @@ -202,6 +204,10 @@ func (i *int64Inst) Record(ctx context.Context, val int64, opts ...metric.Record i.aggregate(ctx, val, c.Attributes()) } +func (i *int64Inst) Enabled() bool { + return len(i.measures) != 0 +} + func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { // nolint:revive // okay to shadow pkg with method. for _, in := range i.measures { in(ctx, val, s) @@ -215,6 +221,7 @@ type float64Inst struct { embedded.Float64UpDownCounter embedded.Float64Histogram embedded.Float64Gauge + x.EnabledInstrument } var ( @@ -234,6 +241,10 @@ func (i *float64Inst) Record(ctx context.Context, val float64, opts ...metric.Re i.aggregate(ctx, val, c.Attributes()) } +func (i *float64Inst) Enabled() bool { + return len(i.measures) != 0 +} + func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) { for _, in := range i.measures { in(ctx, val, s) diff --git a/sdk/metric/internal/x/README.md b/sdk/metric/internal/x/README.md index aba69d65471..8bc7841d9a2 100644 --- a/sdk/metric/internal/x/README.md +++ b/sdk/metric/internal/x/README.md @@ -10,6 +10,7 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for - [Cardinality Limit](#cardinality-limit) - [Exemplars](#exemplars) +- [Instrument Enabled](#instrument-enabled) ### Cardinality Limit @@ -102,6 +103,24 @@ 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() bool } + +ctr, err := m.Int64Counter("expensive-counter") +c, ok := ctr.(enabledInstrument) +if c.Enabled() { + 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 08919937068..b62681d5583 100644 --- a/sdk/metric/internal/x/x.go +++ b/sdk/metric/internal/x/x.go @@ -67,3 +67,14 @@ func (f Feature[T]) Enabled() bool { _, ok := f.Lookup() return ok } + +// EnabledInstrument provides the interface for synchronous +// instruments to returned whether or not they are enabled. +type EnabledInstrument interface { + // Enabled returns whether the instrument has any measurements + // associated with it. + // + // This function can be used in places where measuring an instrument + // would result in computationally expensive operations. + Enabled() bool +} diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index 189d66de3e0..b2a23c4f286 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -24,6 +24,7 @@ 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" @@ -388,6 +389,9 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Int64Counter("sint") assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.True(t, c.Enabled()) ctr.Add(ctx, 3) }, want: metricdata.Metrics{ @@ -407,6 +411,9 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Int64UpDownCounter("sint") assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.True(t, c.Enabled()) ctr.Add(ctx, 11) }, want: metricdata.Metrics{ @@ -452,6 +459,9 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Float64Counter("sfloat") assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.True(t, c.Enabled()) ctr.Add(ctx, 3) }, want: metricdata.Metrics{ @@ -471,6 +481,9 @@ func TestMeterCreatesInstruments(t *testing.T) { ctr, err := m.Float64UpDownCounter("sfloat") assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.True(t, c.Enabled()) ctr.Add(ctx, 11) }, want: metricdata.Metrics{ @@ -532,6 +545,106 @@ func TestMeterCreatesInstruments(t *testing.T) { } } +func TestMeterWithDropView(t *testing.T) { + testCases := []struct { + name string + fn func(*testing.T, metric.Meter) + }{ + { + name: "SyncInt64Count", + fn: func(t *testing.T, m metric.Meter) { + ctr, err := m.Int64Counter("sint") + assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, c.Enabled()) + }, + }, + { + name: "SyncInt64UpDownCount", + fn: func(t *testing.T, m metric.Meter) { + ctr, err := m.Int64UpDownCounter("sint") + assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, c.Enabled()) + }, + }, + { + name: "SyncInt64Gauge", + fn: func(t *testing.T, m metric.Meter) { + gauge, err := m.Int64Gauge("sint") + assert.NoError(t, err) + g, ok := gauge.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, g.Enabled()) + }, + }, + { + name: "SyncInt64Histogram", + fn: func(t *testing.T, m metric.Meter) { + histo, err := m.Int64Histogram("histogram") + assert.NoError(t, err) + h, ok := histo.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, h.Enabled()) + }, + }, + { + name: "SyncFloat64Count", + fn: func(t *testing.T, m metric.Meter) { + ctr, err := m.Float64Counter("sfloat") + assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, c.Enabled()) + }, + }, + { + name: "SyncFloat64UpDownCount", + fn: func(t *testing.T, m metric.Meter) { + ctr, err := m.Float64UpDownCounter("sfloat") + assert.NoError(t, err) + c, ok := ctr.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, c.Enabled()) + }, + }, + { + name: "SyncFloat64Gauge", + fn: func(t *testing.T, m metric.Meter) { + gauge, err := m.Float64Gauge("sfloat") + assert.NoError(t, err) + g, ok := gauge.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, g.Enabled()) + }, + }, + { + name: "SyncFloat64Histogram", + fn: func(t *testing.T, m metric.Meter) { + histo, err := m.Float64Histogram("histogram") + assert.NoError(t, err) + h, ok := histo.(x.EnabledInstrument) + assert.True(t, ok) + assert.False(t, h.Enabled()) + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + rdr := NewManualReader() + dropView := NewView( + Instrument{Name: "*"}, + Stream{Aggregation: AggregationDrop{}}, + ) + m := NewMeterProvider(WithReader(rdr), WithView(dropView)).Meter("testInstruments") + tt.fn(t, m) + }) + } +} + func TestMeterCreatesInstrumentsValidations(t *testing.T) { testCases := []struct { name string