Skip to content

Commit

Permalink
sdk/metric: Add experimental Enabled method to synchronous instruments (
Browse files Browse the repository at this point in the history
#6016)

Fixes #6002

---------

Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com>
Co-authored-by: Robert Pająk <pellared@hotmail.com>
Co-authored-by: Damien Mathieu <42@dmathieu.com>
  • Loading branch information
3 people authored Dec 6, 2024
1 parent 3bb224b commit 0598dae
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ 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)
- Add `EnabledInstrument` interface in `go.opentelemetry.io/otel/sdk/metric/internal/x`.
This is an experimental interface that is implemented by synchronous instruments provided by `go.opentelemetry.io/otel/sdk/metric`.
Users can use it to avoid performing computationally expensive operations when recording measurements.
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)

### Changed

Expand Down
11 changes: 11 additions & 0 deletions sdk/metric/instrument.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -190,6 +191,7 @@ 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) {
Expand All @@ -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(_ context.Context) 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)
Expand All @@ -222,6 +228,7 @@ 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) {
Expand All @@ -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(_ context.Context) 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)
Expand Down
19 changes: 19 additions & 0 deletions sdk/metric/internal/x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(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).
Expand Down
12 changes: 12 additions & 0 deletions sdk/metric/internal/x/x.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package x // import "go.opentelemetry.io/otel/sdk/metric/internal/x"

import (
"context"
"os"
"strconv"
)
Expand Down Expand Up @@ -67,3 +68,14 @@ 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 returns 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
}
85 changes: 85 additions & 0 deletions sdk/metric/meter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -388,6 +389,9 @@ 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(context.Background()))
ctr.Add(ctx, 3)
},
want: metricdata.Metrics{
Expand All @@ -407,6 +411,9 @@ 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(context.Background()))
ctr.Add(ctx, 11)
},
want: metricdata.Metrics{
Expand Down Expand Up @@ -452,6 +459,9 @@ 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(context.Background()))
ctr.Add(ctx, 3)
},
want: metricdata.Metrics{
Expand All @@ -471,6 +481,9 @@ 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(context.Background()))
ctr.Add(ctx, 11)
},
want: metricdata.Metrics{
Expand Down Expand Up @@ -532,6 +545,78 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
}

func TestMeterWithDropView(t *testing.T) {
dropView := NewView(
Instrument{Name: "*"},
Stream{Aggregation: AggregationDrop{}},
)
m := NewMeterProvider(WithView(dropView)).Meter(t.Name())

testCases := []struct {
name string
fn func(*testing.T) (any, error)
}{
{
name: "Int64Counter",
fn: func(*testing.T) (any, error) {
return m.Int64Counter("sint")
},
},
{
name: "Int64UpDownCounter",
fn: func(*testing.T) (any, error) {
return m.Int64UpDownCounter("sint")
},
},
{
name: "Int64Gauge",
fn: func(*testing.T) (any, error) {
return m.Int64Gauge("sint")
},
},
{
name: "Int64Histogram",
fn: func(*testing.T) (any, error) {
return m.Int64Histogram("histogram")
},
},
{
name: "Float64Counter",
fn: func(*testing.T) (any, error) {
return m.Float64Counter("sfloat")
},
},
{
name: "Float64UpDownCounter",
fn: func(*testing.T) (any, error) {
return m.Float64UpDownCounter("sfloat")
},
},
{
name: "Float64Gauge",
fn: func(*testing.T) (any, error) {
return m.Float64Gauge("sfloat")
},
},
{
name: "Float64Histogram",
fn: func(*testing.T) (any, error) {
return m.Float64Histogram("histogram")
},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.fn(t)
require.NoError(t, err)
c, ok := got.(x.EnabledInstrument)
require.True(t, ok)
assert.False(t, c.Enabled(context.Background()))
})
}
}

func TestMeterCreatesInstrumentsValidations(t *testing.T) {
testCases := []struct {
name string
Expand Down

0 comments on commit 0598dae

Please sign in to comment.