diff --git a/metric/asyncfloat64.go b/metric/asyncfloat64.go index b7fc973a66c..db69e23cf7e 100644 --- a/metric/asyncfloat64.go +++ b/metric/asyncfloat64.go @@ -43,6 +43,7 @@ type Float64ObservableCounter interface { type Float64ObservableCounterConfig struct { description string unit string + optIn bool callbacks []Float64Callback } @@ -66,6 +67,11 @@ func (c Float64ObservableCounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Float64ObservableCounterConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Float64ObservableCounterConfig) Callbacks() []Float64Callback { return c.callbacks @@ -101,6 +107,7 @@ type Float64ObservableUpDownCounter interface { type Float64ObservableUpDownCounterConfig struct { description string unit string + optIn bool callbacks []Float64Callback } @@ -126,6 +133,11 @@ func (c Float64ObservableUpDownCounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Float64ObservableUpDownCounterConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Float64ObservableUpDownCounterConfig) Callbacks() []Float64Callback { return c.callbacks @@ -160,6 +172,7 @@ type Float64ObservableGauge interface { type Float64ObservableGaugeConfig struct { description string unit string + optIn bool callbacks []Float64Callback } @@ -183,6 +196,11 @@ func (c Float64ObservableGaugeConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Float64ObservableGaugeConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Float64ObservableGaugeConfig) Callbacks() []Float64Callback { return c.callbacks diff --git a/metric/asyncfloat64_test.go b/metric/asyncfloat64_test.go index 14934e3c160..58e915eec83 100644 --- a/metric/asyncfloat64_test.go +++ b/metric/asyncfloat64_test.go @@ -18,12 +18,14 @@ func TestFloat64ObservableConfiguration(t *testing.T) { token float64 = 43 desc = "Instrument description." uBytes = "By" + optIn = true ) run := func(got float64ObservableConfig) func(*testing.T) { return func(t *testing.T) { assert.Equal(t, desc, got.Description(), "description") assert.Equal(t, uBytes, got.Unit(), "unit") + assert.Equal(t, optIn, got.OptIn(), "optIn") // Functions are not comparable. cBacks := got.Callbacks() @@ -44,6 +46,7 @@ func TestFloat64ObservableConfiguration(t *testing.T) { NewFloat64ObservableCounterConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithFloat64Callback(cback), ), )) @@ -52,6 +55,7 @@ func TestFloat64ObservableConfiguration(t *testing.T) { NewFloat64ObservableUpDownCounterConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithFloat64Callback(cback), ), )) @@ -60,6 +64,7 @@ func TestFloat64ObservableConfiguration(t *testing.T) { NewFloat64ObservableGaugeConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithFloat64Callback(cback), ), )) @@ -68,6 +73,7 @@ func TestFloat64ObservableConfiguration(t *testing.T) { type float64ObservableConfig interface { Description() string Unit() string + OptIn() bool Callbacks() []Float64Callback } diff --git a/metric/asyncint64.go b/metric/asyncint64.go index 4404b71a22f..12908c9eb25 100644 --- a/metric/asyncint64.go +++ b/metric/asyncint64.go @@ -42,6 +42,7 @@ type Int64ObservableCounter interface { type Int64ObservableCounterConfig struct { description string unit string + optIn bool callbacks []Int64Callback } @@ -65,6 +66,11 @@ func (c Int64ObservableCounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64ObservableCounterConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Int64ObservableCounterConfig) Callbacks() []Int64Callback { return c.callbacks @@ -100,6 +106,7 @@ type Int64ObservableUpDownCounter interface { type Int64ObservableUpDownCounterConfig struct { description string unit string + optIn bool callbacks []Int64Callback } @@ -125,6 +132,11 @@ func (c Int64ObservableUpDownCounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64ObservableUpDownCounterConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Int64ObservableUpDownCounterConfig) Callbacks() []Int64Callback { return c.callbacks @@ -159,6 +171,7 @@ type Int64ObservableGauge interface { type Int64ObservableGaugeConfig struct { description string unit string + optIn bool callbacks []Int64Callback } @@ -182,6 +195,11 @@ func (c Int64ObservableGaugeConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64ObservableGaugeConfig) OptIn() bool { + return c.optIn +} + // Callbacks returns the configured callbacks. func (c Int64ObservableGaugeConfig) Callbacks() []Int64Callback { return c.callbacks diff --git a/metric/asyncint64_test.go b/metric/asyncint64_test.go index 5c58b7ca13b..db78b87b986 100644 --- a/metric/asyncint64_test.go +++ b/metric/asyncint64_test.go @@ -18,12 +18,14 @@ func TestInt64ObservableConfiguration(t *testing.T) { token int64 = 43 desc = "Instrument description." uBytes = "By" + optIn = true ) run := func(got int64ObservableConfig) func(*testing.T) { return func(t *testing.T) { assert.Equal(t, desc, got.Description(), "description") assert.Equal(t, uBytes, got.Unit(), "unit") + assert.Equal(t, optIn, got.OptIn(), "optIn") // Functions are not comparable. cBacks := got.Callbacks() @@ -44,6 +46,7 @@ func TestInt64ObservableConfiguration(t *testing.T) { NewInt64ObservableCounterConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithInt64Callback(cback), ), )) @@ -52,6 +55,7 @@ func TestInt64ObservableConfiguration(t *testing.T) { NewInt64ObservableUpDownCounterConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithInt64Callback(cback), ), )) @@ -60,6 +64,7 @@ func TestInt64ObservableConfiguration(t *testing.T) { NewInt64ObservableGaugeConfig( WithDescription(desc), WithUnit(uBytes), + WithOptIn(), WithInt64Callback(cback), ), )) @@ -68,6 +73,7 @@ func TestInt64ObservableConfiguration(t *testing.T) { type int64ObservableConfig interface { Description() string Unit() string + OptIn() bool Callbacks() []Int64Callback } diff --git a/metric/example_test.go b/metric/example_test.go index 67af3751ffe..b21ba8bbcae 100644 --- a/metric/example_test.go +++ b/metric/example_test.go @@ -301,3 +301,23 @@ func ExampleMeter_attributes() { metric.WithAttributes(semconv.HTTPResponseStatusCode(statusCode))) }) } + +// You can define metrics which are disabled by default using the [WithOptIn] option. +// +// Here's how you might define a counter that is disabled by default. +func ExampleMeter_withOptIn() { + var err error + itemsCounter, err := meter.Int64UpDownCounter( + "items.counter", + metric.WithDescription("Number of items."), + metric.WithUnit("{item}"), + metric.WithOptIn(), + ) + if err != nil { + panic(err) + } + + // This increment is a no-op unless the user has explicitly enabled the + // metric using the SDK. + itemsCounter.Add(context.Background(), 1) +} diff --git a/metric/instrument.go b/metric/instrument.go index 9f48d5f117c..4179abdada3 100644 --- a/metric/instrument.go +++ b/metric/instrument.go @@ -196,6 +196,85 @@ func (o unitOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64Ob // The unit u should be defined using the appropriate [UCUM](https://ucum.org) case-sensitive code. func WithUnit(u string) InstrumentOption { return unitOpt(u) } +type optInOpt struct{} + +func (o optInOpt) applyFloat64Counter(c Float64CounterConfig) Float64CounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64UpDownCounter(c Float64UpDownCounterConfig) Float64UpDownCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64ObservableUpDownCounter( + c Float64ObservableUpDownCounterConfig, +) Float64ObservableUpDownCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyFloat64ObservableGauge(c Float64ObservableGaugeConfig) Float64ObservableGaugeConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64Counter(c Int64CounterConfig) Int64CounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64UpDownCounter(c Int64UpDownCounterConfig) Int64UpDownCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64ObservableUpDownCounter( + c Int64ObservableUpDownCounterConfig, +) Int64ObservableUpDownCounterConfig { + c.optIn = true + return c +} + +func (o optInOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64ObservableGaugeConfig { + c.optIn = true + return c +} + +// WithOptIn sets the instrument to be disabled by default. +func WithOptIn() InstrumentOption { return optInOpt{} } + // WithExplicitBucketBoundaries sets the instrument explicit bucket boundaries. // // This option is considered "advisory", and may be ignored by API implementations. diff --git a/metric/syncfloat64.go b/metric/syncfloat64.go index 8403a4bad2d..edaddfef5d6 100644 --- a/metric/syncfloat64.go +++ b/metric/syncfloat64.go @@ -32,6 +32,7 @@ type Float64Counter interface { type Float64CounterConfig struct { description string unit string + optIn bool } // NewFloat64CounterConfig returns a new [Float64CounterConfig] with all opts @@ -54,6 +55,11 @@ func (c Float64CounterConfig) Unit() string { return c.unit } +// optIn returns true if the instrument is disabled by default. +func (c Float64CounterConfig) OptIn() bool { + return c.optIn +} + // Float64CounterOption applies options to a [Float64CounterConfig]. See // [InstrumentOption] for other options that can be used as a // Float64CounterOption. @@ -85,6 +91,7 @@ type Float64UpDownCounter interface { type Float64UpDownCounterConfig struct { description string unit string + optIn bool } // NewFloat64UpDownCounterConfig returns a new [Float64UpDownCounterConfig] @@ -107,6 +114,11 @@ func (c Float64UpDownCounterConfig) Unit() string { return c.unit } +// optIn returns true if the instrument is disabled by default. +func (c Float64UpDownCounterConfig) OptIn() bool { + return c.optIn +} + // Float64UpDownCounterOption applies options to a // [Float64UpDownCounterConfig]. See [InstrumentOption] for other options that // can be used as a Float64UpDownCounterOption. @@ -138,6 +150,7 @@ type Float64Histogram interface { type Float64HistogramConfig struct { description string unit string + optIn bool explicitBucketBoundaries []float64 } @@ -161,6 +174,11 @@ func (c Float64HistogramConfig) Unit() string { return c.unit } +// optIn returns true if the instrument is disabled by default. +func (c Float64HistogramConfig) OptIn() bool { + return c.optIn +} + // ExplicitBucketBoundaries returns the configured explicit bucket boundaries. func (c Float64HistogramConfig) ExplicitBucketBoundaries() []float64 { return c.explicitBucketBoundaries @@ -196,6 +214,7 @@ type Float64Gauge interface { type Float64GaugeConfig struct { description string unit string + optIn bool } // NewFloat64GaugeConfig returns a new [Float64GaugeConfig] with all opts @@ -218,6 +237,11 @@ func (c Float64GaugeConfig) Unit() string { return c.unit } +// optIn returns true if the instrument is disabled by default. +func (c Float64GaugeConfig) OptIn() bool { + return c.optIn +} + // Float64GaugeOption applies options to a [Float64GaugeConfig]. See // [InstrumentOption] for other options that can be used as a // Float64GaugeOption. diff --git a/metric/syncfloat64_test.go b/metric/syncfloat64_test.go index 223eb081b03..e815480decf 100644 --- a/metric/syncfloat64_test.go +++ b/metric/syncfloat64_test.go @@ -14,35 +14,38 @@ func TestFloat64Configuration(t *testing.T) { token float64 = 43 desc = "Instrument description." uBytes = "By" + optIn = true ) run := func(got float64Config) func(*testing.T) { return func(t *testing.T) { assert.Equal(t, desc, got.Description(), "description") assert.Equal(t, uBytes, got.Unit(), "unit") + assert.Equal(t, optIn, got.OptIn(), "optIn") } } t.Run("Float64Counter", run( - NewFloat64CounterConfig(WithDescription(desc), WithUnit(uBytes)), + NewFloat64CounterConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Float64UpDownCounter", run( - NewFloat64UpDownCounterConfig(WithDescription(desc), WithUnit(uBytes)), + NewFloat64UpDownCounterConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Float64Histogram", run( - NewFloat64HistogramConfig(WithDescription(desc), WithUnit(uBytes)), + NewFloat64HistogramConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Float64Gauge", run( - NewFloat64GaugeConfig(WithDescription(desc), WithUnit(uBytes)), + NewFloat64GaugeConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) } type float64Config interface { Description() string Unit() string + OptIn() bool } func TestFloat64ExplicitBucketHistogramConfiguration(t *testing.T) { diff --git a/metric/syncint64.go b/metric/syncint64.go index 783fdfba773..d90a91a93bc 100644 --- a/metric/syncint64.go +++ b/metric/syncint64.go @@ -32,6 +32,7 @@ type Int64Counter interface { type Int64CounterConfig struct { description string unit string + optIn bool } // NewInt64CounterConfig returns a new [Int64CounterConfig] with all opts @@ -54,6 +55,11 @@ func (c Int64CounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64CounterConfig) OptIn() bool { + return c.optIn +} + // Int64CounterOption applies options to a [Int64CounterConfig]. See // [InstrumentOption] for other options that can be used as an // Int64CounterOption. @@ -85,6 +91,7 @@ type Int64UpDownCounter interface { type Int64UpDownCounterConfig struct { description string unit string + optIn bool } // NewInt64UpDownCounterConfig returns a new [Int64UpDownCounterConfig] with @@ -107,6 +114,11 @@ func (c Int64UpDownCounterConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64UpDownCounterConfig) OptIn() bool { + return c.optIn +} + // Int64UpDownCounterOption applies options to a [Int64UpDownCounterConfig]. // See [InstrumentOption] for other options that can be used as an // Int64UpDownCounterOption. @@ -138,6 +150,7 @@ type Int64Histogram interface { type Int64HistogramConfig struct { description string unit string + optIn bool explicitBucketBoundaries []float64 } @@ -161,6 +174,11 @@ func (c Int64HistogramConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64HistogramConfig) OptIn() bool { + return c.optIn +} + // ExplicitBucketBoundaries returns the configured explicit bucket boundaries. func (c Int64HistogramConfig) ExplicitBucketBoundaries() []float64 { return c.explicitBucketBoundaries @@ -196,6 +214,7 @@ type Int64Gauge interface { type Int64GaugeConfig struct { description string unit string + optIn bool } // NewInt64GaugeConfig returns a new [Int64GaugeConfig] with all opts @@ -218,6 +237,11 @@ func (c Int64GaugeConfig) Unit() string { return c.unit } +// OptIn returns true if the instrument is disabled by default. +func (c Int64GaugeConfig) OptIn() bool { + return c.optIn +} + // Int64GaugeOption applies options to a [Int64GaugeConfig]. See // [InstrumentOption] for other options that can be used as a // Int64GaugeOption. diff --git a/metric/syncint64_test.go b/metric/syncint64_test.go index 9bf52bf325f..bb0d1d9e404 100644 --- a/metric/syncint64_test.go +++ b/metric/syncint64_test.go @@ -14,35 +14,38 @@ func TestInt64Configuration(t *testing.T) { token int64 = 43 desc = "Instrument description." uBytes = "By" + optIn = true ) run := func(got int64Config) func(*testing.T) { return func(t *testing.T) { assert.Equal(t, desc, got.Description(), "description") assert.Equal(t, uBytes, got.Unit(), "unit") + assert.Equal(t, optIn, got.OptIn(), "optIn") } } t.Run("Int64Counter", run( - NewInt64CounterConfig(WithDescription(desc), WithUnit(uBytes)), + NewInt64CounterConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Int64UpDownCounter", run( - NewInt64UpDownCounterConfig(WithDescription(desc), WithUnit(uBytes)), + NewInt64UpDownCounterConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Int64Histogram", run( - NewInt64HistogramConfig(WithDescription(desc), WithUnit(uBytes)), + NewInt64HistogramConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) t.Run("Int64Gauge", run( - NewInt64GaugeConfig(WithDescription(desc), WithUnit(uBytes)), + NewInt64GaugeConfig(WithDescription(desc), WithUnit(uBytes), WithOptIn()), )) } type int64Config interface { Description() string Unit() string + OptIn() bool } func TestInt64ExplicitBucketHistogramConfiguration(t *testing.T) { diff --git a/sdk/metric/example_test.go b/sdk/metric/example_test.go index 22bf60f6946..554a34320d3 100644 --- a/sdk/metric/example_test.go +++ b/sdk/metric/example_test.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + apimetric "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/exemplar" @@ -157,6 +158,41 @@ func ExampleNewView() { // unit: ms } +func ExampleNewView_optIn() { + // Create a view that renames the "latency" instrument from the v0.34.0 + // version of the "http" instrumentation library as "request.latency". + enabledTrue := true + view := metric.NewView(metric.Instrument{ + Name: "optin.counter", + Scope: instrumentation.Scope{ + Name: "example", + }, + // This enables a metric that is disabled by default. + }, metric.Stream{Enabled: &enabledTrue}) + + // The created view can then be registered with the OpenTelemetry metric + // SDK using the WithView option. + mp := metric.NewMeterProvider( + metric.WithView(view), + ) + + // A metric can be marked opt-in using apimetric.WithOptIn. + mp.Meter("example").Int64Counter("optin.counter", apimetric.WithOptIn()) + + // Below is an example of how the view will + // function in the SDK for certain instruments. + stream, _ := view(metric.Instrument{ + Name: "optin.counter", + Kind: metric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "example", + }, + }) + fmt.Println("enabled:", *stream.Enabled) + // Output: + // enabled: true +} + func ExampleNewView_wildcard() { // Create a view that sets unit to milliseconds for any instrument with a // name suffix of ".ms". diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index 63cccc508f4..a5c34de00ec 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -151,6 +151,10 @@ type Stream struct { // // If unspecified, [DefaultExemplarReservoirProviderSelector] is used. ExemplarReservoirProviderSelector ExemplarReservoirProviderSelector + // Enabled can be used to enable an OptIn metric, or disable a metric that + // is not OptIn. If it is unset, it leaves the enablement of the metric in + // its default state. + Enabled *bool } // instID are the identifying properties of a instrument. diff --git a/sdk/metric/meter.go b/sdk/metric/meter.go index e0a1e90e778..ec0b91ec8f4 100644 --- a/sdk/metric/meter.go +++ b/sdk/metric/meter.go @@ -70,7 +70,7 @@ func (m *meter) Int64Counter(name string, options ...metric.Int64CounterOption) cfg := metric.NewInt64CounterConfig(options...) const kind = InstrumentKindCounter p := int64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -88,7 +88,7 @@ func (m *meter) Int64UpDownCounter( cfg := metric.NewInt64UpDownCounterConfig(options...) const kind = InstrumentKindUpDownCounter p := int64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -117,7 +117,7 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met cfg := metric.NewInt64GaugeConfig(options...) const kind = InstrumentKindGauge p := int64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -127,7 +127,7 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met // int64ObservableInstrument returns a new observable identified by the Instrument. // It registers callbacks for each reader's pipeline. -func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int64Callback) (int64Observable, error) { +func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int64Callback, optIn bool) (int64Observable, error) { key := instID{ Name: id.Name, Description: id.Description, @@ -142,7 +142,7 @@ func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int6 for _, insert := range m.int64Resolver.inserters { // Connect the measure functions for instruments in this pipeline with the // callbacks for this pipeline. - in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind)) + in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind), optIn) if err != nil { return inst, err } @@ -188,7 +188,7 @@ func (m *meter) Int64ObservableCounter( Kind: InstrumentKindObservableCounter, Scope: m.scope, } - return m.int64ObservableInstrument(id, cfg.Callbacks()) + return m.int64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } // Int64ObservableUpDownCounter returns a new instrument identified by name and @@ -212,7 +212,7 @@ func (m *meter) Int64ObservableUpDownCounter( Kind: InstrumentKindObservableUpDownCounter, Scope: m.scope, } - return m.int64ObservableInstrument(id, cfg.Callbacks()) + return m.int64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } // Int64ObservableGauge returns a new instrument identified by name and @@ -236,7 +236,7 @@ func (m *meter) Int64ObservableGauge( Kind: InstrumentKindObservableGauge, Scope: m.scope, } - return m.int64ObservableInstrument(id, cfg.Callbacks()) + return m.int64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } // Float64Counter returns a new instrument identified by name and configured @@ -246,7 +246,7 @@ func (m *meter) Float64Counter(name string, options ...metric.Float64CounterOpti cfg := metric.NewFloat64CounterConfig(options...) const kind = InstrumentKindCounter p := float64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -264,7 +264,7 @@ func (m *meter) Float64UpDownCounter( cfg := metric.NewFloat64UpDownCounterConfig(options...) const kind = InstrumentKindUpDownCounter p := float64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -296,7 +296,7 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) cfg := metric.NewFloat64GaugeConfig(options...) const kind = InstrumentKindGauge p := float64InstProvider{m} - i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), cfg.OptIn()) if err != nil { return i, err } @@ -309,6 +309,7 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) func (m *meter) float64ObservableInstrument( id Instrument, callbacks []metric.Float64Callback, + optIn bool, ) (float64Observable, error) { key := instID{ Name: id.Name, @@ -324,7 +325,7 @@ func (m *meter) float64ObservableInstrument( for _, insert := range m.float64Resolver.inserters { // Connect the measure functions for instruments in this pipeline with the // callbacks for this pipeline. - in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind)) + in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind), optIn) if err != nil { return inst, err } @@ -370,7 +371,7 @@ func (m *meter) Float64ObservableCounter( Kind: InstrumentKindObservableCounter, Scope: m.scope, } - return m.float64ObservableInstrument(id, cfg.Callbacks()) + return m.float64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } // Float64ObservableUpDownCounter returns a new instrument identified by name @@ -394,7 +395,7 @@ func (m *meter) Float64ObservableUpDownCounter( Kind: InstrumentKindObservableUpDownCounter, Scope: m.scope, } - return m.float64ObservableInstrument(id, cfg.Callbacks()) + return m.float64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } // Float64ObservableGauge returns a new instrument identified by name and @@ -418,7 +419,7 @@ func (m *meter) Float64ObservableGauge( Kind: InstrumentKindObservableGauge, Scope: m.scope, } - return m.float64ObservableInstrument(id, cfg.Callbacks()) + return m.float64ObservableInstrument(id, cfg.Callbacks(), cfg.OptIn()) } func validateInstrumentName(name string) error { @@ -633,7 +634,7 @@ func (noopRegister) Unregister() error { // int64InstProvider provides int64 OpenTelemetry instruments. type int64InstProvider struct{ *meter } -func (p int64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]aggregate.Measure[int64], error) { +func (p int64InstProvider) aggs(kind InstrumentKind, name, desc, u string, optIn bool) ([]aggregate.Measure[int64], error) { inst := Instrument{ Name: name, Description: desc, @@ -641,7 +642,7 @@ func (p int64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]ag Kind: kind, Scope: p.scope, } - return p.int64Resolver.Aggregators(inst) + return p.int64Resolver.Aggregators(inst, optIn) } func (p int64InstProvider) histogramAggs( @@ -661,19 +662,19 @@ func (p int64InstProvider) histogramAggs( Kind: InstrumentKindHistogram, Scope: p.scope, } - measures, err := p.int64Resolver.HistogramAggregators(inst, boundaries) + measures, err := p.int64Resolver.HistogramAggregators(inst, boundaries, cfg.OptIn()) return measures, errors.Join(aggError, err) } // lookup returns the resolved instrumentImpl. -func (p int64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*int64Inst, error) { +func (p int64InstProvider) lookup(kind InstrumentKind, name, desc, u string, optIn bool) (*int64Inst, error) { return p.int64Insts.Lookup(instID{ Name: name, Description: desc, Unit: u, Kind: kind, }, func() (*int64Inst, error) { - aggs, err := p.aggs(kind, name, desc, u) + aggs, err := p.aggs(kind, name, desc, u, optIn) return &int64Inst{measures: aggs}, err }) } @@ -694,7 +695,7 @@ func (p int64InstProvider) lookupHistogram(name string, cfg metric.Int64Histogra // float64InstProvider provides float64 OpenTelemetry instruments. type float64InstProvider struct{ *meter } -func (p float64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]aggregate.Measure[float64], error) { +func (p float64InstProvider) aggs(kind InstrumentKind, name, desc, u string, optIn bool) ([]aggregate.Measure[float64], error) { inst := Instrument{ Name: name, Description: desc, @@ -702,7 +703,7 @@ func (p float64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([] Kind: kind, Scope: p.scope, } - return p.float64Resolver.Aggregators(inst) + return p.float64Resolver.Aggregators(inst, optIn) } func (p float64InstProvider) histogramAggs( @@ -722,19 +723,19 @@ func (p float64InstProvider) histogramAggs( Kind: InstrumentKindHistogram, Scope: p.scope, } - measures, err := p.float64Resolver.HistogramAggregators(inst, boundaries) + measures, err := p.float64Resolver.HistogramAggregators(inst, boundaries, cfg.OptIn()) return measures, errors.Join(aggError, err) } // lookup returns the resolved instrumentImpl. -func (p float64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*float64Inst, error) { +func (p float64InstProvider) lookup(kind InstrumentKind, name, desc, u string, optIn bool) (*float64Inst, error) { return p.float64Insts.Lookup(instID{ Name: name, Description: desc, Unit: u, Kind: kind, }, func() (*float64Inst, error) { - aggs, err := p.aggs(kind, name, desc, u) + aggs, err := p.aggs(kind, name, desc, u, optIn) return &float64Inst{measures: aggs}, err }) } diff --git a/sdk/metric/pipeline.go b/sdk/metric/pipeline.go index 408fddc8d4e..d550cba56ab 100644 --- a/sdk/metric/pipeline.go +++ b/sdk/metric/pipeline.go @@ -236,7 +236,7 @@ func newInserter[N int64 | float64](p *pipeline, vc *cache[string, instID]) *ins // // If an instrument is determined to use a Drop aggregation, that instrument is // not inserted nor returned. -func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation) ([]aggregate.Measure[N], error) { +func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation, optIn bool) ([]aggregate.Measure[N], error) { var ( matched bool measures []aggregate.Measure[N] @@ -250,7 +250,7 @@ func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation) continue } matched = true - in, id, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation) + in, id, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation, optIn) if e != nil { err = errors.Join(err, e) } @@ -279,7 +279,7 @@ func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation) Description: inst.Description, Unit: inst.Unit, } - in, _, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation) + in, _, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation, optIn) if e != nil { if err == nil { err = errCreatingAggregators @@ -354,6 +354,7 @@ func (i *inserter[N]) cachedAggregator( kind InstrumentKind, stream Stream, readerAggregation Aggregation, + optIn bool, ) (meas aggregate.Measure[N], aggID uint64, err error) { switch stream.Aggregation.(type) { case nil: @@ -368,6 +369,12 @@ func (i *inserter[N]) cachedAggregator( stream.ExemplarReservoirProviderSelector = DefaultExemplarReservoirProviderSelector } + isEnabled := stream.Enabled != nil && *stream.Enabled + isDisabled := stream.Enabled != nil && !*stream.Enabled + if (optIn && !isEnabled) || isDisabled { + stream.Aggregation = AggregationDrop{} + } + if err := isAggregatorCompatible(kind, stream.Aggregation); err != nil { return nil, 0, fmt.Errorf( "creating aggregator with instrumentKind: %d, aggregation %v: %w", @@ -641,12 +648,12 @@ func newResolver[N int64 | float64](p pipelines, vc *cache[string, instID]) reso // Aggregators returns the Aggregators that must be updated by the instrument // defined by key. -func (r resolver[N]) Aggregators(id Instrument) ([]aggregate.Measure[N], error) { +func (r resolver[N]) Aggregators(id Instrument, optIn bool) ([]aggregate.Measure[N], error) { var measures []aggregate.Measure[N] var err error for _, i := range r.inserters { - in, e := i.Instrument(id, i.readerDefaultAggregation(id.Kind)) + in, e := i.Instrument(id, i.readerDefaultAggregation(id.Kind), optIn) if e != nil { err = errors.Join(err, e) } @@ -658,7 +665,7 @@ func (r resolver[N]) Aggregators(id Instrument) ([]aggregate.Measure[N], error) // HistogramAggregators returns the histogram Aggregators that must be updated by the instrument // defined by key. If boundaries were provided on instrument instantiation, those take precedence // over boundaries provided by the reader. -func (r resolver[N]) HistogramAggregators(id Instrument, boundaries []float64) ([]aggregate.Measure[N], error) { +func (r resolver[N]) HistogramAggregators(id Instrument, boundaries []float64, optIn bool) ([]aggregate.Measure[N], error) { var measures []aggregate.Measure[N] var err error @@ -668,7 +675,7 @@ func (r resolver[N]) HistogramAggregators(id Instrument, boundaries []float64) ( histAgg.Boundaries = boundaries agg = histAgg } - in, e := i.Instrument(id, agg) + in, e := i.Instrument(id, agg, optIn) if e != nil { err = errors.Join(err, e) } diff --git a/sdk/metric/pipeline_registry_test.go b/sdk/metric/pipeline_registry_test.go index e7b3fb95709..dd939d8f908 100644 --- a/sdk/metric/pipeline_registry_test.go +++ b/sdk/metric/pipeline_registry_test.go @@ -394,7 +394,7 @@ func testCreateAggregators[N int64 | float64](t *testing.T) { p := newPipeline(nil, tt.reader, tt.views, exemplar.AlwaysOffFilter, 0) i := newInserter[N](p, &c) readerAggregation := i.readerDefaultAggregation(tt.inst.Kind) - input, err := i.Instrument(tt.inst, readerAggregation) + input, err := i.Instrument(tt.inst, readerAggregation, false) var comps []aggregate.ComputeAggregation for _, instSyncs := range p.aggregations { for _, i := range instSyncs { @@ -419,7 +419,7 @@ func testInvalidInstrumentShouldPanic[N int64 | float64]() { Kind: InstrumentKind(255), } readerAggregation := i.readerDefaultAggregation(inst.Kind) - _, _ = i.Instrument(inst, readerAggregation) + _, _ = i.Instrument(inst, readerAggregation, false) } func TestInvalidInstrumentShouldPanic(t *testing.T) { @@ -435,7 +435,7 @@ func TestPipelinesAggregatorForEachReader(t *testing.T) { inst := Instrument{Name: "foo", Kind: InstrumentKindCounter} var c cache[string, instID] r := newResolver[int64](pipes, &c) - aggs, err := r.Aggregators(inst) + aggs, err := r.Aggregators(inst, false) require.NoError(t, err, "resolved Aggregators error") require.Len(t, aggs, 2, "instrument aggregators") @@ -516,7 +516,7 @@ func testPipelineRegistryResolveIntAggregators(t *testing.T, p pipelines, wantCo inst := Instrument{Name: "foo", Kind: InstrumentKindCounter} var c cache[string, instID] r := newResolver[int64](p, &c) - aggs, err := r.Aggregators(inst) + aggs, err := r.Aggregators(inst, false) assert.NoError(t, err) require.Len(t, aggs, wantCount) @@ -526,7 +526,7 @@ func testPipelineRegistryResolveFloatAggregators(t *testing.T, p pipelines, want inst := Instrument{Name: "foo", Kind: InstrumentKindCounter} var c cache[string, instID] r := newResolver[float64](p, &c) - aggs, err := r.Aggregators(inst) + aggs, err := r.Aggregators(inst, false) assert.NoError(t, err) require.Len(t, aggs, wantCount) @@ -536,7 +536,7 @@ func testPipelineRegistryResolveIntHistogramAggregators(t *testing.T, p pipeline inst := Instrument{Name: "foo", Kind: InstrumentKindCounter} var c cache[string, instID] r := newResolver[int64](p, &c) - aggs, err := r.HistogramAggregators(inst, []float64{1, 2, 3}) + aggs, err := r.HistogramAggregators(inst, []float64{1, 2, 3}, false) assert.NoError(t, err) require.Len(t, aggs, wantCount) @@ -546,7 +546,7 @@ func testPipelineRegistryResolveFloatHistogramAggregators(t *testing.T, p pipeli inst := Instrument{Name: "foo", Kind: InstrumentKindCounter} var c cache[string, instID] r := newResolver[float64](p, &c) - aggs, err := r.HistogramAggregators(inst, []float64{1, 2, 3}) + aggs, err := r.HistogramAggregators(inst, []float64{1, 2, 3}, false) assert.NoError(t, err) require.Len(t, aggs, wantCount) @@ -575,20 +575,20 @@ func TestPipelineRegistryCreateAggregatorsIncompatibleInstrument(t *testing.T) { var vc cache[string, instID] ri := newResolver[int64](p, &vc) - intAggs, err := ri.Aggregators(inst) + intAggs, err := ri.Aggregators(inst, false) assert.Error(t, err) assert.Empty(t, intAggs) rf := newResolver[float64](p, &vc) - floatAggs, err := rf.Aggregators(inst) + floatAggs, err := rf.Aggregators(inst, false) assert.Error(t, err) assert.Empty(t, floatAggs) - intAggs, err = ri.HistogramAggregators(inst, []float64{1, 2, 3}) + intAggs, err = ri.HistogramAggregators(inst, []float64{1, 2, 3}, false) assert.Error(t, err) assert.Empty(t, intAggs) - floatAggs, err = rf.HistogramAggregators(inst, []float64{1, 2, 3}) + floatAggs, err = rf.HistogramAggregators(inst, []float64{1, 2, 3}, false) assert.Error(t, err) assert.Empty(t, floatAggs) } @@ -634,14 +634,14 @@ func TestResolveAggregatorsDuplicateErrors(t *testing.T) { var vc cache[string, instID] ri := newResolver[int64](p, &vc) - intAggs, err := ri.Aggregators(fooInst) + intAggs, err := ri.Aggregators(fooInst, false) assert.NoError(t, err) assert.Equal(t, 0, l.InfoN(), "no info logging should happen") assert.Len(t, intAggs, 1) // The Rename view should produce the same instrument without an error, the // default view should also cause a new aggregator to be returned. - intAggs, err = ri.Aggregators(barInst) + intAggs, err = ri.Aggregators(barInst, false) assert.NoError(t, err) assert.Equal(t, 0, l.InfoN(), "no info logging should happen") assert.Len(t, intAggs, 2) @@ -649,19 +649,19 @@ func TestResolveAggregatorsDuplicateErrors(t *testing.T) { // Creating a float foo instrument should log a warning because there is an // int foo instrument. rf := newResolver[float64](p, &vc) - floatAggs, err := rf.Aggregators(fooInst) + floatAggs, err := rf.Aggregators(fooInst, false) assert.NoError(t, err) assert.Equal(t, 1, l.InfoN(), "instrument conflict not logged") assert.Len(t, floatAggs, 1) fooInst = Instrument{Name: "foo-float", Kind: InstrumentKindCounter} - floatAggs, err = rf.Aggregators(fooInst) + floatAggs, err = rf.Aggregators(fooInst, false) assert.NoError(t, err) assert.Equal(t, 0, l.InfoN(), "no info logging should happen") assert.Len(t, floatAggs, 1) - floatAggs, err = rf.Aggregators(barInst) + floatAggs, err = rf.Aggregators(barInst, false) assert.NoError(t, err) // Both the rename and default view aggregators created above should now // conflict. Therefore, 2 warning messages should be logged. diff --git a/sdk/metric/pipeline_test.go b/sdk/metric/pipeline_test.go index ca0b34f826b..250258982df 100644 --- a/sdk/metric/pipeline_test.go +++ b/sdk/metric/pipeline_test.go @@ -159,7 +159,7 @@ func testDefaultViewImplicit[N int64 | float64]() func(t *testing.T) { var c cache[string, instID] i := newInserter[N](test.pipe, &c) readerAggregation := i.readerDefaultAggregation(inst.Kind) - got, err := i.Instrument(inst, readerAggregation) + got, err := i.Instrument(inst, readerAggregation, false) require.NoError(t, err) assert.Len(t, got, 1, "default view not applied") for _, in := range got { @@ -386,7 +386,7 @@ func TestInserterCachedAggregatorNameConflict(t *testing.T) { i := newInserter[int64](pipe, &vc) readerAggregation := i.readerDefaultAggregation(kind) - _, origID, err := i.cachedAggregator(scope, kind, stream, readerAggregation) + _, origID, err := i.cachedAggregator(scope, kind, stream, readerAggregation, false) require.NoError(t, err) require.Len(t, pipe.aggregations, 1) @@ -396,7 +396,7 @@ func TestInserterCachedAggregatorNameConflict(t *testing.T) { require.Equal(t, name, iSync[0].name) stream.Name = "RequestCount" - _, id, err := i.cachedAggregator(scope, kind, stream, readerAggregation) + _, id, err := i.cachedAggregator(scope, kind, stream, readerAggregation, false) require.NoError(t, err) assert.Equal(t, origID, id, "multiple aggregators for equivalent name") diff --git a/sdk/metric/view.go b/sdk/metric/view.go index 630890f4263..ca3d03ecaf2 100644 --- a/sdk/metric/view.go +++ b/sdk/metric/view.go @@ -102,6 +102,7 @@ func NewView(criteria Instrument, mask Stream) View { Aggregation: agg, AttributeFilter: mask.AttributeFilter, ExemplarReservoirProviderSelector: mask.ExemplarReservoirProviderSelector, + Enabled: mask.Enabled, }, true } return Stream{}, false