From dafe137bbebad8aae48e7336240c317dbebf6b40 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 16 May 2024 09:56:40 -0700 Subject: [PATCH] Add the synchronous gauge to the metric API and SDK (#5304) Resolve #5225 The specification has [added a synchronous gauge instrument](https://github.com/open-telemetry/opentelemetry-specification/pull/3540). That instrument has now been [stabilized](https://github.com/open-telemetry/opentelemetry-specification/pull/4019), and that stabilization is included in the [next release](https://github.com/open-telemetry/opentelemetry-specification/pull/4034). This adds the new synchronous gauge instrument to the metric API and all implementation we publish. This change will be a breaking change for any SDK developer. The `embedded` package is updated to ensure our compatibility guarantees are meet. --------- Co-authored-by: David Ashpole --- CHANGELOG.md | 3 ++ internal/global/instruments.go | 52 ++++++++++++++++++++++++++++ internal/global/instruments_test.go | 14 ++++++++ internal/global/meter.go | 22 ++++++++++++ internal/global/meter_test.go | 6 ++++ internal/global/meter_types_test.go | 12 +++++++ metric/embedded/embedded.go | 20 +++++++++++ metric/instrument.go | 22 ++++++++++++ metric/meter.go | 8 +++++ metric/noop/noop.go | 28 +++++++++++++++ metric/noop/noop_test.go | 8 +++++ metric/syncfloat64.go | 52 ++++++++++++++++++++++++++++ metric/syncfloat64_test.go | 4 +++ metric/syncint64.go | 52 ++++++++++++++++++++++++++++ metric/syncint64_test.go | 4 +++ sdk/metric/instrument.go | 8 +++++ sdk/metric/instrument_test.go | 7 +++- sdk/metric/instrumentkind_string.go | 11 +++--- sdk/metric/meter.go | 30 ++++++++++++++++ sdk/metric/pipeline.go | 15 +++++--- sdk/metric/pipeline_registry_test.go | 45 ++++++++++++++++++++++++ sdk/metric/reader.go | 2 +- sdk/metric/reader_test.go | 2 ++ sdk/metric/view_test.go | 1 + 24 files changed, 416 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0658330c74..b6e2814363c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `RecordFactory` in `go.opentelemetry.io/otel/sdk/log/logtest` to facilitate testing the exporter and processor implementations. (#5258) - Add example for `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`. (#5242) - The count of dropped records from the `BatchProcessor` in `go.opentelemetry.io/otel/sdk/log` is logged. (#5276) +- Add the synchronous gauge instrument to `go.opentelemetry.io/otel/metric`. (#5304) + - An `int64` or `float64` synchronous gauge instrument can now be created from a `Meter`. + - All implementations of the API (`go.opentelemetry.io/otel/metric/noop`, `go.opentelemetry.io/otel/sdk/metric`) are updated to support this instrument. - Add logs to `go.opentelemetry.io/otel/example/dice`. (#5349) ### Changed diff --git a/internal/global/instruments.go b/internal/global/instruments.go index 0c8ed20a596..3a0cc42f6a4 100644 --- a/internal/global/instruments.go +++ b/internal/global/instruments.go @@ -281,6 +281,32 @@ func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...metric.Reco } } +type sfGauge struct { + embedded.Float64Gauge + + name string + opts []metric.Float64GaugeOption + + delegate atomic.Value // metric.Float64Gauge +} + +var _ metric.Float64Gauge = (*sfGauge)(nil) + +func (i *sfGauge) setDelegate(m metric.Meter) { + ctr, err := m.Float64Gauge(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *sfGauge) Record(ctx context.Context, x float64, opts ...metric.RecordOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Float64Gauge).Record(ctx, x, opts...) + } +} + type siCounter struct { embedded.Int64Counter @@ -358,3 +384,29 @@ func (i *siHistogram) Record(ctx context.Context, x int64, opts ...metric.Record ctr.(metric.Int64Histogram).Record(ctx, x, opts...) } } + +type siGauge struct { + embedded.Int64Gauge + + name string + opts []metric.Int64GaugeOption + + delegate atomic.Value // metric.Int64Gauge +} + +var _ metric.Int64Gauge = (*siGauge)(nil) + +func (i *siGauge) setDelegate(m metric.Meter) { + ctr, err := m.Int64Gauge(i.name, i.opts...) + if err != nil { + GetErrorHandler().Handle(err) + return + } + i.delegate.Store(ctr) +} + +func (i *siGauge) Record(ctx context.Context, x int64, opts ...metric.RecordOption) { + if ctr := i.delegate.Load(); ctr != nil { + ctr.(metric.Int64Gauge).Record(ctx, x, opts...) + } +} diff --git a/internal/global/instruments_test.go b/internal/global/instruments_test.go index 4e7241f8442..48c772e3b8a 100644 --- a/internal/global/instruments_test.go +++ b/internal/global/instruments_test.go @@ -117,6 +117,12 @@ func TestSyncInstrumentSetDelegateConcurrentSafe(t *testing.T) { f := func(v float64) { delegate.Record(context.Background(), v) } testFloat64ConcurrentSafe(f, delegate.setDelegate) }) + + t.Run("Gauge", func(t *testing.T) { + delegate := &sfGauge{} + f := func(v float64) { delegate.Record(context.Background(), v) } + testFloat64ConcurrentSafe(f, delegate.setDelegate) + }) }) // Int64 Instruments @@ -139,6 +145,12 @@ func TestSyncInstrumentSetDelegateConcurrentSafe(t *testing.T) { f := func(v int64) { delegate.Record(context.Background(), v) } testInt64ConcurrentSafe(f, delegate.setDelegate) }) + + t.Run("Gauge", func(t *testing.T) { + delegate := &siGauge{} + f := func(v int64) { delegate.Record(context.Background(), v) } + testInt64ConcurrentSafe(f, delegate.setDelegate) + }) }) } @@ -149,6 +161,7 @@ type testCountingFloatInstrument struct { embedded.Float64Counter embedded.Float64UpDownCounter embedded.Float64Histogram + embedded.Float64Gauge embedded.Float64ObservableCounter embedded.Float64ObservableUpDownCounter embedded.Float64ObservableGauge @@ -173,6 +186,7 @@ type testCountingIntInstrument struct { embedded.Int64Counter embedded.Int64UpDownCounter embedded.Int64Histogram + embedded.Int64Gauge embedded.Int64ObservableCounter embedded.Int64ObservableUpDownCounter embedded.Int64ObservableGauge diff --git a/internal/global/meter.go b/internal/global/meter.go index f21898591e5..590fa7385f3 100644 --- a/internal/global/meter.go +++ b/internal/global/meter.go @@ -164,6 +164,17 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti return i, nil } +func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (metric.Int64Gauge, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Int64Gauge(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &siGauge{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { if del, ok := m.delegate.Load().(metric.Meter); ok { return del.Int64ObservableCounter(name, options...) @@ -230,6 +241,17 @@ func (m *meter) Float64Histogram(name string, options ...metric.Float64Histogram return i, nil } +func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) (metric.Float64Gauge, error) { + if del, ok := m.delegate.Load().(metric.Meter); ok { + return del.Float64Gauge(name, options...) + } + m.mtx.Lock() + defer m.mtx.Unlock() + i := &sfGauge{name: name, opts: options} + m.instruments = append(m.instruments, i) + return i, nil +} + func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { if del, ok := m.delegate.Load().(metric.Meter); ok { return del.Float64ObservableCounter(name, options...) diff --git a/internal/global/meter_test.go b/internal/global/meter_test.go index 4962eedb9fe..19cc15884b9 100644 --- a/internal/global/meter_test.go +++ b/internal/global/meter_test.go @@ -61,9 +61,11 @@ func TestMeterConcurrentSafe(t *testing.T) { _, _ = mtr.Float64Counter(name) _, _ = mtr.Float64UpDownCounter(name) _, _ = mtr.Float64Histogram(name) + _, _ = mtr.Float64Gauge(name) _, _ = mtr.Int64Counter(name) _, _ = mtr.Int64UpDownCounter(name) _, _ = mtr.Int64Histogram(name) + _, _ = mtr.Int64Gauge(name) _, _ = mtr.RegisterCallback(zeroCallback) if !once { wg.Done() @@ -142,6 +144,8 @@ func testSetupAllInstrumentTypes(t *testing.T, m metric.Meter) (metric.Float64Co assert.NoError(t, err) _, err = m.Float64Histogram("test_Sync_Histogram") assert.NoError(t, err) + _, err = m.Float64Gauge("test_Sync_Gauge") + assert.NoError(t, err) _, err = m.Int64Counter("test_Sync_Counter") assert.NoError(t, err) @@ -149,6 +153,8 @@ func testSetupAllInstrumentTypes(t *testing.T, m metric.Meter) (metric.Float64Co assert.NoError(t, err) _, err = m.Int64Histogram("test_Sync_Histogram") assert.NoError(t, err) + _, err = m.Int64Gauge("test_Sync_Gauge") + assert.NoError(t, err) return sfcounter, afcounter } diff --git a/internal/global/meter_types_test.go b/internal/global/meter_types_test.go index ae4cb9e1619..f23ae1d63e1 100644 --- a/internal/global/meter_types_test.go +++ b/internal/global/meter_types_test.go @@ -36,10 +36,12 @@ type testMeter struct { sfCount int sfUDCount int sfHist int + sfGauge int siCount int siUDCount int siHist int + siGauge int callbacks []metric.Callback } @@ -59,6 +61,11 @@ func (m *testMeter) Int64Histogram(name string, options ...metric.Int64Histogram return &testCountingIntInstrument{}, nil } +func (m *testMeter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (metric.Int64Gauge, error) { + m.siGauge++ + return &testCountingIntInstrument{}, nil +} + func (m *testMeter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { m.aiCount++ return &testCountingIntInstrument{}, nil @@ -89,6 +96,11 @@ func (m *testMeter) Float64Histogram(name string, options ...metric.Float64Histo return &testCountingFloatInstrument{}, nil } +func (m *testMeter) Float64Gauge(name string, options ...metric.Float64GaugeOption) (metric.Float64Gauge, error) { + m.sfGauge++ + return &testCountingFloatInstrument{}, nil +} + func (m *testMeter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { m.afCount++ return &testCountingFloatInstrument{}, nil diff --git a/metric/embedded/embedded.go b/metric/embedded/embedded.go index 15bebae084a..1a9dc68093f 100644 --- a/metric/embedded/embedded.go +++ b/metric/embedded/embedded.go @@ -102,6 +102,16 @@ type Float64Counter interface{ float64Counter() } // the API package). type Float64Histogram interface{ float64Histogram() } +// Float64Gauge is embedded in [go.opentelemetry.io/otel/metric.Float64Gauge]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Float64Gauge] if you want users to +// experience a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.Float64Gauge] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Float64Gauge interface{ float64Gauge() } + // Float64ObservableCounter is embedded in // [go.opentelemetry.io/otel/metric.Float64ObservableCounter]. // @@ -174,6 +184,16 @@ type Int64Counter interface{ int64Counter() } // the API package). type Int64Histogram interface{ int64Histogram() } +// Int64Gauge is embedded in [go.opentelemetry.io/otel/metric.Int64Gauge]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/metric.Int64Gauge] if you want users to experience +// a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/metric.Int64Gauge] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type Int64Gauge interface{ int64Gauge() } + // Int64ObservableCounter is embedded in // [go.opentelemetry.io/otel/metric.Int64ObservableCounter]. // diff --git a/metric/instrument.go b/metric/instrument.go index 451413192a9..ea52e402331 100644 --- a/metric/instrument.go +++ b/metric/instrument.go @@ -16,6 +16,7 @@ type InstrumentOption interface { Int64CounterOption Int64UpDownCounterOption Int64HistogramOption + Int64GaugeOption Int64ObservableCounterOption Int64ObservableUpDownCounterOption Int64ObservableGaugeOption @@ -23,6 +24,7 @@ type InstrumentOption interface { Float64CounterOption Float64UpDownCounterOption Float64HistogramOption + Float64GaugeOption Float64ObservableCounterOption Float64ObservableUpDownCounterOption Float64ObservableGaugeOption @@ -51,6 +53,11 @@ func (o descOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64Histogra return c } +func (o descOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig { + c.description = string(o) + return c +} + func (o descOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { c.description = string(o) return c @@ -81,6 +88,11 @@ func (o descOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfi return c } +func (o descOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig { + c.description = string(o) + return c +} + func (o descOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { c.description = string(o) return c @@ -116,6 +128,11 @@ func (o unitOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64Histogra return c } +func (o unitOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig { + c.unit = string(o) + return c +} + func (o unitOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { c.unit = string(o) return c @@ -146,6 +163,11 @@ func (o unitOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfi return c } +func (o unitOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig { + c.unit = string(o) + return c +} + func (o unitOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { c.unit = string(o) return c diff --git a/metric/meter.go b/metric/meter.go index 7aa82e0c17b..460b3f9b08b 100644 --- a/metric/meter.go +++ b/metric/meter.go @@ -58,6 +58,10 @@ type Meter interface { // synchronously record the distribution of int64 measurements during a // computational operation. Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error) + // Int64Gauge returns a new Int64Gauge instrument identified by name and + // configured with options. The instrument is used to synchronously record + // instantaneous int64 measurements during a computational operation. + Int64Gauge(name string, options ...Int64GaugeOption) (Int64Gauge, error) // Int64ObservableCounter returns a new Int64ObservableCounter identified // by name and configured with options. The instrument is used to // asynchronously record increasing int64 measurements once per a @@ -104,6 +108,10 @@ type Meter interface { // synchronously record the distribution of float64 measurements during a // computational operation. Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error) + // Float64Gauge returns a new Float64Gauge instrument identified by name and + // configured with options. The instrument is used to synchronously record + // instantaneous float64 measurements during a computational operation. + Float64Gauge(name string, options ...Float64GaugeOption) (Float64Gauge, error) // Float64ObservableCounter returns a new Float64ObservableCounter // instrument identified by name and configured with options. The // instrument is used to asynchronously record increasing float64 diff --git a/metric/noop/noop.go b/metric/noop/noop.go index 4524a57d251..ca6fcbdc099 100644 --- a/metric/noop/noop.go +++ b/metric/noop/noop.go @@ -32,6 +32,8 @@ var ( _ metric.Float64UpDownCounter = Float64UpDownCounter{} _ metric.Int64Histogram = Int64Histogram{} _ metric.Float64Histogram = Float64Histogram{} + _ metric.Int64Gauge = Int64Gauge{} + _ metric.Float64Gauge = Float64Gauge{} _ metric.Int64ObservableCounter = Int64ObservableCounter{} _ metric.Float64ObservableCounter = Float64ObservableCounter{} _ metric.Int64ObservableGauge = Int64ObservableGauge{} @@ -76,6 +78,12 @@ func (Meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int6 return Int64Histogram{}, nil } +// Int64Gauge returns a Gauge used to record int64 measurements that +// produces no telemetry. +func (Meter) Int64Gauge(string, ...metric.Int64GaugeOption) (metric.Int64Gauge, error) { + return Int64Gauge{}, nil +} + // Int64ObservableCounter returns an ObservableCounter used to record int64 // measurements that produces no telemetry. func (Meter) Int64ObservableCounter(string, ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { @@ -112,6 +120,12 @@ func (Meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric. return Float64Histogram{}, nil } +// Float64Gauge returns a Gauge used to record float64 measurements that +// produces no telemetry. +func (Meter) Float64Gauge(string, ...metric.Float64GaugeOption) (metric.Float64Gauge, error) { + return Float64Gauge{}, nil +} + // Float64ObservableCounter returns an ObservableCounter used to record int64 // measurements that produces no telemetry. func (Meter) Float64ObservableCounter(string, ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { @@ -197,6 +211,20 @@ type Float64Histogram struct{ embedded.Float64Histogram } // Record performs no operation. func (Float64Histogram) Record(context.Context, float64, ...metric.RecordOption) {} +// Int64Gauge is an OpenTelemetry Gauge used to record instantaneous int64 +// measurements. It produces no telemetry. +type Int64Gauge struct{ embedded.Int64Gauge } + +// Record performs no operation. +func (Int64Gauge) Record(context.Context, int64, ...metric.RecordOption) {} + +// Float64Gauge is an OpenTelemetry Gauge used to record instantaneous float64 +// measurements. It produces no telemetry. +type Float64Gauge struct{ embedded.Float64Gauge } + +// Record performs no operation. +func (Float64Gauge) Record(context.Context, float64, ...metric.RecordOption) {} + // Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record // int64 measurements. It produces no telemetry. type Int64ObservableCounter struct { diff --git a/metric/noop/noop_test.go b/metric/noop/noop_test.go index a325f19bb35..4770fb45fd2 100644 --- a/metric/noop/noop_test.go +++ b/metric/noop/noop_test.go @@ -55,6 +55,14 @@ func TestImplementationNoPanics(t *testing.T) { reflect.ValueOf(Float64Histogram{}), reflect.TypeOf((*metric.Float64Histogram)(nil)).Elem(), )) + t.Run("Int64Gauge", assertAllExportedMethodNoPanic( + reflect.ValueOf(Int64Gauge{}), + reflect.TypeOf((*metric.Int64Gauge)(nil)).Elem(), + )) + t.Run("Float64Gauge", assertAllExportedMethodNoPanic( + reflect.ValueOf(Float64Gauge{}), + reflect.TypeOf((*metric.Float64Gauge)(nil)).Elem(), + )) t.Run("Int64ObservableCounter", assertAllExportedMethodNoPanic( reflect.ValueOf(Int64ObservableCounter{}), reflect.TypeOf((*metric.Int64ObservableCounter)(nil)).Elem(), diff --git a/metric/syncfloat64.go b/metric/syncfloat64.go index 1b00a7f6c15..8403a4bad2d 100644 --- a/metric/syncfloat64.go +++ b/metric/syncfloat64.go @@ -172,3 +172,55 @@ func (c Float64HistogramConfig) ExplicitBucketBoundaries() []float64 { type Float64HistogramOption interface { applyFloat64Histogram(Float64HistogramConfig) Float64HistogramConfig } + +// Float64Gauge is an instrument that records instantaneous float64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Float64Gauge interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Float64Gauge + + // Record records the instantaneous value. + // + // 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) +} + +// Float64GaugeConfig contains options for synchronous gauge instruments that +// record float64 values. +type Float64GaugeConfig struct { + description string + unit string +} + +// NewFloat64GaugeConfig returns a new [Float64GaugeConfig] with all opts +// applied. +func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig { + var config Float64GaugeConfig + for _, o := range opts { + config = o.applyFloat64Gauge(config) + } + return config +} + +// Description returns the configured description. +func (c Float64GaugeConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Float64GaugeConfig) Unit() string { + return c.unit +} + +// Float64GaugeOption applies options to a [Float64GaugeConfig]. See +// [InstrumentOption] for other options that can be used as a +// Float64GaugeOption. +type Float64GaugeOption interface { + applyFloat64Gauge(Float64GaugeConfig) Float64GaugeConfig +} diff --git a/metric/syncfloat64_test.go b/metric/syncfloat64_test.go index 13d6296dde1..223eb081b03 100644 --- a/metric/syncfloat64_test.go +++ b/metric/syncfloat64_test.go @@ -34,6 +34,10 @@ func TestFloat64Configuration(t *testing.T) { t.Run("Float64Histogram", run( NewFloat64HistogramConfig(WithDescription(desc), WithUnit(uBytes)), )) + + t.Run("Float64Gauge", run( + NewFloat64GaugeConfig(WithDescription(desc), WithUnit(uBytes)), + )) } type float64Config interface { diff --git a/metric/syncint64.go b/metric/syncint64.go index ec96c7f1816..783fdfba773 100644 --- a/metric/syncint64.go +++ b/metric/syncint64.go @@ -172,3 +172,55 @@ func (c Int64HistogramConfig) ExplicitBucketBoundaries() []float64 { type Int64HistogramOption interface { applyInt64Histogram(Int64HistogramConfig) Int64HistogramConfig } + +// Int64Gauge is an instrument that records instantaneous int64 values. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Int64Gauge interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Int64Gauge + + // Record records the instantaneous value. + // + // 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) +} + +// Int64GaugeConfig contains options for synchronous gauge instruments that +// record int64 values. +type Int64GaugeConfig struct { + description string + unit string +} + +// NewInt64GaugeConfig returns a new [Int64GaugeConfig] with all opts +// applied. +func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig { + var config Int64GaugeConfig + for _, o := range opts { + config = o.applyInt64Gauge(config) + } + return config +} + +// Description returns the configured description. +func (c Int64GaugeConfig) Description() string { + return c.description +} + +// Unit returns the configured unit. +func (c Int64GaugeConfig) Unit() string { + return c.unit +} + +// Int64GaugeOption applies options to a [Int64GaugeConfig]. See +// [InstrumentOption] for other options that can be used as a +// Int64GaugeOption. +type Int64GaugeOption interface { + applyInt64Gauge(Int64GaugeConfig) Int64GaugeConfig +} diff --git a/metric/syncint64_test.go b/metric/syncint64_test.go index b648de67b21..9bf52bf325f 100644 --- a/metric/syncint64_test.go +++ b/metric/syncint64_test.go @@ -34,6 +34,10 @@ func TestInt64Configuration(t *testing.T) { t.Run("Int64Histogram", run( NewInt64HistogramConfig(WithDescription(desc), WithUnit(uBytes)), )) + + t.Run("Int64Gauge", run( + NewInt64GaugeConfig(WithDescription(desc), WithUnit(uBytes)), + )) } type int64Config interface { diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index fa8ce053b6c..22845895e16 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -42,6 +42,10 @@ const ( // distribution of values synchronously with the code path they are // measuring. InstrumentKindHistogram + // InstrumentKindGauge identifies a group of instruments that record + // instantaneous values synchronously with the code path they are + // measuring. + InstrumentKindGauge // InstrumentKindObservableCounter identifies a group of instruments that // record increasing values in an asynchronous callback. InstrumentKindObservableCounter @@ -175,12 +179,14 @@ type int64Inst struct { embedded.Int64Counter embedded.Int64UpDownCounter embedded.Int64Histogram + embedded.Int64Gauge } var ( _ metric.Int64Counter = (*int64Inst)(nil) _ metric.Int64UpDownCounter = (*int64Inst)(nil) _ metric.Int64Histogram = (*int64Inst)(nil) + _ metric.Int64Gauge = (*int64Inst)(nil) ) func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) { @@ -205,12 +211,14 @@ type float64Inst struct { embedded.Float64Counter embedded.Float64UpDownCounter embedded.Float64Histogram + embedded.Float64Gauge } var ( _ metric.Float64Counter = (*float64Inst)(nil) _ metric.Float64UpDownCounter = (*float64Inst)(nil) _ metric.Float64Histogram = (*float64Inst)(nil) + _ metric.Float64Gauge = (*float64Inst)(nil) ) func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) { diff --git a/sdk/metric/instrument_test.go b/sdk/metric/instrument_test.go index 60066f425aa..3a8c2d26c3a 100644 --- a/sdk/metric/instrument_test.go +++ b/sdk/metric/instrument_test.go @@ -25,7 +25,12 @@ func BenchmarkInstrument(b *testing.B) { build := aggregate.Builder[int64]{} var meas []aggregate.Measure[int64] - in, _ := build.PrecomputedLastValue() + build.Temporality = metricdata.CumulativeTemporality + in, _ := build.LastValue() + meas = append(meas, in) + + build.Temporality = metricdata.DeltaTemporality + in, _ = build.LastValue() meas = append(meas, in) build.Temporality = metricdata.CumulativeTemporality diff --git a/sdk/metric/instrumentkind_string.go b/sdk/metric/instrumentkind_string.go index d5f9e982c2b..8fc3d851fdb 100644 --- a/sdk/metric/instrumentkind_string.go +++ b/sdk/metric/instrumentkind_string.go @@ -12,14 +12,15 @@ func _() { _ = x[InstrumentKindCounter-1] _ = x[InstrumentKindUpDownCounter-2] _ = x[InstrumentKindHistogram-3] - _ = x[InstrumentKindObservableCounter-4] - _ = x[InstrumentKindObservableUpDownCounter-5] - _ = x[InstrumentKindObservableGauge-6] + _ = x[InstrumentKindGauge-4] + _ = x[InstrumentKindObservableCounter-5] + _ = x[InstrumentKindObservableUpDownCounter-6] + _ = x[InstrumentKindObservableGauge-7] } -const _InstrumentKind_name = "instrumentKindUndefinedCounterUpDownCounterHistogramObservableCounterObservableUpDownCounterObservableGauge" +const _InstrumentKind_name = "instrumentKindUndefinedCounterUpDownCounterHistogramGaugeObservableCounterObservableUpDownCounterObservableGauge" -var _InstrumentKind_index = [...]uint8{0, 23, 30, 43, 52, 69, 92, 107} +var _InstrumentKind_index = [...]uint8{0, 23, 30, 43, 52, 57, 74, 97, 112} func (i InstrumentKind) String() string { if i >= InstrumentKind(len(_InstrumentKind_index)-1) { diff --git a/sdk/metric/meter.go b/sdk/metric/meter.go index 7840c48647a..479b7610eb1 100644 --- a/sdk/metric/meter.go +++ b/sdk/metric/meter.go @@ -108,6 +108,21 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti return i, validateInstrumentName(name) } +// Int64Gauge returns a new instrument identified by name and configured +// with options. The instrument is used to synchronously record the +// distribution of int64 measurements during a computational operation. +func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (metric.Int64Gauge, error) { + cfg := metric.NewInt64GaugeConfig(options...) + const kind = InstrumentKindGauge + p := int64InstProvider{m} + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + if err != nil { + return i, err + } + + return i, validateInstrumentName(name) +} + // 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) { @@ -242,6 +257,21 @@ func (m *meter) Float64Histogram(name string, options ...metric.Float64Histogram return i, validateInstrumentName(name) } +// Float64Gauge returns a new instrument identified by name and configured +// with options. The instrument is used to synchronously record the +// distribution of float64 measurements during a computational operation. +func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) (metric.Float64Gauge, error) { + cfg := metric.NewFloat64GaugeConfig(options...) + const kind = InstrumentKindGauge + p := float64InstProvider{m} + i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) + if err != nil { + return i, err + } + + return i, validateInstrumentName(name) +} + // float64ObservableInstrument returns a new observable identified by the Instrument. // It registers callbacks for each reader's pipeline. func (m *meter) float64ObservableInstrument(id Instrument, callbacks []metric.Float64Callback) (float64Observable, error) { diff --git a/sdk/metric/pipeline.go b/sdk/metric/pipeline.go index 45dab6619f2..c6f9597198c 100644 --- a/sdk/metric/pipeline.go +++ b/sdk/metric/pipeline.go @@ -447,10 +447,12 @@ func (i *inserter[N]) aggregateFunc(b aggregate.Builder[N], agg Aggregation, kin case AggregationDrop: // Return nil in and out to signify the drop aggregator. case AggregationLastValue: - if kind == InstrumentKindObservableGauge { + switch kind { + case InstrumentKindGauge: + meas, comp = b.LastValue() + case InstrumentKindObservableGauge: meas, comp = b.PrecomputedLastValue() } - // TODO (#5304): Support synchronous gauges. case AggregationSum: switch kind { case InstrumentKindObservableCounter: @@ -467,7 +469,7 @@ func (i *inserter[N]) aggregateFunc(b aggregate.Builder[N], agg Aggregation, kin case AggregationExplicitBucketHistogram: var noSum bool switch kind { - case InstrumentKindUpDownCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge: + case InstrumentKindUpDownCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge, InstrumentKindGauge: // The sum should not be collected for any instrument that can make // negative measurements: // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/metrics/sdk.md#histogram-aggregations @@ -477,7 +479,7 @@ func (i *inserter[N]) aggregateFunc(b aggregate.Builder[N], agg Aggregation, kin case AggregationBase2ExponentialHistogram: var noSum bool switch kind { - case InstrumentKindUpDownCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge: + case InstrumentKindUpDownCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge, InstrumentKindGauge: // The sum should not be collected for any instrument that can make // negative measurements: // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/metrics/sdk.md#histogram-aggregations @@ -500,6 +502,7 @@ func (i *inserter[N]) aggregateFunc(b aggregate.Builder[N], agg Aggregation, kin // | Counter | ✓ | | ✓ | ✓ | ✓ | // | UpDownCounter | ✓ | | ✓ | ✓ | ✓ | // | Histogram | ✓ | | ✓ | ✓ | ✓ | +// | Gauge | ✓ | ✓ | | ✓ | ✓ | // | Observable Counter | ✓ | | ✓ | ✓ | ✓ | // | Observable UpDownCounter | ✓ | | ✓ | ✓ | ✓ | // | Observable Gauge | ✓ | ✓ | | ✓ | ✓ |. @@ -512,6 +515,7 @@ func isAggregatorCompatible(kind InstrumentKind, agg Aggregation) error { case InstrumentKindCounter, InstrumentKindUpDownCounter, InstrumentKindHistogram, + InstrumentKindGauge, InstrumentKindObservableCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge: @@ -529,7 +533,8 @@ func isAggregatorCompatible(kind InstrumentKind, agg Aggregation) error { return errIncompatibleAggregation } case AggregationLastValue: - if kind == InstrumentKindObservableGauge { + switch kind { + case InstrumentKindObservableGauge, InstrumentKindGauge: return nil } // TODO: review need for aggregation check after diff --git a/sdk/metric/pipeline_registry_test.go b/sdk/metric/pipeline_registry_test.go index 0311e28cc35..ea0b01daa59 100644 --- a/sdk/metric/pipeline_registry_test.go +++ b/sdk/metric/pipeline_registry_test.go @@ -149,6 +149,7 @@ func testCreateAggregators[N int64 | float64](t *testing.T) { {Name: "foo", Kind: InstrumentKindCounter}, {Name: "foo", Kind: InstrumentKindUpDownCounter}, {Name: "foo", Kind: InstrumentKindHistogram}, + {Name: "foo", Kind: InstrumentKindGauge}, {Name: "foo", Kind: InstrumentKindObservableCounter}, {Name: "foo", Kind: InstrumentKindObservableUpDownCounter}, {Name: "foo", Kind: InstrumentKindObservableGauge}, @@ -184,6 +185,12 @@ func testCreateAggregators[N int64 | float64](t *testing.T) { inst: instruments[InstrumentKindHistogram], validate: assertHist[N](metricdata.DeltaTemporality), }, + { + name: "Default/Delta/Gauge", + reader: NewManualReader(WithTemporalitySelector(deltaTemporalitySelector)), + inst: instruments[InstrumentKindGauge], + validate: assertLastValue[N], + }, { name: "Default/Delta/PrecomputedSum/Monotonic", reader: NewManualReader(WithTemporalitySelector(deltaTemporalitySelector)), @@ -220,6 +227,12 @@ func testCreateAggregators[N int64 | float64](t *testing.T) { inst: instruments[InstrumentKindHistogram], validate: assertHist[N](metricdata.CumulativeTemporality), }, + { + name: "Default/Cumulative/Gauge", + reader: NewManualReader(), + inst: instruments[InstrumentKindGauge], + validate: assertLastValue[N], + }, { name: "Default/Cumulative/PrecomputedSum/Monotonic", reader: NewManualReader(), @@ -307,6 +320,12 @@ func testCreateAggregators[N int64 | float64](t *testing.T) { inst: instruments[InstrumentKindHistogram], validate: assertHist[N](metricdata.CumulativeTemporality), }, + { + name: "Reader/Default/Cumulative/Gauge", + reader: NewManualReader(WithAggregationSelector(func(ik InstrumentKind) Aggregation { return AggregationDefault{} })), + inst: instruments[InstrumentKindGauge], + validate: assertLastValue[N], + }, { name: "Reader/Default/Cumulative/PrecomputedSum/Monotonic", reader: NewManualReader(WithAggregationSelector(func(ik InstrumentKind) Aggregation { return AggregationDefault{} })), @@ -699,6 +718,32 @@ func TestIsAggregatorCompatible(t *testing.T) { kind: InstrumentKindHistogram, agg: AggregationBase2ExponentialHistogram{}, }, + { + name: "SyncGauge and Drop", + kind: InstrumentKindGauge, + agg: AggregationDrop{}, + }, + { + name: "SyncGauge and LastValue", + kind: InstrumentKindGauge, + agg: AggregationLastValue{}, + }, + { + name: "SyncGauge and Sum", + kind: InstrumentKindGauge, + agg: AggregationSum{}, + want: errIncompatibleAggregation, + }, + { + name: "SyncGauge and ExplicitBucketHistogram", + kind: InstrumentKindGauge, + agg: AggregationExplicitBucketHistogram{}, + }, + { + name: "SyncGauge and ExponentialHistogram", + kind: InstrumentKindGauge, + agg: AggregationBase2ExponentialHistogram{}, + }, { name: "ObservableCounter and Drop", kind: InstrumentKindObservableCounter, diff --git a/sdk/metric/reader.go b/sdk/metric/reader.go index 1d7403122f7..a55f9a5372c 100644 --- a/sdk/metric/reader.go +++ b/sdk/metric/reader.go @@ -148,7 +148,7 @@ func DefaultAggregationSelector(ik InstrumentKind) Aggregation { switch ik { case InstrumentKindCounter, InstrumentKindUpDownCounter, InstrumentKindObservableCounter, InstrumentKindObservableUpDownCounter: return AggregationSum{} - case InstrumentKindObservableGauge: + case InstrumentKindObservableGauge, InstrumentKindGauge: return AggregationLastValue{} case InstrumentKindHistogram: return AggregationExplicitBucketHistogram{ diff --git a/sdk/metric/reader_test.go b/sdk/metric/reader_test.go index 04a51693890..7646a4ad0bf 100644 --- a/sdk/metric/reader_test.go +++ b/sdk/metric/reader_test.go @@ -300,6 +300,7 @@ func TestDefaultAggregationSelector(t *testing.T) { InstrumentKindCounter, InstrumentKindUpDownCounter, InstrumentKindHistogram, + InstrumentKindGauge, InstrumentKindObservableCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge, @@ -317,6 +318,7 @@ func TestDefaultTemporalitySelector(t *testing.T) { InstrumentKindCounter, InstrumentKindUpDownCounter, InstrumentKindHistogram, + InstrumentKindGauge, InstrumentKindObservableCounter, InstrumentKindObservableUpDownCounter, InstrumentKindObservableGauge, diff --git a/sdk/metric/view_test.go b/sdk/metric/view_test.go index c952560fc45..37bf37b7a4d 100644 --- a/sdk/metric/view_test.go +++ b/sdk/metric/view_test.go @@ -185,6 +185,7 @@ func TestNewViewMatch(t *testing.T) { {}, {Kind: InstrumentKindUpDownCounter}, {Kind: InstrumentKindHistogram}, + {Kind: InstrumentKindGauge}, {Kind: InstrumentKindObservableCounter}, {Kind: InstrumentKindObservableUpDownCounter}, {Kind: InstrumentKindObservableGauge},