Skip to content

Commit 0598dae

Browse files
codebotenpellareddmathieu
authored
sdk/metric: Add experimental Enabled method to synchronous instruments (#6016)
Fixes #6002 --------- Signed-off-by: Alex Boten <[email protected]> Co-authored-by: Robert Pająk <[email protected]> Co-authored-by: Damien Mathieu <[email protected]>
1 parent 3bb224b commit 0598dae

File tree

5 files changed

+131
-0
lines changed

5 files changed

+131
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1111
### Added
1212

1313
- Add `Reset` method to `SpanRecorder` in `go.opentelemetry.io/otel/sdk/trace/tracetest`. (#5994)
14+
- Add `EnabledInstrument` interface in `go.opentelemetry.io/otel/sdk/metric/internal/x`.
15+
This is an experimental interface that is implemented by synchronous instruments provided by `go.opentelemetry.io/otel/sdk/metric`.
16+
Users can use it to avoid performing computationally expensive operations when recording measurements.
17+
It does not fall within the scope of the OpenTelemetry Go versioning and stability [policy](./VERSIONING.md) and it may be changed in backwards incompatible ways or removed in feature releases. (#6016)
1418

1519
### Changed
1620

sdk/metric/instrument.go

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.opentelemetry.io/otel/metric/embedded"
1717
"go.opentelemetry.io/otel/sdk/instrumentation"
1818
"go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
19+
"go.opentelemetry.io/otel/sdk/metric/internal/x"
1920
)
2021

2122
var zeroScope instrumentation.Scope
@@ -190,6 +191,7 @@ var (
190191
_ metric.Int64UpDownCounter = (*int64Inst)(nil)
191192
_ metric.Int64Histogram = (*int64Inst)(nil)
192193
_ metric.Int64Gauge = (*int64Inst)(nil)
194+
_ x.EnabledInstrument = (*int64Inst)(nil)
193195
)
194196

195197
func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) {
@@ -202,6 +204,10 @@ func (i *int64Inst) Record(ctx context.Context, val int64, opts ...metric.Record
202204
i.aggregate(ctx, val, c.Attributes())
203205
}
204206

207+
func (i *int64Inst) Enabled(_ context.Context) bool {
208+
return len(i.measures) != 0
209+
}
210+
205211
func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { // nolint:revive // okay to shadow pkg with method.
206212
for _, in := range i.measures {
207213
in(ctx, val, s)
@@ -222,6 +228,7 @@ var (
222228
_ metric.Float64UpDownCounter = (*float64Inst)(nil)
223229
_ metric.Float64Histogram = (*float64Inst)(nil)
224230
_ metric.Float64Gauge = (*float64Inst)(nil)
231+
_ x.EnabledInstrument = (*float64Inst)(nil)
225232
)
226233

227234
func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) {
@@ -234,6 +241,10 @@ func (i *float64Inst) Record(ctx context.Context, val float64, opts ...metric.Re
234241
i.aggregate(ctx, val, c.Attributes())
235242
}
236243

244+
func (i *float64Inst) Enabled(_ context.Context) bool {
245+
return len(i.measures) != 0
246+
}
247+
237248
func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) {
238249
for _, in := range i.measures {
239250
in(ctx, val, s)

sdk/metric/internal/x/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for
1010

1111
- [Cardinality Limit](#cardinality-limit)
1212
- [Exemplars](#exemplars)
13+
- [Instrument Enabled](#instrument-enabled)
1314

1415
### Cardinality Limit
1516

@@ -102,6 +103,24 @@ Revert to the default exemplar filter (`"trace_based"`)
102103
unset OTEL_METRICS_EXEMPLAR_FILTER
103104
```
104105

106+
### Instrument Enabled
107+
108+
To help users avoid performing computationally expensive operations when recording measurements, synchronous instruments provide an `Enabled` method.
109+
110+
#### Examples
111+
112+
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:
113+
114+
```go
115+
type enabledInstrument interface { Enabled(context.Context) bool }
116+
117+
ctr, err := m.Int64Counter("expensive-counter")
118+
c, ok := ctr.(enabledInstrument)
119+
if !ok || c.Enabled(context.Background()) {
120+
c.Add(expensiveComputation())
121+
}
122+
```
123+
105124
## Compatibility and Stability
106125

107126
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md).

sdk/metric/internal/x/x.go

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package x // import "go.opentelemetry.io/otel/sdk/metric/internal/x"
99

1010
import (
11+
"context"
1112
"os"
1213
"strconv"
1314
)
@@ -67,3 +68,14 @@ func (f Feature[T]) Enabled() bool {
6768
_, ok := f.Lookup()
6869
return ok
6970
}
71+
72+
// EnabledInstrument informs whether the instrument is enabled.
73+
//
74+
// EnabledInstrument interface is implemented by synchronous instruments.
75+
type EnabledInstrument interface {
76+
// Enabled returns whether the instrument will process measurements for the given context.
77+
//
78+
// This function can be used in places where measuring an instrument
79+
// would result in computationally expensive operations.
80+
Enabled(context.Context) bool
81+
}

sdk/metric/meter_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"go.opentelemetry.io/otel/metric"
2525
"go.opentelemetry.io/otel/sdk/instrumentation"
2626
"go.opentelemetry.io/otel/sdk/metric/exemplar"
27+
"go.opentelemetry.io/otel/sdk/metric/internal/x"
2728
"go.opentelemetry.io/otel/sdk/metric/metricdata"
2829
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
2930
"go.opentelemetry.io/otel/sdk/resource"
@@ -388,6 +389,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
388389
ctr, err := m.Int64Counter("sint")
389390
assert.NoError(t, err)
390391

392+
c, ok := ctr.(x.EnabledInstrument)
393+
require.True(t, ok)
394+
assert.True(t, c.Enabled(context.Background()))
391395
ctr.Add(ctx, 3)
392396
},
393397
want: metricdata.Metrics{
@@ -407,6 +411,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
407411
ctr, err := m.Int64UpDownCounter("sint")
408412
assert.NoError(t, err)
409413

414+
c, ok := ctr.(x.EnabledInstrument)
415+
require.True(t, ok)
416+
assert.True(t, c.Enabled(context.Background()))
410417
ctr.Add(ctx, 11)
411418
},
412419
want: metricdata.Metrics{
@@ -452,6 +459,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
452459
ctr, err := m.Float64Counter("sfloat")
453460
assert.NoError(t, err)
454461

462+
c, ok := ctr.(x.EnabledInstrument)
463+
require.True(t, ok)
464+
assert.True(t, c.Enabled(context.Background()))
455465
ctr.Add(ctx, 3)
456466
},
457467
want: metricdata.Metrics{
@@ -471,6 +481,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
471481
ctr, err := m.Float64UpDownCounter("sfloat")
472482
assert.NoError(t, err)
473483

484+
c, ok := ctr.(x.EnabledInstrument)
485+
require.True(t, ok)
486+
assert.True(t, c.Enabled(context.Background()))
474487
ctr.Add(ctx, 11)
475488
},
476489
want: metricdata.Metrics{
@@ -532,6 +545,78 @@ func TestMeterCreatesInstruments(t *testing.T) {
532545
}
533546
}
534547

548+
func TestMeterWithDropView(t *testing.T) {
549+
dropView := NewView(
550+
Instrument{Name: "*"},
551+
Stream{Aggregation: AggregationDrop{}},
552+
)
553+
m := NewMeterProvider(WithView(dropView)).Meter(t.Name())
554+
555+
testCases := []struct {
556+
name string
557+
fn func(*testing.T) (any, error)
558+
}{
559+
{
560+
name: "Int64Counter",
561+
fn: func(*testing.T) (any, error) {
562+
return m.Int64Counter("sint")
563+
},
564+
},
565+
{
566+
name: "Int64UpDownCounter",
567+
fn: func(*testing.T) (any, error) {
568+
return m.Int64UpDownCounter("sint")
569+
},
570+
},
571+
{
572+
name: "Int64Gauge",
573+
fn: func(*testing.T) (any, error) {
574+
return m.Int64Gauge("sint")
575+
},
576+
},
577+
{
578+
name: "Int64Histogram",
579+
fn: func(*testing.T) (any, error) {
580+
return m.Int64Histogram("histogram")
581+
},
582+
},
583+
{
584+
name: "Float64Counter",
585+
fn: func(*testing.T) (any, error) {
586+
return m.Float64Counter("sfloat")
587+
},
588+
},
589+
{
590+
name: "Float64UpDownCounter",
591+
fn: func(*testing.T) (any, error) {
592+
return m.Float64UpDownCounter("sfloat")
593+
},
594+
},
595+
{
596+
name: "Float64Gauge",
597+
fn: func(*testing.T) (any, error) {
598+
return m.Float64Gauge("sfloat")
599+
},
600+
},
601+
{
602+
name: "Float64Histogram",
603+
fn: func(*testing.T) (any, error) {
604+
return m.Float64Histogram("histogram")
605+
},
606+
},
607+
}
608+
609+
for _, tt := range testCases {
610+
t.Run(tt.name, func(t *testing.T) {
611+
got, err := tt.fn(t)
612+
require.NoError(t, err)
613+
c, ok := got.(x.EnabledInstrument)
614+
require.True(t, ok)
615+
assert.False(t, c.Enabled(context.Background()))
616+
})
617+
}
618+
}
619+
535620
func TestMeterCreatesInstrumentsValidations(t *testing.T) {
536621
testCases := []struct {
537622
name string

0 commit comments

Comments
 (0)