diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e42aeedf4c..0ddf1ebc4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `go.opentelemetry.io/otel/metric/embedded` package. (#3916) - The `Version` function to `go.opentelemetry.io/otel/sdk` to return the SDK version. (#3949) - Add a `WithNamespace` option to `go.opentelemetry.io/otel/exporters/prometheus` to allow users to prefix metrics with a namespace. (#3970) +- The following configuration types were added to `go.opentelemetry.io/otel/metric/instrument` to be used in the configuration of measurement methods. (#3971) + - The `AddConfig` used to hold configuration for addition measurements + - `NewAddConfig` used to create a new `AddConfig` + - `AddOption` used to configure an `AddConfig` + - The `RecordConfig` used to hold configuration for recorded measurements + - `NewRecordConfig` used to create a new `RecordConfig` + - `RecordOption` used to configure a `RecordConfig` + - The `ObserveConfig` used to hold configuration for observed measurements + - `NewObserveConfig` used to create a new `ObserveConfig` + - `ObserveOption` used to configure an `ObserveConfig` +- `WithAttributeSet` and `WithAttributes` are added to `go.opentelemetry.io/otel/metric/instrument`. + They return an option used during a measurement that defines the attribute Set associated with the measurement. (#3971) - The `Version` function to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` to return the OTLP metrics client version. (#3956) - The `Version` function to `go.opentelemetry.io/otel/exporters/otlp/otlptrace` to return the OTLP trace client version. (#3956) @@ -24,6 +36,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Move No-Op implementation from `go.opentelemetry.io/otel/metric` into its own package `go.opentelemetry.io/otel/metric/noop`. (#3941) - `metric.NewNoopMeterProvider` is replaced with `noop.NewMeterProvider` - Wrap `UploadMetrics` error in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/` to improve error message when encountering generic grpc errors. (#3974) +- The measurement methods for all instruments in `go.opentelemetry.io/otel/metric/instrument` accept an option instead of the variadic `"go.opentelemetry.io/otel/attribute".KeyValue`. (#3971) + - The `Int64Counter.Add` method now accepts `...AddOption` + - The `Float64Counter.Add` method now accepts `...AddOption` + - The `Int64UpDownCounter.Add` method now accepts `...AddOption` + - The `Float64UpDownCounter.Add` method now accepts `...AddOption` + - The `Int64Histogram.Record` method now accepts `...RecordOption` + - The `Float64Histogram.Record` method now accepts `...RecordOption` + - The `Int64Observer.Observe` method now accepts `...ObserveOption` + - The `Float64Observer.Observe` method now accepts `...ObserveOption` +- The `Observer` methods in `go.opentelemetry.io/otel/metric` accept an option instead of the variadic `"go.opentelemetry.io/otel/attribute".KeyValue`. (#3971) + - The `Observer.ObserveInt64` method now accepts `...ObserveOption` + - The `Observer.ObserveFloat64` method now accepts `...ObserveOption` - Move global metric back to `go.opentelemetry.io/otel/metric/global` from `go.opentelemetry.io/otel`. (#3986) ### Fixed diff --git a/example/prometheus/main.go b/example/prometheus/main.go index 652140d9442..c716aa1e447 100644 --- a/example/prometheus/main.go +++ b/example/prometheus/main.go @@ -50,17 +50,17 @@ func main() { // Start the prometheus HTTP server and pass the exporter Collector to it go serveMetrics() - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) // This is the equivalent of prometheus.NewCounterVec counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) if err != nil { log.Fatal(err) } - counter.Add(ctx, 5, attrs...) + counter.Add(ctx, 5, opt) gauge, err := meter.Float64ObservableGauge("bar", instrument.WithDescription("a fun little gauge")) if err != nil { @@ -68,7 +68,7 @@ func main() { } _, err = meter.RegisterCallback(func(_ context.Context, o api.Observer) error { n := -10. + rng.Float64()*(90.) // [-10, 100) - o.ObserveFloat64(gauge, n, attrs...) + o.ObserveFloat64(gauge, n, opt) return nil }, gauge) if err != nil { @@ -80,10 +80,10 @@ func main() { if err != nil { log.Fatal(err) } - histogram.Record(ctx, 23, attrs...) - histogram.Record(ctx, 7, attrs...) - histogram.Record(ctx, 101, attrs...) - histogram.Record(ctx, 105, attrs...) + histogram.Record(ctx, 23, opt) + histogram.Record(ctx, 7, opt) + histogram.Record(ctx, 101, opt) + histogram.Record(ctx, 105, opt) ctx, _ = signal.NotifyContext(ctx, os.Interrupt) <-ctx.Done() diff --git a/example/view/main.go b/example/view/main.go index b609e48737d..1fc6d9cd636 100644 --- a/example/view/main.go +++ b/example/view/main.go @@ -64,25 +64,25 @@ func main() { // Start the prometheus HTTP server and pass the exporter Collector to it go serveMetrics() - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) if err != nil { log.Fatal(err) } - counter.Add(ctx, 5, attrs...) + counter.Add(ctx, 5, opt) histogram, err := meter.Float64Histogram("custom_histogram", instrument.WithDescription("a histogram with custom buckets and rename")) if err != nil { log.Fatal(err) } - histogram.Record(ctx, 136, attrs...) - histogram.Record(ctx, 64, attrs...) - histogram.Record(ctx, 701, attrs...) - histogram.Record(ctx, 830, attrs...) + histogram.Record(ctx, 136, opt) + histogram.Record(ctx, 64, opt) + histogram.Record(ctx, 701, opt) + histogram.Record(ctx, 830, opt) ctx, _ = signal.NotifyContext(ctx, os.Interrupt) <-ctx.Done() diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index d80d8899c5d..868e575319b 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -46,67 +46,67 @@ func TestPrometheusExporter(t *testing.T) { name: "counter", expectedFile: "testdata/counter.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } + ) counter, err := meter.Float64Counter( "foo", instrument.WithDescription("a simple counter"), instrument.WithUnit("ms"), ) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) - attrs2 := []attribute.KeyValue{ + attrs2 := attribute.NewSet( attribute.Key("A").String("D"), attribute.Key("C").String("B"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } - counter.Add(ctx, 5, attrs2...) + ) + counter.Add(ctx, 5, instrument.WithAttributeSet(attrs2)) }, }, { name: "gauge", expectedFile: "testdata/gauge.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) gauge, err := meter.Float64UpDownCounter( "bar", instrument.WithDescription("a fun little gauge"), instrument.WithUnit("1"), ) require.NoError(t, err) - gauge.Add(ctx, 1.0, attrs...) - gauge.Add(ctx, -.25, attrs...) + gauge.Add(ctx, 1.0, opt) + gauge.Add(ctx, -.25, opt) }, }, { name: "histogram", expectedFile: "testdata/histogram.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) histogram, err := meter.Float64Histogram( "histogram_baz", instrument.WithDescription("a very nice histogram"), instrument.WithUnit("By"), ) require.NoError(t, err) - histogram.Record(ctx, 23, attrs...) - histogram.Record(ctx, 7, attrs...) - histogram.Record(ctx, 101, attrs...) - histogram.Record(ctx, 105, attrs...) + histogram.Record(ctx, 23, opt) + histogram.Record(ctx, 7, opt) + histogram.Record(ctx, 101, opt) + histogram.Record(ctx, 105, opt) }, }, { @@ -114,7 +114,7 @@ func TestPrometheusExporter(t *testing.T) { expectedFile: "testdata/sanitized_labels.txt", options: []Option{WithoutUnits()}, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( // exact match, value should be overwritten attribute.Key("A.B").String("X"), attribute.Key("A.B").String("Q"), @@ -122,7 +122,7 @@ func TestPrometheusExporter(t *testing.T) { // unintended match due to sanitization, values should be concatenated attribute.Key("C.D").String("Y"), attribute.Key("C/D").String("Z"), - } + ) counter, err := meter.Float64Counter( "foo", instrument.WithDescription("a sanitary counter"), @@ -130,37 +130,37 @@ func TestPrometheusExporter(t *testing.T) { instrument.WithUnit("By"), ) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) }, }, { name: "invalid instruments are renamed", expectedFile: "testdata/sanitized_names.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) // Valid. gauge, err := meter.Float64UpDownCounter("bar", instrument.WithDescription("a fun little gauge")) require.NoError(t, err) - gauge.Add(ctx, 100, attrs...) - gauge.Add(ctx, -25, attrs...) + gauge.Add(ctx, 100, opt) + gauge.Add(ctx, -25, opt) // Invalid, will be renamed. gauge, err = meter.Float64UpDownCounter("invalid.gauge.name", instrument.WithDescription("a gauge with an invalid name")) require.NoError(t, err) - gauge.Add(ctx, 100, attrs...) + gauge.Add(ctx, 100, opt) counter, err := meter.Float64Counter("0invalid.counter.name", instrument.WithDescription("a counter with an invalid name")) require.NoError(t, err) - counter.Add(ctx, 100, attrs...) + counter.Add(ctx, 100, opt) histogram, err := meter.Float64Histogram("invalid.hist.name", instrument.WithDescription("a histogram with an invalid name")) require.NoError(t, err) - histogram.Record(ctx, 23, attrs...) + histogram.Record(ctx, 23, opt) }, }, { @@ -168,17 +168,17 @@ func TestPrometheusExporter(t *testing.T) { emptyResource: true, expectedFile: "testdata/empty_resource.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } + ) counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) }, }, { @@ -189,17 +189,17 @@ func TestPrometheusExporter(t *testing.T) { }, expectedFile: "testdata/custom_resource.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } + ) counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) }, }, { @@ -207,17 +207,17 @@ func TestPrometheusExporter(t *testing.T) { options: []Option{WithoutTargetInfo()}, expectedFile: "testdata/without_target_info.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } + ) counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) }, }, { @@ -225,18 +225,18 @@ func TestPrometheusExporter(t *testing.T) { options: []Option{WithoutScopeInfo()}, expectedFile: "testdata/without_scope_info.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) gauge, err := meter.Int64UpDownCounter( "bar", instrument.WithDescription("a fun little gauge"), instrument.WithUnit("1"), ) require.NoError(t, err) - gauge.Add(ctx, 2, attrs...) - gauge.Add(ctx, -1, attrs...) + gauge.Add(ctx, 2, opt) + gauge.Add(ctx, -1, opt) }, }, { @@ -244,18 +244,18 @@ func TestPrometheusExporter(t *testing.T) { options: []Option{WithoutScopeInfo(), WithoutTargetInfo()}, expectedFile: "testdata/without_scope_and_target_info.txt", recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), - } + ) counter, err := meter.Int64Counter( "bar", instrument.WithDescription("a fun little counter"), instrument.WithUnit("By"), ) require.NoError(t, err) - counter.Add(ctx, 2, attrs...) - counter.Add(ctx, 1, attrs...) + counter.Add(ctx, 2, opt) + counter.Add(ctx, 1, opt) }, }, { @@ -265,17 +265,17 @@ func TestPrometheusExporter(t *testing.T) { WithNamespace("test"), }, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { - attrs := []attribute.KeyValue{ + opt := instrument.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), attribute.Key("E").Bool(true), attribute.Key("F").Int(42), - } + ) counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter")) require.NoError(t, err) - counter.Add(ctx, 5, attrs...) - counter.Add(ctx, 10.3, attrs...) - counter.Add(ctx, 9, attrs...) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) }, }, } @@ -388,7 +388,7 @@ func TestMultiScopes(t *testing.T) { instrument.WithUnit("ms"), instrument.WithDescription("meter foo counter")) assert.NoError(t, err) - fooCounter.Add(ctx, 100, attribute.String("type", "foo")) + fooCounter.Add(ctx, 100, instrument.WithAttributes(attribute.String("type", "foo"))) barCounter, err := provider.Meter("meterbar", otelmetric.WithInstrumentationVersion("v0.1.0")). Int64Counter( @@ -396,7 +396,7 @@ func TestMultiScopes(t *testing.T) { instrument.WithUnit("ms"), instrument.WithDescription("meter bar counter")) assert.NoError(t, err) - barCounter.Add(ctx, 200, attribute.String("type", "bar")) + barCounter.Add(ctx, 200, instrument.WithAttributes(attribute.String("type", "bar"))) file, err := os.Open("testdata/multi_scopes.txt") require.NoError(t, err) @@ -407,6 +407,12 @@ func TestMultiScopes(t *testing.T) { } func TestDuplicateMetrics(t *testing.T) { + ab := attribute.NewSet(attribute.String("A", "B")) + withAB := instrument.WithAttributeSet(ab) + typeBar := attribute.NewSet(attribute.String("type", "bar")) + withTypeBar := instrument.WithAttributeSet(typeBar) + typeFoo := attribute.NewSet(attribute.String("type", "foo")) + withTypeFoo := instrument.WithAttributeSet(typeFoo) testCases := []struct { name string customResouceAttrs []attribute.KeyValue @@ -421,13 +427,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter counter foo")) assert.NoError(t, err) - fooA.Add(ctx, 100, attribute.String("A", "B")) + fooA.Add(ctx, 100, withAB) fooB, err := meterB.Int64Counter("foo", instrument.WithUnit("By"), instrument.WithDescription("meter counter foo")) assert.NoError(t, err) - fooB.Add(ctx, 100, attribute.String("A", "B")) + fooB.Add(ctx, 100, withAB) }, possibleExpectedFiles: []string{"testdata/no_conflict_two_counters.txt"}, }, @@ -438,13 +444,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter gauge foo")) assert.NoError(t, err) - fooA.Add(ctx, 100, attribute.String("A", "B")) + fooA.Add(ctx, 100, withAB) fooB, err := meterB.Int64UpDownCounter("foo", instrument.WithUnit("By"), instrument.WithDescription("meter gauge foo")) assert.NoError(t, err) - fooB.Add(ctx, 100, attribute.String("A", "B")) + fooB.Add(ctx, 100, withAB) }, possibleExpectedFiles: []string{"testdata/no_conflict_two_updowncounters.txt"}, }, @@ -455,13 +461,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter histogram foo")) assert.NoError(t, err) - fooA.Record(ctx, 100, attribute.String("A", "B")) + fooA.Record(ctx, 100, withAB) fooB, err := meterB.Int64Histogram("foo", instrument.WithUnit("By"), instrument.WithDescription("meter histogram foo")) assert.NoError(t, err) - fooB.Record(ctx, 100, attribute.String("A", "B")) + fooB.Record(ctx, 100, withAB) }, possibleExpectedFiles: []string{"testdata/no_conflict_two_histograms.txt"}, }, @@ -472,13 +478,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter a bar")) assert.NoError(t, err) - barA.Add(ctx, 100, attribute.String("type", "bar")) + barA.Add(ctx, 100, withTypeBar) barB, err := meterB.Int64Counter("bar", instrument.WithUnit("By"), instrument.WithDescription("meter b bar")) assert.NoError(t, err) - barB.Add(ctx, 100, attribute.String("type", "bar")) + barB.Add(ctx, 100, withTypeBar) }, possibleExpectedFiles: []string{ "testdata/conflict_help_two_counters_1.txt", @@ -492,13 +498,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter a bar")) assert.NoError(t, err) - barA.Add(ctx, 100, attribute.String("type", "bar")) + barA.Add(ctx, 100, withTypeBar) barB, err := meterB.Int64UpDownCounter("bar", instrument.WithUnit("By"), instrument.WithDescription("meter b bar")) assert.NoError(t, err) - barB.Add(ctx, 100, attribute.String("type", "bar")) + barB.Add(ctx, 100, withTypeBar) }, possibleExpectedFiles: []string{ "testdata/conflict_help_two_updowncounters_1.txt", @@ -512,13 +518,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter a bar")) assert.NoError(t, err) - barA.Record(ctx, 100, attribute.String("A", "B")) + barA.Record(ctx, 100, withAB) barB, err := meterB.Int64Histogram("bar", instrument.WithUnit("By"), instrument.WithDescription("meter b bar")) assert.NoError(t, err) - barB.Record(ctx, 100, attribute.String("A", "B")) + barB.Record(ctx, 100, withAB) }, possibleExpectedFiles: []string{ "testdata/conflict_help_two_histograms_1.txt", @@ -532,13 +538,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter bar")) assert.NoError(t, err) - bazA.Add(ctx, 100, attribute.String("type", "bar")) + bazA.Add(ctx, 100, withTypeBar) bazB, err := meterB.Int64Counter("bar", instrument.WithUnit("ms"), instrument.WithDescription("meter bar")) assert.NoError(t, err) - bazB.Add(ctx, 100, attribute.String("type", "bar")) + bazB.Add(ctx, 100, withTypeBar) }, options: []Option{WithoutUnits()}, possibleExpectedFiles: []string{"testdata/conflict_unit_two_counters.txt"}, @@ -550,13 +556,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter gauge bar")) assert.NoError(t, err) - barA.Add(ctx, 100, attribute.String("type", "bar")) + barA.Add(ctx, 100, withTypeBar) barB, err := meterB.Int64UpDownCounter("bar", instrument.WithUnit("ms"), instrument.WithDescription("meter gauge bar")) assert.NoError(t, err) - barB.Add(ctx, 100, attribute.String("type", "bar")) + barB.Add(ctx, 100, withTypeBar) }, options: []Option{WithoutUnits()}, possibleExpectedFiles: []string{"testdata/conflict_unit_two_updowncounters.txt"}, @@ -568,13 +574,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter histogram bar")) assert.NoError(t, err) - barA.Record(ctx, 100, attribute.String("A", "B")) + barA.Record(ctx, 100, withAB) barB, err := meterB.Int64Histogram("bar", instrument.WithUnit("ms"), instrument.WithDescription("meter histogram bar")) assert.NoError(t, err) - barB.Record(ctx, 100, attribute.String("A", "B")) + barB.Record(ctx, 100, withAB) }, options: []Option{WithoutUnits()}, possibleExpectedFiles: []string{"testdata/conflict_unit_two_histograms.txt"}, @@ -586,13 +592,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter foo")) assert.NoError(t, err) - counter.Add(ctx, 100, attribute.String("type", "foo")) + counter.Add(ctx, 100, withTypeFoo) gauge, err := meterA.Int64UpDownCounter("foo_total", instrument.WithUnit("By"), instrument.WithDescription("meter foo")) assert.NoError(t, err) - gauge.Add(ctx, 200, attribute.String("type", "foo")) + gauge.Add(ctx, 200, withTypeFoo) }, options: []Option{WithoutUnits()}, possibleExpectedFiles: []string{ @@ -607,13 +613,13 @@ func TestDuplicateMetrics(t *testing.T) { instrument.WithUnit("By"), instrument.WithDescription("meter gauge foo")) assert.NoError(t, err) - fooA.Add(ctx, 100, attribute.String("A", "B")) + fooA.Add(ctx, 100, withAB) fooHistogramA, err := meterA.Int64Histogram("foo", instrument.WithUnit("By"), instrument.WithDescription("meter histogram foo")) assert.NoError(t, err) - fooHistogramA.Record(ctx, 100, attribute.String("A", "B")) + fooHistogramA.Record(ctx, 100, withAB) }, possibleExpectedFiles: []string{ "testdata/conflict_type_histogram_and_updowncounter_1.txt", diff --git a/metric/example_test.go b/metric/example_test.go index fbaefcd123f..1782e79097f 100644 --- a/metric/example_test.go +++ b/metric/example_test.go @@ -63,7 +63,7 @@ func ExampleMeter_asynchronous_single() { // // For demonstration purpose, a static value is used here. usage := 75000 - obsrv.Observe(int64(usage), attribute.Int("disk.id", 3)) + obsrv.Observe(int64(usage), instrument.WithAttributes(attribute.Int("disk.id", 3))) return nil }), ) diff --git a/metric/instrument/asyncfloat64.go b/metric/instrument/asyncfloat64.go index 356c969b6b1..5aaac43a7aa 100644 --- a/metric/instrument/asyncfloat64.go +++ b/metric/instrument/asyncfloat64.go @@ -17,7 +17,6 @@ package instrument // import "go.opentelemetry.io/otel/metric/instrument" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -207,8 +206,8 @@ type Float64ObservableGaugeOption interface { type Float64Observer interface { embedded.Float64Observer - // Observe records the float64 value with attributes. - Observe(value float64, attributes ...attribute.KeyValue) + // Observe records the float64 value. + Observe(value float64, opts ...ObserveOption) } // Float64Callback is a function registered with a Meter that makes diff --git a/metric/instrument/asyncfloat64_test.go b/metric/instrument/asyncfloat64_test.go index 5a23c2845f7..73be71d1dc9 100644 --- a/metric/instrument/asyncfloat64_test.go +++ b/metric/instrument/asyncfloat64_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -89,6 +88,6 @@ type float64Observer struct { got float64 } -func (o *float64Observer) Observe(v float64, _ ...attribute.KeyValue) { +func (o *float64Observer) Observe(v float64, _ ...ObserveOption) { o.got = v } diff --git a/metric/instrument/asyncint64.go b/metric/instrument/asyncint64.go index 05bfc8dbe0f..e9027b4b480 100644 --- a/metric/instrument/asyncint64.go +++ b/metric/instrument/asyncint64.go @@ -17,7 +17,6 @@ package instrument // import "go.opentelemetry.io/otel/metric/instrument" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -206,8 +205,8 @@ type Int64ObservableGaugeOption interface { type Int64Observer interface { embedded.Int64Observer - // Observe records the int64 value with attributes. - Observe(value int64, attributes ...attribute.KeyValue) + // Observe records the int64 value. + Observe(value int64, opts ...ObserveOption) } // Int64Callback is a function registered with a Meter that makes observations diff --git a/metric/instrument/asyncint64_test.go b/metric/instrument/asyncint64_test.go index b52f0730d5d..2b7bc91c003 100644 --- a/metric/instrument/asyncint64_test.go +++ b/metric/instrument/asyncint64_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -89,6 +88,6 @@ type int64Observer struct { got int64 } -func (o *int64Observer) Observe(v int64, _ ...attribute.KeyValue) { +func (o *int64Observer) Observe(v int64, _ ...ObserveOption) { o.got = v } diff --git a/metric/instrument/instrument.go b/metric/instrument/instrument.go index 3eaeb302767..cb0373c16e5 100644 --- a/metric/instrument/instrument.go +++ b/metric/instrument/instrument.go @@ -14,6 +14,8 @@ package instrument // import "go.opentelemetry.io/otel/metric/instrument" +import "go.opentelemetry.io/otel/attribute" + // Observable is used as a grouping mechanism for all instruments that are // updated within a Callback. type Observable interface { @@ -166,3 +168,165 @@ func (o unitOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64Ob // WithUnit sets the instrument unit. func WithUnit(u string) Option { return unitOpt(u) } + +// AddOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as a AddOption. +type AddOption interface { + applyAdd(AddConfig) AddConfig +} + +// AddConfig contains options for an addition measurement. +type AddConfig struct { + attrs attribute.Set +} + +// NewAddConfig returns a new [AddConfig] with all opts applied. +func NewAddConfig(opts []AddOption) AddConfig { + config := AddConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyAdd(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c AddConfig) Attributes() attribute.Set { + return c.attrs +} + +// RecordOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as a RecordOption. +type RecordOption interface { + applyRecord(RecordConfig) RecordConfig +} + +// RecordConfig contains options for a recorded measurement. +type RecordConfig struct { + attrs attribute.Set +} + +// NewRecordConfig returns a new [RecordConfig] with all opts applied. +func NewRecordConfig(opts []RecordOption) RecordConfig { + config := RecordConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyRecord(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c RecordConfig) Attributes() attribute.Set { + return c.attrs +} + +// ObserveOption applies options to an addition measurement. See +// [MeasurementOption] for other options that can be used as a ObserveOption. +type ObserveOption interface { + applyObserve(ObserveConfig) ObserveConfig +} + +// ObserveConfig contains options for an observed measurement. +type ObserveConfig struct { + attrs attribute.Set +} + +// NewObserveConfig returns a new [ObserveConfig] with all opts applied. +func NewObserveConfig(opts []ObserveOption) ObserveConfig { + config := ObserveConfig{attrs: *attribute.EmptySet()} + for _, o := range opts { + config = o.applyObserve(config) + } + return config +} + +// Attributes returns the configured attribute set. +func (c ObserveConfig) Attributes() attribute.Set { + return c.attrs +} + +// MeasurementOption applies options to all instrument measurement. +type MeasurementOption interface { + AddOption + RecordOption + ObserveOption +} + +type attrOpt struct { + set attribute.Set +} + +// mergeSets returns the union of keys between a and b. Any duplicate keys will +// use the value associated with b. +func mergeSets(a, b attribute.Set) attribute.Set { + // NewMergeIterator uses the first value for any duplicates. + iter := attribute.NewMergeIterator(&b, &a) + merged := make([]attribute.KeyValue, 0, a.Len()+b.Len()) + for iter.Next() { + merged = append(merged, iter.Attribute()) + } + return attribute.NewSet(merged...) +} + +func (o attrOpt) applyAdd(c AddConfig) AddConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +func (o attrOpt) applyRecord(c RecordConfig) RecordConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig { + switch { + case o.set.Len() == 0: + case c.attrs.Len() == 0: + c.attrs = o.set + default: + c.attrs = mergeSets(c.attrs, o.set) + } + return c +} + +// WithAttributeSet sets the attribute Set associated with a measurement is +// made with. +// +// If multiple WithAttributeSet or WithAttributes options are passed the +// attributes will be merged together in the order they are passed. Attributes +// with duplicate keys will use the last value passed. +func WithAttributeSet(attributes attribute.Set) MeasurementOption { + return attrOpt{set: attributes} +} + +// WithAttributes converts attributes into an attribute Set and sets the Set to +// be associated with a measurement. This is shorthand for: +// +// cp := make([]attribute.KeyValue, len(attributes)) +// copy(cp, attributes) +// WithAttributes(attribute.NewSet(cp...)) +// +// [attribute.NewSet] may modify the passed attributes so this will make a copy +// of attributes before creating a set in order to ensure this function is +// concurrent safe. This makes this option function less optimized in +// comparison to [WithAttributeSet]. Therefore, [WithAttributeSet] should be +// preferred for performance sensitive code. +// +// See [WithAttributeSet] for information about how multiple WithAttributes are +// merged. +func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption { + cp := make([]attribute.KeyValue, len(attributes)) + copy(cp, attributes) + return attrOpt{set: attribute.NewSet(cp...)} +} diff --git a/metric/instrument/instrument_test.go b/metric/instrument/instrument_test.go new file mode 100644 index 00000000000..23f2bdab215 --- /dev/null +++ b/metric/instrument/instrument_test.go @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package instrument // import "go.opentelemetry.io/otel/metric/instrument" + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/attribute" +) + +type attrConf interface { + Attributes() attribute.Set +} + +func TestConfigAttrs(t *testing.T) { + t.Run("AddConfig", testConfAttr(func(mo ...MeasurementOption) attrConf { + opts := make([]AddOption, len(mo)) + for i := range mo { + opts[i] = mo[i].(AddOption) + } + return NewAddConfig(opts) + })) + + t.Run("RecordConfig", testConfAttr(func(mo ...MeasurementOption) attrConf { + opts := make([]RecordOption, len(mo)) + for i := range mo { + opts[i] = mo[i].(RecordOption) + } + return NewRecordConfig(opts) + })) + + t.Run("ObserveConfig", testConfAttr(func(mo ...MeasurementOption) attrConf { + opts := make([]ObserveOption, len(mo)) + for i := range mo { + opts[i] = mo[i].(ObserveOption) + } + return NewObserveConfig(opts) + })) +} + +func testConfAttr(newConf func(...MeasurementOption) attrConf) func(t *testing.T) { + return func(t *testing.T) { + t.Run("ZeroConfigEmpty", func(t *testing.T) { + c := newConf() + assert.Equal(t, *attribute.EmptySet(), c.Attributes()) + }) + + t.Run("EmptySet", func(t *testing.T) { + c := newConf(WithAttributeSet(*attribute.EmptySet())) + assert.Equal(t, *attribute.EmptySet(), c.Attributes()) + }) + + aliceAttr := attribute.String("user", "Alice") + alice := attribute.NewSet(aliceAttr) + t.Run("SingleWithAttributeSet", func(t *testing.T) { + c := newConf(WithAttributeSet(alice)) + assert.Equal(t, alice, c.Attributes()) + }) + + t.Run("SingleWithAttributes", func(t *testing.T) { + c := newConf(WithAttributes(aliceAttr)) + assert.Equal(t, alice, c.Attributes()) + }) + + bobAttr := attribute.String("user", "Bob") + bob := attribute.NewSet(bobAttr) + t.Run("MultiWithAttributeSet", func(t *testing.T) { + c := newConf(WithAttributeSet(alice), WithAttributeSet(bob)) + assert.Equal(t, bob, c.Attributes()) + }) + + t.Run("MergedWithAttributes", func(t *testing.T) { + c := newConf(WithAttributes(aliceAttr, bobAttr)) + assert.Equal(t, bob, c.Attributes()) + }) + + t.Run("MultiWithAttributeSet", func(t *testing.T) { + c := newConf(WithAttributes(aliceAttr), WithAttributes(bobAttr)) + assert.Equal(t, bob, c.Attributes()) + }) + + t.Run("MergedEmpty", func(t *testing.T) { + c := newConf(WithAttributeSet(alice), WithAttributeSet(*attribute.EmptySet())) + assert.Equal(t, alice, c.Attributes()) + }) + } +} + +func TestWithAttributesRace(t *testing.T) { + attrs := []attribute.KeyValue{ + attribute.String("user", "Alice"), + attribute.Bool("admin", true), + attribute.String("user", "Bob"), + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + opt := []AddOption{WithAttributes(attrs...)} + _ = NewAddConfig(opt) + }() + wg.Add(1) + go func() { + defer wg.Done() + opt := []AddOption{WithAttributes(attrs...)} + _ = NewAddConfig(opt) + }() + + wg.Add(1) + go func() { + defer wg.Done() + opt := []RecordOption{WithAttributes(attrs...)} + _ = NewRecordConfig(opt) + }() + wg.Add(1) + go func() { + defer wg.Done() + opt := []RecordOption{WithAttributes(attrs...)} + _ = NewRecordConfig(opt) + }() + + wg.Add(1) + go func() { + defer wg.Done() + opt := []ObserveOption{WithAttributes(attrs...)} + _ = NewObserveConfig(opt) + }() + wg.Add(1) + go func() { + defer wg.Done() + opt := []ObserveOption{WithAttributes(attrs...)} + _ = NewObserveConfig(opt) + }() + + wg.Wait() +} diff --git a/metric/instrument/syncfloat64.go b/metric/instrument/syncfloat64.go index 03b3bf6ac2d..2399f83637e 100644 --- a/metric/instrument/syncfloat64.go +++ b/metric/instrument/syncfloat64.go @@ -17,7 +17,6 @@ package instrument // import "go.opentelemetry.io/otel/metric/instrument" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -31,7 +30,7 @@ type Float64Counter interface { embedded.Float64Counter // Add records a change to the counter. - Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + Add(ctx context.Context, incr float64, opts ...AddOption) } // Float64CounterConfig contains options for synchronous counter instruments that @@ -78,7 +77,7 @@ type Float64UpDownCounter interface { embedded.Float64UpDownCounter // Add records a change to the counter. - Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + Add(ctx context.Context, incr float64, opts ...AddOption) } // Float64UpDownCounterConfig contains options for synchronous counter @@ -126,7 +125,7 @@ type Float64Histogram interface { embedded.Float64Histogram // Record adds an additional value to the distribution. - Record(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + Record(ctx context.Context, incr float64, opts ...RecordOption) } // Float64HistogramConfig contains options for synchronous counter instruments diff --git a/metric/instrument/syncint64.go b/metric/instrument/syncint64.go index 22d0718c87c..53d1c5b6c0a 100644 --- a/metric/instrument/syncint64.go +++ b/metric/instrument/syncint64.go @@ -17,7 +17,6 @@ package instrument // import "go.opentelemetry.io/otel/metric/instrument" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" ) @@ -31,7 +30,7 @@ type Int64Counter interface { embedded.Int64Counter // Add records a change to the counter. - Add(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + Add(ctx context.Context, incr int64, opts ...AddOption) } // Int64CounterConfig contains options for synchronous counter instruments that @@ -78,7 +77,7 @@ type Int64UpDownCounter interface { embedded.Int64UpDownCounter // Add records a change to the counter. - Add(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + Add(ctx context.Context, incr int64, opts ...AddOption) } // Int64UpDownCounterConfig contains options for synchronous counter @@ -126,7 +125,7 @@ type Int64Histogram interface { embedded.Int64Histogram // Record adds an additional value to the distribution. - Record(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + Record(ctx context.Context, incr int64, opts ...RecordOption) } // Int64HistogramConfig contains options for synchronous counter instruments diff --git a/metric/internal/global/instruments.go b/metric/internal/global/instruments.go index a825b4acadf..79dd446630a 100644 --- a/metric/internal/global/instruments.go +++ b/metric/internal/global/instruments.go @@ -19,7 +19,6 @@ import ( "sync/atomic" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/instrument" @@ -225,9 +224,9 @@ func (i *sfCounter) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *sfCounter) Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) { +func (i *sfCounter) Add(ctx context.Context, incr float64, opts ...instrument.AddOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Float64Counter).Add(ctx, incr, attrs...) + ctr.(instrument.Float64Counter).Add(ctx, incr, opts...) } } @@ -251,9 +250,9 @@ func (i *sfUpDownCounter) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *sfUpDownCounter) Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) { +func (i *sfUpDownCounter) Add(ctx context.Context, incr float64, opts ...instrument.AddOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Float64UpDownCounter).Add(ctx, incr, attrs...) + ctr.(instrument.Float64UpDownCounter).Add(ctx, incr, opts...) } } @@ -277,9 +276,9 @@ func (i *sfHistogram) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *sfHistogram) Record(ctx context.Context, x float64, attrs ...attribute.KeyValue) { +func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...instrument.RecordOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Float64Histogram).Record(ctx, x, attrs...) + ctr.(instrument.Float64Histogram).Record(ctx, x, opts...) } } @@ -303,9 +302,9 @@ func (i *siCounter) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *siCounter) Add(ctx context.Context, x int64, attrs ...attribute.KeyValue) { +func (i *siCounter) Add(ctx context.Context, x int64, opts ...instrument.AddOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Int64Counter).Add(ctx, x, attrs...) + ctr.(instrument.Int64Counter).Add(ctx, x, opts...) } } @@ -329,9 +328,9 @@ func (i *siUpDownCounter) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *siUpDownCounter) Add(ctx context.Context, x int64, attrs ...attribute.KeyValue) { +func (i *siUpDownCounter) Add(ctx context.Context, x int64, opts ...instrument.AddOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Int64UpDownCounter).Add(ctx, x, attrs...) + ctr.(instrument.Int64UpDownCounter).Add(ctx, x, opts...) } } @@ -355,8 +354,8 @@ func (i *siHistogram) setDelegate(m metric.Meter) { i.delegate.Store(ctr) } -func (i *siHistogram) Record(ctx context.Context, x int64, attrs ...attribute.KeyValue) { +func (i *siHistogram) Record(ctx context.Context, x int64, opts ...instrument.RecordOption) { if ctr := i.delegate.Load(); ctr != nil { - ctr.(instrument.Int64Histogram).Record(ctx, x, attrs...) + ctr.(instrument.Int64Histogram).Record(ctx, x, opts...) } } diff --git a/metric/internal/global/instruments_test.go b/metric/internal/global/instruments_test.go index 6b5af6715ea..ce78cfb1252 100644 --- a/metric/internal/global/instruments_test.go +++ b/metric/internal/global/instruments_test.go @@ -18,18 +18,17 @@ import ( "context" "testing" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/noop" ) -func testFloat64Race(interact func(context.Context, float64, ...attribute.KeyValue), setDelegate func(metric.Meter)) { +func testFloat64Race(interact func(float64), setDelegate func(metric.Meter)) { finish := make(chan struct{}) go func() { for { - interact(context.Background(), 1) + interact(1) select { case <-finish: return @@ -42,11 +41,11 @@ func testFloat64Race(interact func(context.Context, float64, ...attribute.KeyVal close(finish) } -func testInt64Race(interact func(context.Context, int64, ...attribute.KeyValue), setDelegate func(metric.Meter)) { +func testInt64Race(interact func(int64), setDelegate func(metric.Meter)) { finish := make(chan struct{}) go func() { for { - interact(context.Background(), 1) + interact(1) select { case <-finish: return @@ -64,19 +63,19 @@ func TestAsyncInstrumentSetDelegateRace(t *testing.T) { t.Run("Float64", func(t *testing.T) { t.Run("Counter", func(t *testing.T) { delegate := &afCounter{} - f := func(context.Context, float64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(float64) { _ = delegate.Unwrap() } testFloat64Race(f, delegate.setDelegate) }) t.Run("UpDownCounter", func(t *testing.T) { delegate := &afUpDownCounter{} - f := func(context.Context, float64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(float64) { _ = delegate.Unwrap() } testFloat64Race(f, delegate.setDelegate) }) t.Run("Gauge", func(t *testing.T) { delegate := &afGauge{} - f := func(context.Context, float64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(float64) { _ = delegate.Unwrap() } testFloat64Race(f, delegate.setDelegate) }) }) @@ -86,19 +85,19 @@ func TestAsyncInstrumentSetDelegateRace(t *testing.T) { t.Run("Int64", func(t *testing.T) { t.Run("Counter", func(t *testing.T) { delegate := &aiCounter{} - f := func(context.Context, int64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(int64) { _ = delegate.Unwrap() } testInt64Race(f, delegate.setDelegate) }) t.Run("UpDownCounter", func(t *testing.T) { delegate := &aiUpDownCounter{} - f := func(context.Context, int64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(int64) { _ = delegate.Unwrap() } testInt64Race(f, delegate.setDelegate) }) t.Run("Gauge", func(t *testing.T) { delegate := &aiGauge{} - f := func(context.Context, int64, ...attribute.KeyValue) { _ = delegate.Unwrap() } + f := func(int64) { _ = delegate.Unwrap() } testInt64Race(f, delegate.setDelegate) }) }) @@ -109,17 +108,20 @@ func TestSyncInstrumentSetDelegateRace(t *testing.T) { t.Run("Float64", func(t *testing.T) { t.Run("Counter", func(t *testing.T) { delegate := &sfCounter{} - testFloat64Race(delegate.Add, delegate.setDelegate) + f := func(v float64) { delegate.Add(context.Background(), v) } + testFloat64Race(f, delegate.setDelegate) }) t.Run("UpDownCounter", func(t *testing.T) { delegate := &sfUpDownCounter{} - testFloat64Race(delegate.Add, delegate.setDelegate) + f := func(v float64) { delegate.Add(context.Background(), v) } + testFloat64Race(f, delegate.setDelegate) }) t.Run("Histogram", func(t *testing.T) { delegate := &sfHistogram{} - testFloat64Race(delegate.Record, delegate.setDelegate) + f := func(v float64) { delegate.Record(context.Background(), v) } + testFloat64Race(f, delegate.setDelegate) }) }) @@ -128,17 +130,20 @@ func TestSyncInstrumentSetDelegateRace(t *testing.T) { t.Run("Int64", func(t *testing.T) { t.Run("Counter", func(t *testing.T) { delegate := &siCounter{} - testInt64Race(delegate.Add, delegate.setDelegate) + f := func(v int64) { delegate.Add(context.Background(), v) } + testInt64Race(f, delegate.setDelegate) }) t.Run("UpDownCounter", func(t *testing.T) { delegate := &siUpDownCounter{} - testInt64Race(delegate.Add, delegate.setDelegate) + f := func(v int64) { delegate.Add(context.Background(), v) } + testInt64Race(f, delegate.setDelegate) }) t.Run("Histogram", func(t *testing.T) { delegate := &siHistogram{} - testInt64Race(delegate.Record, delegate.setDelegate) + f := func(v int64) { delegate.Record(context.Background(), v) } + testInt64Race(f, delegate.setDelegate) }) }) } @@ -158,10 +163,10 @@ type testCountingFloatInstrument struct { func (i *testCountingFloatInstrument) observe() { i.count++ } -func (i *testCountingFloatInstrument) Add(context.Context, float64, ...attribute.KeyValue) { +func (i *testCountingFloatInstrument) Add(context.Context, float64, ...instrument.AddOption) { i.count++ } -func (i *testCountingFloatInstrument) Record(context.Context, float64, ...attribute.KeyValue) { +func (i *testCountingFloatInstrument) Record(context.Context, float64, ...instrument.RecordOption) { i.count++ } @@ -180,9 +185,9 @@ type testCountingIntInstrument struct { func (i *testCountingIntInstrument) observe() { i.count++ } -func (i *testCountingIntInstrument) Add(context.Context, int64, ...attribute.KeyValue) { +func (i *testCountingIntInstrument) Add(context.Context, int64, ...instrument.AddOption) { i.count++ } -func (i *testCountingIntInstrument) Record(context.Context, int64, ...attribute.KeyValue) { +func (i *testCountingIntInstrument) Record(context.Context, int64, ...instrument.RecordOption) { i.count++ } diff --git a/metric/internal/global/meter_types_test.go b/metric/internal/global/meter_types_test.go index 76827e72eb1..45de7ca4dbb 100644 --- a/metric/internal/global/meter_types_test.go +++ b/metric/internal/global/meter_types_test.go @@ -17,7 +17,6 @@ package global // import "go.opentelemetry.io/otel/metric/internal/global" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/instrument" @@ -157,14 +156,14 @@ type observationRecorder struct { ctx context.Context } -func (o observationRecorder) ObserveFloat64(i instrument.Float64Observable, value float64, attr ...attribute.KeyValue) { +func (o observationRecorder) ObserveFloat64(i instrument.Float64Observable, value float64, _ ...instrument.ObserveOption) { iImpl, ok := i.(*testCountingFloatInstrument) if ok { iImpl.observe() } } -func (o observationRecorder) ObserveInt64(i instrument.Int64Observable, value int64, attr ...attribute.KeyValue) { +func (o observationRecorder) ObserveInt64(i instrument.Int64Observable, value int64, _ ...instrument.ObserveOption) { iImpl, ok := i.(*testCountingIntInstrument) if ok { iImpl.observe() diff --git a/metric/meter.go b/metric/meter.go index f09feca83aa..0f77cde8d7e 100644 --- a/metric/meter.go +++ b/metric/meter.go @@ -17,7 +17,6 @@ package metric // import "go.opentelemetry.io/otel/metric" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/instrument" ) @@ -141,10 +140,10 @@ type Callback func(context.Context, Observer) error type Observer interface { embedded.Observer - // ObserveFloat64 records the float64 value with attributes for obsrv. - ObserveFloat64(obsrv instrument.Float64Observable, value float64, attributes ...attribute.KeyValue) - // ObserveInt64 records the int64 value with attributes for obsrv. - ObserveInt64(obsrv instrument.Int64Observable, value int64, attributes ...attribute.KeyValue) + // ObserveFloat64 records the float64 value for obsrv. + ObserveFloat64(obsrv instrument.Float64Observable, value float64, opts ...instrument.ObserveOption) + // ObserveInt64 records the int64 value for obsrv. + ObserveInt64(obsrv instrument.Int64Observable, value int64, opts ...instrument.ObserveOption) } // Registration is an token representing the unique registration of a callback diff --git a/metric/noop/noop.go b/metric/noop/noop.go index b4eaf4d6406..683f3e1c84c 100644 --- a/metric/noop/noop.go +++ b/metric/noop/noop.go @@ -26,7 +26,6 @@ package noop // import "go.opentelemetry.io/otel/metric/noop" import ( "context" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/instrument" @@ -153,11 +152,11 @@ func (Meter) RegisterCallback(metric.Callback, ...instrument.Observable) (metric type Observer struct{ embedded.Observer } // ObserveFloat64 performs no operation. -func (Observer) ObserveFloat64(instrument.Float64Observable, float64, ...attribute.KeyValue) { +func (Observer) ObserveFloat64(instrument.Float64Observable, float64, ...instrument.ObserveOption) { } // ObserveInt64 performs no operation. -func (Observer) ObserveInt64(instrument.Int64Observable, int64, ...attribute.KeyValue) { +func (Observer) ObserveInt64(instrument.Int64Observable, int64, ...instrument.ObserveOption) { } // Registration is the registration of a Callback with a No-Op Meter. @@ -173,42 +172,42 @@ func (Registration) Unregister() error { return nil } type Int64Counter struct{ embedded.Int64Counter } // Add performs no operation. -func (Int64Counter) Add(context.Context, int64, ...attribute.KeyValue) {} +func (Int64Counter) Add(context.Context, int64, ...instrument.AddOption) {} // Float64Counter is an OpenTelemetry Counter used to record float64 // measurements. It produces no telemetry. type Float64Counter struct{ embedded.Float64Counter } // Add performs no operation. -func (Float64Counter) Add(context.Context, float64, ...attribute.KeyValue) {} +func (Float64Counter) Add(context.Context, float64, ...instrument.AddOption) {} // Int64UpDownCounter is an OpenTelemetry UpDownCounter used to record int64 // measurements. It produces no telemetry. type Int64UpDownCounter struct{ embedded.Int64UpDownCounter } // Add performs no operation. -func (Int64UpDownCounter) Add(context.Context, int64, ...attribute.KeyValue) {} +func (Int64UpDownCounter) Add(context.Context, int64, ...instrument.AddOption) {} // Float64UpDownCounter is an OpenTelemetry UpDownCounter used to record // float64 measurements. It produces no telemetry. type Float64UpDownCounter struct{ embedded.Float64UpDownCounter } // Add performs no operation. -func (Float64UpDownCounter) Add(context.Context, float64, ...attribute.KeyValue) {} +func (Float64UpDownCounter) Add(context.Context, float64, ...instrument.AddOption) {} // Int64Histogram is an OpenTelemetry Histogram used to record int64 // measurements. It produces no telemetry. type Int64Histogram struct{ embedded.Int64Histogram } // Record performs no operation. -func (Int64Histogram) Record(context.Context, int64, ...attribute.KeyValue) {} +func (Int64Histogram) Record(context.Context, int64, ...instrument.RecordOption) {} // Float64Histogram is an OpenTelemetry Histogram used to record float64 // measurements. It produces no telemetry. type Float64Histogram struct{ embedded.Float64Histogram } // Record performs no operation. -func (Float64Histogram) Record(context.Context, float64, ...attribute.KeyValue) {} +func (Float64Histogram) Record(context.Context, float64, ...instrument.RecordOption) {} // Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record // int64 measurements. It produces no telemetry. @@ -256,11 +255,11 @@ type Float64ObservableUpDownCounter struct { type Int64Observer struct{ embedded.Int64Observer } // Observe performs no operation. -func (Int64Observer) Observe(int64, ...attribute.KeyValue) {} +func (Int64Observer) Observe(int64, ...instrument.ObserveOption) {} // Float64Observer is a recorder of float64 measurements that performs no // operation. type Float64Observer struct{ embedded.Float64Observer } // Observe performs no operation. -func (Float64Observer) Observe(float64, ...attribute.KeyValue) {} +func (Float64Observer) Observe(float64, ...instrument.ObserveOption) {} diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go index e1dbba593b6..8ac57a67e3c 100644 --- a/sdk/metric/benchmark_test.go +++ b/sdk/metric/benchmark_test.go @@ -66,59 +66,65 @@ func benchSyncViews(views ...View) func(*testing.B) { iCtr, err := meter.Int64Counter("int64-counter") assert.NoError(b, err) b.Run("Int64Counter", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { iCtr.Add(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.AddOption{instrument.WithAttributeSet(s)} + return func() { iCtr.Add(ctx, 1, o...) } } }())) fCtr, err := meter.Float64Counter("float64-counter") assert.NoError(b, err) b.Run("Float64Counter", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { fCtr.Add(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.AddOption{instrument.WithAttributeSet(s)} + return func() { fCtr.Add(ctx, 1, o...) } } }())) iUDCtr, err := meter.Int64UpDownCounter("int64-up-down-counter") assert.NoError(b, err) b.Run("Int64UpDownCounter", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { iUDCtr.Add(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.AddOption{instrument.WithAttributeSet(s)} + return func() { iUDCtr.Add(ctx, 1, o...) } } }())) fUDCtr, err := meter.Float64UpDownCounter("float64-up-down-counter") assert.NoError(b, err) b.Run("Float64UpDownCounter", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { fUDCtr.Add(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.AddOption{instrument.WithAttributeSet(s)} + return func() { fUDCtr.Add(ctx, 1, o...) } } }())) iHist, err := meter.Int64Histogram("int64-histogram") assert.NoError(b, err) b.Run("Int64Histogram", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { iHist.Record(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.RecordOption{instrument.WithAttributeSet(s)} + return func() { iHist.Record(ctx, 1, o...) } } }())) fHist, err := meter.Float64Histogram("float64-histogram") assert.NoError(b, err) b.Run("Float64Histogram", benchMeasAttrs(func() measF { - return func(attr []attribute.KeyValue) func() { - return func() { fHist.Record(ctx, 1, attr...) } + return func(s attribute.Set) func() { + o := []instrument.RecordOption{instrument.WithAttributeSet(s)} + return func() { fHist.Record(ctx, 1, o...) } } }())) } } -type measF func(attr []attribute.KeyValue) func() +type measF func(s attribute.Set) func() func benchMeasAttrs(meas measF) func(*testing.B) { return func(b *testing.B) { b.Run("Attributes/0", func(b *testing.B) { - f := meas(nil) + f := meas(*attribute.EmptySet()) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -126,8 +132,7 @@ func benchMeasAttrs(meas measF) func(*testing.B) { } }) b.Run("Attributes/1", func(b *testing.B) { - attrs := []attribute.KeyValue{attribute.Bool("K", true)} - f := meas(attrs) + f := meas(attribute.NewSet(attribute.Bool("K", true))) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -141,7 +146,7 @@ func benchMeasAttrs(meas measF) func(*testing.B) { for i := 2; i < n; i++ { attrs = append(attrs, attribute.Int(strconv.Itoa(i), i)) } - f := meas(attrs) + f := meas(attribute.NewSet(attrs...)) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -165,163 +170,163 @@ func benchCollectViews(views ...View) func(*testing.B) { } ctx := context.Background() return func(b *testing.B) { - b.Run("Int64Counter/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64Counter/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64Counter") i, err := m.Int64Counter("int64-counter") assert.NoError(b, err) - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Int64Counter/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64Counter/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64Counter") i, err := m.Int64Counter("int64-counter") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Float64Counter/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64Counter/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64Counter") i, err := m.Float64Counter("float64-counter") assert.NoError(b, err) - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Float64Counter/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64Counter/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64Counter") i, err := m.Float64Counter("float64-counter") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Int64UpDownCounter/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64UpDownCounter/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64UpDownCounter") i, err := m.Int64UpDownCounter("int64-up-down-counter") assert.NoError(b, err) - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Int64UpDownCounter/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64UpDownCounter/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64UpDownCounter") i, err := m.Int64UpDownCounter("int64-up-down-counter") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Float64UpDownCounter/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64UpDownCounter/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64UpDownCounter") i, err := m.Float64UpDownCounter("float64-up-down-counter") assert.NoError(b, err) - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Float64UpDownCounter/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64UpDownCounter/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64UpDownCounter") i, err := m.Float64UpDownCounter("float64-up-down-counter") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Add(ctx, 1, attr...) + i.Add(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Int64Histogram/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64Histogram/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64Histogram") i, err := m.Int64Histogram("int64-histogram") assert.NoError(b, err) - i.Record(ctx, 1, attr...) + i.Record(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Int64Histogram/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64Histogram/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64Histogram") i, err := m.Int64Histogram("int64-histogram") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Record(ctx, 1, attr...) + i.Record(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Float64Histogram/1", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64Histogram/1", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64Histogram") i, err := m.Float64Histogram("float64-histogram") assert.NoError(b, err) - i.Record(ctx, 1, attr...) + i.Record(ctx, 1, instrument.WithAttributeSet(s)) return r })) - b.Run("Float64Histogram/10", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64Histogram/10", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64Histogram") i, err := m.Float64Histogram("float64-histogram") assert.NoError(b, err) for n := 0; n < 10; n++ { - i.Record(ctx, 1, attr...) + i.Record(ctx, 1, instrument.WithAttributeSet(s)) } return r })) - b.Run("Int64ObservableCounter", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64ObservableCounter", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64ObservableCounter") _, err := m.Int64ObservableCounter( "int64-observable-counter", - instrument.WithInt64Callback(int64Cback(attr)), + instrument.WithInt64Callback(int64Cback(s)), ) assert.NoError(b, err) return r })) - b.Run("Float64ObservableCounter", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64ObservableCounter", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64ObservableCounter") _, err := m.Float64ObservableCounter( "float64-observable-counter", - instrument.WithFloat64Callback(float64Cback(attr)), + instrument.WithFloat64Callback(float64Cback(s)), ) assert.NoError(b, err) return r })) - b.Run("Int64ObservableUpDownCounter", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64ObservableUpDownCounter", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64ObservableUpDownCounter") _, err := m.Int64ObservableUpDownCounter( "int64-observable-up-down-counter", - instrument.WithInt64Callback(int64Cback(attr)), + instrument.WithInt64Callback(int64Cback(s)), ) assert.NoError(b, err) return r })) - b.Run("Float64ObservableUpDownCounter", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64ObservableUpDownCounter", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64ObservableUpDownCounter") _, err := m.Float64ObservableUpDownCounter( "float64-observable-up-down-counter", - instrument.WithFloat64Callback(float64Cback(attr)), + instrument.WithFloat64Callback(float64Cback(s)), ) assert.NoError(b, err) return r })) - b.Run("Int64ObservableGauge", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Int64ObservableGauge", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Int64ObservableGauge") _, err := m.Int64ObservableGauge( "int64-observable-gauge", - instrument.WithInt64Callback(int64Cback(attr)), + instrument.WithInt64Callback(int64Cback(s)), ) assert.NoError(b, err) return r })) - b.Run("Float64ObservableGauge", benchCollectAttrs(func(attr []attribute.KeyValue) Reader { + b.Run("Float64ObservableGauge", benchCollectAttrs(func(s attribute.Set) Reader { m, r := setup("benchCollectViews/Float64ObservableGauge") _, err := m.Float64ObservableGauge( "float64-observable-gauge", - instrument.WithFloat64Callback(float64Cback(attr)), + instrument.WithFloat64Callback(float64Cback(s)), ) assert.NoError(b, err) return r @@ -329,21 +334,23 @@ func benchCollectViews(views ...View) func(*testing.B) { } } -func int64Cback(attr []attribute.KeyValue) instrument.Int64Callback { +func int64Cback(s attribute.Set) instrument.Int64Callback { + opt := []instrument.ObserveOption{instrument.WithAttributeSet(s)} return func(_ context.Context, o instrument.Int64Observer) error { - o.Observe(1, attr...) + o.Observe(1, opt...) return nil } } -func float64Cback(attr []attribute.KeyValue) instrument.Float64Callback { +func float64Cback(s attribute.Set) instrument.Float64Callback { + opt := []instrument.ObserveOption{instrument.WithAttributeSet(s)} return func(_ context.Context, o instrument.Float64Observer) error { - o.Observe(1, attr...) + o.Observe(1, opt...) return nil } } -func benchCollectAttrs(setup func([]attribute.KeyValue) Reader) func(*testing.B) { +func benchCollectAttrs(setup func(attribute.Set) Reader) func(*testing.B) { ctx := context.Background() out := new(metricdata.ResourceMetrics) run := func(reader Reader) func(b *testing.B) { @@ -355,14 +362,14 @@ func benchCollectAttrs(setup func([]attribute.KeyValue) Reader) func(*testing.B) } } return func(b *testing.B) { - b.Run("Attributes/0", run(setup(nil))) + b.Run("Attributes/0", run(setup(*attribute.EmptySet()))) attrs := []attribute.KeyValue{attribute.Bool("K", true)} - b.Run("Attributes/1", run(setup(attrs))) + b.Run("Attributes/1", run(setup(attribute.NewSet(attrs...)))) for i := 2; i < 10; i++ { attrs = append(attrs, attribute.Int(strconv.Itoa(i), i)) } - b.Run("Attributes/10", run(setup(attrs))) + b.Run("Attributes/10", run(setup(attribute.NewSet(attrs...)))) } } diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index e75e373d46c..b4eb05a1606 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -170,40 +170,63 @@ type streamID struct { Number string } -type instrumentImpl[N int64 | float64] struct { - aggregators []internal.Aggregator[N] +type int64Inst struct { + aggregators []internal.Aggregator[int64] - embedded.Float64Counter - embedded.Float64UpDownCounter - embedded.Float64Histogram embedded.Int64Counter embedded.Int64UpDownCounter embedded.Int64Histogram } -var _ instrument.Float64Counter = (*instrumentImpl[float64])(nil) -var _ instrument.Float64UpDownCounter = (*instrumentImpl[float64])(nil) -var _ instrument.Float64Histogram = (*instrumentImpl[float64])(nil) -var _ instrument.Int64Counter = (*instrumentImpl[int64])(nil) -var _ instrument.Int64UpDownCounter = (*instrumentImpl[int64])(nil) -var _ instrument.Int64Histogram = (*instrumentImpl[int64])(nil) +var _ instrument.Int64Counter = (*int64Inst)(nil) +var _ instrument.Int64UpDownCounter = (*int64Inst)(nil) +var _ instrument.Int64Histogram = (*int64Inst)(nil) + +func (i *int64Inst) Add(ctx context.Context, val int64, opts ...instrument.AddOption) { + c := instrument.NewAddConfig(opts) + i.aggregate(ctx, val, c.Attributes()) +} + +func (i *int64Inst) Record(ctx context.Context, val int64, opts ...instrument.RecordOption) { + c := instrument.NewRecordConfig(opts) + i.aggregate(ctx, val, c.Attributes()) +} + +func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { + if err := ctx.Err(); err != nil { + return + } + for _, agg := range i.aggregators { + agg.Aggregate(val, s) + } +} + +type float64Inst struct { + aggregators []internal.Aggregator[float64] + + embedded.Float64Counter + embedded.Float64UpDownCounter + embedded.Float64Histogram +} + +var _ instrument.Float64Counter = (*float64Inst)(nil) +var _ instrument.Float64UpDownCounter = (*float64Inst)(nil) +var _ instrument.Float64Histogram = (*float64Inst)(nil) -func (i *instrumentImpl[N]) Add(ctx context.Context, val N, attrs ...attribute.KeyValue) { - i.aggregate(ctx, val, attrs) +func (i *float64Inst) Add(ctx context.Context, val float64, opts ...instrument.AddOption) { + c := instrument.NewAddConfig(opts) + i.aggregate(ctx, val, c.Attributes()) } -func (i *instrumentImpl[N]) Record(ctx context.Context, val N, attrs ...attribute.KeyValue) { - i.aggregate(ctx, val, attrs) +func (i *float64Inst) Record(ctx context.Context, val float64, opts ...instrument.RecordOption) { + c := instrument.NewRecordConfig(opts) + i.aggregate(ctx, val, c.Attributes()) } -func (i *instrumentImpl[N]) aggregate(ctx context.Context, val N, attrs []attribute.KeyValue) { +func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) { if err := ctx.Err(); err != nil { return } - // Do not use single attribute.Sortable and attribute.NewSetWithSortable, - // this method needs to be concurrent safe. Let the sync.Pool in the - // attribute package handle allocations of the Sortable. - s := attribute.NewSet(attrs...) for _, agg := range i.aggregators { agg.Aggregate(val, s) } @@ -277,11 +300,7 @@ func newObservable[N int64 | float64](scope instrumentation.Scope, kind Instrume } // observe records the val for the set of attrs. -func (o *observable[N]) observe(val N, attrs []attribute.KeyValue) { - // Do not use single attribute.Sortable and attribute.NewSetWithSortable, - // this method needs to be concurrent safe. Let the sync.Pool in the - // attribute package handle allocations of the Sortable. - s := attribute.NewSet(attrs...) +func (o *observable[N]) observe(val N, s attribute.Set) { for _, agg := range o.aggregators { agg.Aggregate(val, s) } diff --git a/sdk/metric/instrument_test.go b/sdk/metric/instrument_test.go index 4315effbfd0..0a8f5924473 100644 --- a/sdk/metric/instrument_test.go +++ b/sdk/metric/instrument_test.go @@ -23,16 +23,16 @@ import ( ) func BenchmarkInstrument(b *testing.B) { - attr := func(id int) []attribute.KeyValue { - return []attribute.KeyValue{ + attr := func(id int) attribute.Set { + return attribute.NewSet( attribute.String("user", "Alice"), attribute.Bool("admin", true), attribute.Int("id", id), - } + ) } b.Run("instrumentImpl/aggregate", func(b *testing.B) { - inst := instrumentImpl[int64]{aggregators: []internal.Aggregator[int64]{ + inst := int64Inst{aggregators: []internal.Aggregator[int64]{ internal.NewLastValue[int64](), internal.NewCumulativeSum[int64](true), internal.NewDeltaSum[int64](true), diff --git a/sdk/metric/internal/aggregator_example_test.go b/sdk/metric/internal/aggregator_example_test.go index 297d0f5118e..f36d8262c5e 100644 --- a/sdk/metric/internal/aggregator_example_test.go +++ b/sdk/metric/internal/aggregator_example_test.go @@ -94,8 +94,8 @@ type inst struct { embedded.Int64Histogram } -func (inst) Add(context.Context, int64, ...attribute.KeyValue) {} -func (inst) Record(context.Context, int64, ...attribute.KeyValue) {} +func (inst) Add(context.Context, int64, ...instrument.AddOption) {} +func (inst) Record(context.Context, int64, ...instrument.RecordOption) {} func Example() { m := meter{} diff --git a/sdk/metric/meter.go b/sdk/metric/meter.go index 72b37ea6634..fe74a73de70 100644 --- a/sdk/metric/meter.go +++ b/sdk/metric/meter.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" @@ -38,8 +37,8 @@ type meter struct { scope instrumentation.Scope pipes pipelines - int64IP *instProvider[int64] - float64IP *instProvider[float64] + int64IP *int64InstProvider + float64IP *float64InstProvider } func newMeter(s instrumentation.Scope, p pipelines) *meter { @@ -50,8 +49,8 @@ func newMeter(s instrumentation.Scope, p pipelines) *meter { return &meter{ scope: s, pipes: p, - int64IP: newInstProvider[int64](s, p, &viewCache), - float64IP: newInstProvider[float64](s, p, &viewCache), + int64IP: newInt64InstProvider(s, p, &viewCache), + float64IP: newFloat64InstProvider(s, p, &viewCache), } } @@ -297,7 +296,7 @@ var ( errUnregObserver = errors.New("observable instrument not registered for callback") ) -func (r observer) ObserveFloat64(o instrument.Float64Observable, v float64, a ...attribute.KeyValue) { +func (r observer) ObserveFloat64(o instrument.Float64Observable, v float64, opts ...instrument.ObserveOption) { var oImpl float64Observable switch conv := o.(type) { case float64Observable: @@ -326,10 +325,11 @@ func (r observer) ObserveFloat64(o instrument.Float64Observable, v float64, a .. ) return } - oImpl.observe(v, a) + c := instrument.NewObserveConfig(opts) + oImpl.observe(v, c.Attributes()) } -func (r observer) ObserveInt64(o instrument.Int64Observable, v int64, a ...attribute.KeyValue) { +func (r observer) ObserveInt64(o instrument.Int64Observable, v int64, opts ...instrument.ObserveOption) { var oImpl int64Observable switch conv := o.(type) { case int64Observable: @@ -358,7 +358,8 @@ func (r observer) ObserveInt64(o instrument.Int64Observable, v int64, a ...attri ) return } - oImpl.observe(v, a) + c := instrument.NewObserveConfig(opts) + oImpl.observe(v, c.Attributes()) } type noopRegister struct{ embedded.Registration } @@ -367,18 +368,18 @@ func (noopRegister) Unregister() error { return nil } -// instProvider provides all OpenTelemetry instruments. -type instProvider[N int64 | float64] struct { +// int64InstProvider provides int64 OpenTelemetry instruments. +type int64InstProvider struct { scope instrumentation.Scope pipes pipelines - resolve resolver[N] + resolve resolver[int64] } -func newInstProvider[N int64 | float64](s instrumentation.Scope, p pipelines, c *cache[string, streamID]) *instProvider[N] { - return &instProvider[N]{scope: s, pipes: p, resolve: newResolver[N](p, c)} +func newInt64InstProvider(s instrumentation.Scope, p pipelines, c *cache[string, streamID]) *int64InstProvider { + return &int64InstProvider{scope: s, pipes: p, resolve: newResolver[int64](p, c)} } -func (p *instProvider[N]) aggs(kind InstrumentKind, name, desc, u string) ([]internal.Aggregator[N], error) { +func (p *int64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]internal.Aggregator[int64], error) { inst := Instrument{ Name: name, Description: desc, @@ -390,12 +391,40 @@ func (p *instProvider[N]) aggs(kind InstrumentKind, name, desc, u string) ([]int } // lookup returns the resolved instrumentImpl. -func (p *instProvider[N]) lookup(kind InstrumentKind, name, desc, u string) (*instrumentImpl[N], error) { +func (p *int64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*int64Inst, error) { aggs, err := p.aggs(kind, name, desc, u) - return &instrumentImpl[N]{aggregators: aggs}, err + return &int64Inst{aggregators: aggs}, err } -type int64ObservProvider struct{ *instProvider[int64] } +// float64InstProvider provides float64 OpenTelemetry instruments. +type float64InstProvider struct { + scope instrumentation.Scope + pipes pipelines + resolve resolver[float64] +} + +func newFloat64InstProvider(s instrumentation.Scope, p pipelines, c *cache[string, streamID]) *float64InstProvider { + return &float64InstProvider{scope: s, pipes: p, resolve: newResolver[float64](p, c)} +} + +func (p *float64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]internal.Aggregator[float64], error) { + inst := Instrument{ + Name: name, + Description: desc, + Unit: u, + Kind: kind, + Scope: p.scope, + } + return p.resolve.Aggregators(inst) +} + +// lookup returns the resolved instrumentImpl. +func (p *float64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*float64Inst, error) { + aggs, err := p.aggs(kind, name, desc, u) + return &float64Inst{aggregators: aggs}, err +} + +type int64ObservProvider struct{ *int64InstProvider } func (p int64ObservProvider) lookup(kind InstrumentKind, name, desc, u string) (int64Observable, error) { aggs, err := p.aggs(kind, name, desc, u) @@ -423,11 +452,12 @@ type int64Observer struct { int64Observable } -func (o int64Observer) Observe(val int64, attrs ...attribute.KeyValue) { - o.observe(val, attrs) +func (o int64Observer) Observe(val int64, opts ...instrument.ObserveOption) { + c := instrument.NewObserveConfig(opts) + o.observe(val, c.Attributes()) } -type float64ObservProvider struct{ *instProvider[float64] } +type float64ObservProvider struct{ *float64InstProvider } func (p float64ObservProvider) lookup(kind InstrumentKind, name, desc, u string) (float64Observable, error) { aggs, err := p.aggs(kind, name, desc, u) @@ -455,6 +485,7 @@ type float64Observer struct { float64Observable } -func (o float64Observer) Observe(val float64, attrs ...attribute.KeyValue) { - o.observe(val, attrs) +func (o float64Observer) Observe(val float64, opts ...instrument.ObserveOption) { + c := instrument.NewObserveConfig(opts) + o.observe(val, c.Attributes()) } diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index 53abd91e661..5276f1ae04c 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -169,7 +169,8 @@ func TestCallbackUnregisterConcurrency(t *testing.T) { // Instruments should produce correct ResourceMetrics. func TestMeterCreatesInstruments(t *testing.T) { - attrs := []attribute.KeyValue{attribute.String("name", "alice")} + attrs := attribute.NewSet(attribute.String("name", "alice")) + opt := instrument.WithAttributeSet(attrs) testCases := []struct { name string fn func(*testing.T, metric.Meter) @@ -179,7 +180,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableInt64Count", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Int64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } ctr, err := m.Int64ObservableCounter("aint", instrument.WithInt64Callback(cback)) @@ -196,7 +197,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 3}, }, }, @@ -206,7 +207,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableInt64UpDownCount", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Int64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } ctr, err := m.Int64ObservableUpDownCounter("aint", instrument.WithInt64Callback(cback)) @@ -223,7 +224,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 11}, }, }, @@ -233,7 +234,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableInt64Gauge", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Int64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } gauge, err := m.Int64ObservableGauge("agauge", instrument.WithInt64Callback(cback)) @@ -248,7 +249,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Name: "agauge", Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 11}, }, }, @@ -258,7 +259,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableFloat64Count", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Float64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } ctr, err := m.Float64ObservableCounter("afloat", instrument.WithFloat64Callback(cback)) @@ -275,7 +276,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 3}, }, }, @@ -285,7 +286,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableFloat64UpDownCount", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Float64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } ctr, err := m.Float64ObservableUpDownCounter("afloat", instrument.WithFloat64Callback(cback)) @@ -302,7 +303,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[float64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 11}, }, }, @@ -312,7 +313,7 @@ func TestMeterCreatesInstruments(t *testing.T) { name: "ObservableFloat64Gauge", fn: func(t *testing.T, m metric.Meter) { cback := func(_ context.Context, o instrument.Float64Observer) error { - o.Observe(4, attrs...) + o.Observe(4, opt) return nil } gauge, err := m.Float64ObservableGauge("agauge", instrument.WithFloat64Callback(cback)) @@ -327,7 +328,7 @@ func TestMeterCreatesInstruments(t *testing.T) { Name: "agauge", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ - {Attributes: attribute.NewSet(attrs...), Value: 4}, + {Attributes: attrs, Value: 4}, {Value: 11}, }, }, @@ -885,6 +886,12 @@ func TestAttributeFilter(t *testing.T) { } func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { + fooBar := attribute.NewSet(attribute.String("foo", "bar")) + withFooBar := instrument.WithAttributeSet(fooBar) + v1 := attribute.NewSet(attribute.String("foo", "bar"), attribute.Int("version", 1)) + withV1 := instrument.WithAttributeSet(v1) + v2 := attribute.NewSet(attribute.String("foo", "bar"), attribute.Int("version", 2)) + withV2 := instrument.WithAttributeSet(v2) testcases := []struct { name string register func(t *testing.T, mtr metric.Meter) error @@ -898,9 +905,9 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar")) - o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveFloat64(ctr, 1.0, withV1) + o.ObserveFloat64(ctr, 2.0, withFooBar) + o.ObserveFloat64(ctr, 1.0, withV2) return nil }, ctr) return err @@ -909,10 +916,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Name: "afcounter", Data: metricdata.Sum[float64]{ DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 4.0, - }, + {Attributes: fooBar, Value: 4.0}, }, Temporality: temporality, IsMonotonic: true, @@ -927,9 +931,9 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar")) - o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveFloat64(ctr, 1.0, withV1) + o.ObserveFloat64(ctr, 2.0, withFooBar) + o.ObserveFloat64(ctr, 1.0, withV2) return nil }, ctr) return err @@ -956,8 +960,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveFloat64(ctr, 1.0, withV1) + o.ObserveFloat64(ctr, 2.0, withV2) return nil }, ctr) return err @@ -966,10 +970,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Name: "afgauge", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 2.0, - }, + {Attributes: fooBar, Value: 2.0}, }, }, }, @@ -982,9 +983,9 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveInt64(ctr, 20, attribute.String("foo", "bar")) - o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveInt64(ctr, 10, withV1) + o.ObserveInt64(ctr, 20, withFooBar) + o.ObserveInt64(ctr, 10, withV2) return nil }, ctr) return err @@ -993,10 +994,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Name: "aicounter", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 40, - }, + {Attributes: fooBar, Value: 40}, }, Temporality: temporality, IsMonotonic: true, @@ -1011,9 +1009,9 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveInt64(ctr, 20, attribute.String("foo", "bar")) - o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveInt64(ctr, 10, withV1) + o.ObserveInt64(ctr, 20, withFooBar) + o.ObserveInt64(ctr, 10, withV2) return nil }, ctr) return err @@ -1022,10 +1020,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Name: "aiupdowncounter", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 40, - }, + {Attributes: fooBar, Value: 40}, }, Temporality: temporality, IsMonotonic: false, @@ -1040,8 +1035,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } _, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error { - o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1)) - o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2)) + o.ObserveInt64(ctr, 10, withV1) + o.ObserveInt64(ctr, 20, withV2) return nil }, ctr) return err @@ -1050,10 +1045,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Name: "aigauge", Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 20, - }, + {Attributes: fooBar, Value: 20}, }, }, }, @@ -1066,18 +1058,15 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Add(context.Background(), 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Add(context.Background(), 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Add(context.Background(), 1.0, withV1) + ctr.Add(context.Background(), 2.0, withV2) return nil }, wantMetric: metricdata.Metrics{ Name: "sfcounter", Data: metricdata.Sum[float64]{ DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 3.0, - }, + {Attributes: fooBar, Value: 3.0}, }, Temporality: temporality, IsMonotonic: true, @@ -1092,18 +1081,15 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Add(context.Background(), 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Add(context.Background(), 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Add(context.Background(), 1.0, withV1) + ctr.Add(context.Background(), 2.0, withV2) return nil }, wantMetric: metricdata.Metrics{ Name: "sfupdowncounter", Data: metricdata.Sum[float64]{ DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 3.0, - }, + {Attributes: fooBar, Value: 3.0}, }, Temporality: temporality, IsMonotonic: false, @@ -1118,8 +1104,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Record(context.Background(), 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Record(context.Background(), 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Record(context.Background(), 1.0, withV1) + ctr.Record(context.Background(), 2.0, withV2) return nil }, wantMetric: metricdata.Metrics{ @@ -1127,7 +1113,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Data: metricdata.Histogram[float64]{ DataPoints: []metricdata.HistogramDataPoint[float64]{ { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), + Attributes: fooBar, Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Count: 2, @@ -1148,18 +1134,15 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Add(context.Background(), 10, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Add(context.Background(), 20, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Add(context.Background(), 10, withV1) + ctr.Add(context.Background(), 20, withV2) return nil }, wantMetric: metricdata.Metrics{ Name: "sicounter", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 30, - }, + {Attributes: fooBar, Value: 30}, }, Temporality: temporality, IsMonotonic: true, @@ -1174,18 +1157,15 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Add(context.Background(), 10, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Add(context.Background(), 20, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Add(context.Background(), 10, withV1) + ctr.Add(context.Background(), 20, withV2) return nil }, wantMetric: metricdata.Metrics{ Name: "siupdowncounter", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), - Value: 30, - }, + {Attributes: fooBar, Value: 30}, }, Temporality: temporality, IsMonotonic: false, @@ -1200,8 +1180,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { return err } - ctr.Record(context.Background(), 1, attribute.String("foo", "bar"), attribute.Int("version", 1)) - ctr.Record(context.Background(), 2, attribute.String("foo", "bar"), attribute.Int("version", 2)) + ctr.Record(context.Background(), 1, withV1) + ctr.Record(context.Background(), 2, withV2) return nil }, wantMetric: metricdata.Metrics{ @@ -1209,7 +1189,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { Data: metricdata.Histogram[int64]{ DataPoints: []metricdata.HistogramDataPoint[int64]{ { - Attributes: attribute.NewSet(attribute.String("foo", "bar")), + Attributes: fooBar, Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Count: 2, @@ -1296,7 +1276,7 @@ func TestObservableExample(t *testing.T) { _, err := meter.Int64ObservableCounter(instName, instrument.WithInt64Callback( func(_ context.Context, o instrument.Int64Observer) error { for attrSet, val := range observations { - o.Observe(val, attrSet.ToSlice()...) + o.Observe(val, instrument.WithAttributeSet(attrSet)) } return nil },