diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ca2daee0cbd..5e4fc315deb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -227,15 +227,6 @@ updates: schedule: day: sunday interval: weekly - - package-ecosystem: gomod - directory: /internal/metric - labels: - - dependencies - - go - - "Skip Changelog" - schedule: - day: sunday - interval: weekly - package-ecosystem: gomod directory: /internal/tools labels: diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e1f723a65..30b4333441d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### ⚠️ Notice ⚠️ + +This update is a breaking change of the unstable Metrics API. Code instrumented with the `go.opentelemetry.io/otel/metric` <= v0.27.0 will need to be modified. + ### Added - Added support to configure the span limits with environment variables. @@ -24,6 +28,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - For tracestate's members, prepend the new element and remove the oldest one, which is over capacity (#2592) - Add event and link drop counts to the exported data from the `oltptrace` exporter. (#2601) +- The metrics API has been significantly changed. (#2587) - Unify path cleaning functionally in the `otlpmetric` and `otlptrace` config. (#2639) - Change the debug message from the `sdk/trace.BatchSpanProcessor` to reflect the count is cumulative. (#2640) diff --git a/bridge/opencensus/aggregation.go b/bridge/opencensus/aggregation.go index 3d88f7589a4..99d2b07afad 100644 --- a/bridge/opencensus/aggregation.go +++ b/bridge/opencensus/aggregation.go @@ -21,8 +21,8 @@ import ( "go.opencensus.io/metric/metricdata" - "go.opentelemetry.io/otel/metric/number" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" ) var ( diff --git a/bridge/opencensus/exporter.go b/bridge/opencensus/exporter.go index bdc22eced73..d40ddc9d665 100644 --- a/bridge/opencensus/exporter.go +++ b/bridge/opencensus/exporter.go @@ -27,13 +27,13 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -170,17 +170,17 @@ func convertDescriptor(ocDescriptor metricdata.Descriptor) (sdkapi.Descriptor, e // Includes TypeGaugeDistribution, TypeCumulativeDistribution, TypeSummary return sdkapi.Descriptor{}, fmt.Errorf("%w; descriptor type: %v", errConversion, ocDescriptor.Type) } - opts := []metric.InstrumentOption{ - metric.WithDescription(ocDescriptor.Description), + opts := []instrument.Option{ + instrument.WithDescription(ocDescriptor.Description), } switch ocDescriptor.Unit { case metricdata.UnitDimensionless: - opts = append(opts, metric.WithUnit(unit.Dimensionless)) + opts = append(opts, instrument.WithUnit(unit.Dimensionless)) case metricdata.UnitBytes: - opts = append(opts, metric.WithUnit(unit.Bytes)) + opts = append(opts, instrument.WithUnit(unit.Bytes)) case metricdata.UnitMilliseconds: - opts = append(opts, metric.WithUnit(unit.Milliseconds)) + opts = append(opts, instrument.WithUnit(unit.Milliseconds)) } - cfg := metric.NewInstrumentConfig(opts...) + cfg := instrument.NewConfig(opts...) return sdkapi.NewDescriptor(ocDescriptor.Name, ikind, nkind, cfg.Description(), cfg.Unit()), nil } diff --git a/bridge/opencensus/exporter_test.go b/bridge/opencensus/exporter_test.go index 8fe6893477e..79e195c1f6c 100644 --- a/bridge/opencensus/exporter_test.go +++ b/bridge/opencensus/exporter_test.go @@ -26,15 +26,15 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/controller/controllertest" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -400,8 +400,8 @@ func TestConvertDescriptor(t *testing.T) { "foo", sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, - metric.WithDescription("bar"), - metric.WithUnit(unit.Bytes), + instrument.WithDescription("bar"), + instrument.WithUnit(unit.Bytes), ), }, { @@ -416,8 +416,8 @@ func TestConvertDescriptor(t *testing.T) { "foo", sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, - metric.WithDescription("bar"), - metric.WithUnit(unit.Milliseconds), + instrument.WithDescription("bar"), + instrument.WithUnit(unit.Milliseconds), ), }, { @@ -432,8 +432,8 @@ func TestConvertDescriptor(t *testing.T) { "foo", sdkapi.CounterObserverInstrumentKind, number.Int64Kind, - metric.WithDescription("bar"), - metric.WithUnit(unit.Dimensionless), + instrument.WithDescription("bar"), + instrument.WithUnit(unit.Dimensionless), ), }, { @@ -448,8 +448,8 @@ func TestConvertDescriptor(t *testing.T) { "foo", sdkapi.CounterObserverInstrumentKind, number.Float64Kind, - metric.WithDescription("bar"), - metric.WithUnit(unit.Dimensionless), + instrument.WithDescription("bar"), + instrument.WithUnit(unit.Dimensionless), ), }, { diff --git a/example/prometheus/main.go b/example/prometheus/main.go index 8a1248a358e..12148d2595f 100644 --- a/example/prometheus/main.go +++ b/example/prometheus/main.go @@ -25,7 +25,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" @@ -35,6 +35,9 @@ import ( var ( lemonsKey = attribute.Key("ex.com/lemons") + + // TODO Bring back Global package + meterProvider metric.MeterProvider ) func initMeter() { @@ -54,7 +57,9 @@ func initMeter() { if err != nil { log.Panicf("failed to initialize prometheus exporter %v", err) } - global.SetMeterProvider(exporter.MeterProvider()) + // TODO Bring back Global package + // global.SetMeterProvider(exporter.MeterProvider()) + meterProvider = exporter.MeterProvider() http.HandleFunc("/", exporter.ServeHTTP) go func() { @@ -67,23 +72,33 @@ func initMeter() { func main() { initMeter() - meter := global.Meter("ex.com/basic") + // TODO Bring back Global package + // meter := global.Meter("ex.com/basic") + meter := meterProvider.Meter("ex.com/basic") observerLock := new(sync.RWMutex) observerValueToReport := new(float64) observerLabelsToReport := new([]attribute.KeyValue) - cb := func(_ context.Context, result metric.Float64ObserverResult) { + + gaugeObserver, err := meter.AsyncFloat64().Gauge("ex.com.one") + if err != nil { + log.Panicf("failed to initialize instrument: %v", err) + } + _ = meter.RegisterCallback([]instrument.Asynchronous{gaugeObserver}, func(ctx context.Context) { (*observerLock).RLock() value := *observerValueToReport labels := *observerLabelsToReport (*observerLock).RUnlock() - result.Observe(value, labels...) - } - _ = metric.Must(meter).NewFloat64GaugeObserver("ex.com.one", cb, - metric.WithDescription("A GaugeObserver set to 1.0"), - ) + gaugeObserver.Observe(ctx, value, labels...) + }) - histogram := metric.Must(meter).NewFloat64Histogram("ex.com.two") - counter := metric.Must(meter).NewFloat64Counter("ex.com.three") + histogram, err := meter.SyncFloat64().Histogram("ex.com.two") + if err != nil { + log.Panicf("failed to initialize instrument: %v", err) + } + counter, err := meter.SyncFloat64().Counter("ex.com.three") + if err != nil { + log.Panicf("failed to initialize instrument: %v", err) + } commonLabels := []attribute.KeyValue{lemonsKey.Int(10), attribute.String("A", "1"), attribute.String("B", "2"), attribute.String("C", "3")} notSoCommonLabels := []attribute.KeyValue{lemonsKey.Int(13)} @@ -94,12 +109,9 @@ func main() { *observerValueToReport = 1.0 *observerLabelsToReport = commonLabels (*observerLock).Unlock() - meter.RecordBatch( - ctx, - commonLabels, - histogram.Measurement(2.0), - counter.Measurement(12.0), - ) + + histogram.Record(ctx, 2.0, commonLabels...) + counter.Add(ctx, 12.0, commonLabels...) time.Sleep(5 * time.Second) @@ -107,12 +119,8 @@ func main() { *observerValueToReport = 1.0 *observerLabelsToReport = notSoCommonLabels (*observerLock).Unlock() - meter.RecordBatch( - ctx, - notSoCommonLabels, - histogram.Measurement(2.0), - counter.Measurement(22.0), - ) + histogram.Record(ctx, 2.0, notSoCommonLabels...) + counter.Add(ctx, 22.0, notSoCommonLabels...) time.Sleep(5 * time.Second) @@ -120,12 +128,8 @@ func main() { *observerValueToReport = 13.0 *observerLabelsToReport = commonLabels (*observerLock).Unlock() - meter.RecordBatch( - ctx, - commonLabels, - histogram.Measurement(12.0), - counter.Measurement(13.0), - ) + histogram.Record(ctx, 12.0, commonLabels...) + counter.Add(ctx, 13.0, commonLabels...) fmt.Println("Example finished updating, please visit :2222") diff --git a/exporters/otlp/otlpmetric/exporter.go b/exporters/otlp/otlpmetric/exporter.go index 36c41cce762..caf21eaf2a3 100644 --- a/exporters/otlp/otlpmetric/exporter.go +++ b/exporters/otlp/otlpmetric/exporter.go @@ -20,9 +20,9 @@ import ( "sync" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/metrictransform" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/exporters/otlp/otlpmetric/exporter_test.go b/exporters/otlp/otlpmetric/exporter_test.go index 31e3f3e6848..b8ba3dce95e 100644 --- a/exporters/otlp/otlpmetric/exporter_test.go +++ b/exporters/otlp/otlpmetric/exporter_test.go @@ -29,16 +29,16 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/metrictransform" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" commonpb "go.opentelemetry.io/proto/otlp/common/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/metric.go b/exporters/otlp/otlpmetric/internal/metrictransform/metric.go index e1ba26dccd0..002bb64a997 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/metric.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/metric.go @@ -24,10 +24,10 @@ import ( "sync" "time" - "go.opentelemetry.io/otel/metric/number" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" "go.opentelemetry.io/otel/sdk/resource" commonpb "go.opentelemetry.io/proto/otlp/common/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go b/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go index 7b5b801eb6b..d8f2a1c8b62 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go @@ -25,14 +25,14 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" commonpb "go.opentelemetry.io/proto/otlp/common/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) diff --git a/exporters/otlp/otlpmetric/internal/otlpmetrictest/data.go b/exporters/otlp/otlpmetric/internal/otlpmetrictest/data.go index cc3f7871598..2da921f142c 100644 --- a/exporters/otlp/otlpmetric/internal/otlpmetrictest/data.go +++ b/exporters/otlp/otlpmetric/internal/otlpmetrictest/data.go @@ -20,13 +20,13 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // OneRecordReader is a Reader that returns just one diff --git a/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go b/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go index 53af9d5565a..524cb774588 100644 --- a/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go +++ b/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go @@ -25,12 +25,12 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/selector/simple" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) @@ -63,34 +63,37 @@ func RunEndToEndTest(ctx context.Context, t *testing.T, exp *otlpmetric.Exporter case sdkapi.CounterInstrumentKind: switch data.nKind { case number.Int64Kind: - metric.Must(meter).NewInt64Counter(name).Add(ctx, data.val, labels...) + c, _ := meter.SyncInt64().Counter(name) + c.Add(ctx, data.val, labels...) case number.Float64Kind: - metric.Must(meter).NewFloat64Counter(name).Add(ctx, float64(data.val), labels...) + c, _ := meter.SyncFloat64().Counter(name) + c.Add(ctx, float64(data.val), labels...) default: assert.Failf(t, "unsupported number testing kind", data.nKind.String()) } case sdkapi.HistogramInstrumentKind: switch data.nKind { case number.Int64Kind: - metric.Must(meter).NewInt64Histogram(name).Record(ctx, data.val, labels...) + c, _ := meter.SyncInt64().Histogram(name) + c.Record(ctx, data.val, labels...) case number.Float64Kind: - metric.Must(meter).NewFloat64Histogram(name).Record(ctx, float64(data.val), labels...) + c, _ := meter.SyncFloat64().Histogram(name) + c.Record(ctx, float64(data.val), labels...) default: assert.Failf(t, "unsupported number testing kind", data.nKind.String()) } case sdkapi.GaugeObserverInstrumentKind: switch data.nKind { case number.Int64Kind: - metric.Must(meter).NewInt64GaugeObserver(name, - func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(data.val, labels...) - }, - ) + g, _ := meter.AsyncInt64().Gauge(name) + _ = meter.RegisterCallback([]instrument.Asynchronous{g}, func(ctx context.Context) { + g.Observe(ctx, data.val, labels...) + }) case number.Float64Kind: - callback := func(v float64) metric.Float64ObserverFunc { - return metric.Float64ObserverFunc(func(_ context.Context, result metric.Float64ObserverResult) { result.Observe(v, labels...) }) - }(float64(data.val)) - metric.Must(meter).NewFloat64GaugeObserver(name, callback) + g, _ := meter.AsyncFloat64().Gauge(name) + _ = meter.RegisterCallback([]instrument.Asynchronous{g}, func(ctx context.Context) { + g.Observe(ctx, float64(data.val), labels...) + }) default: assert.Failf(t, "unsupported number testing kind", data.nKind.String()) } diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/example_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/example_test.go index f37c39095f7..fe0866b7af5 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/example_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/example_test.go @@ -24,8 +24,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" @@ -54,7 +53,8 @@ func Example_insecure() { controller.WithExporter(exp), controller.WithCollectPeriod(2*time.Second), ) - global.SetMeterProvider(pusher) + // TODO Bring back Global package + // global.SetMeterProvider(pusher) if err := pusher.Start(ctx); err != nil { log.Fatalf("could not start metric controoler: %v", err) @@ -68,14 +68,16 @@ func Example_insecure() { } }() - meter := global.Meter("test-meter") + // TODO Bring Back Global package + // meter := global.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") + meter := pusher.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") // Recorder metric example - counter := metric.Must(meter). - NewFloat64Counter( - "an_important_metric", - metric.WithDescription("Measures the cumulative epicness of the app"), - ) + + counter, err := meter.SyncFloat64().Counter("an_important_metric", instrument.WithDescription("Measures the cumulative epicness of the app")) + if err != nil { + log.Fatalf("Failed to create the instrument: %v", err) + } for i := 0; i < 10; i++ { log.Printf("Doing really hard work (%d / 10)\n", i+1) @@ -113,7 +115,8 @@ func Example_withTLS() { controller.WithExporter(exp), controller.WithCollectPeriod(2*time.Second), ) - global.SetMeterProvider(pusher) + // TODO Bring back Global package + // global.SetMeterProvider(pusher) if err := pusher.Start(ctx); err != nil { log.Fatalf("could not start metric controoler: %v", err) @@ -128,14 +131,15 @@ func Example_withTLS() { } }() - meter := global.Meter("test-meter") + // TODO Bring back Global package + // meter := global.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") + meter := pusher.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") // Recorder metric example - counter := metric.Must(meter). - NewFloat64Counter( - "an_important_metric", - metric.WithDescription("Measures the cumulative epicness of the app"), - ) + counter, err := meter.SyncFloat64().Counter("an_important_metric", instrument.WithDescription("Measures the cumulative epicness of the app")) + if err != nil { + log.Fatalf("Failed to create the instrument: %v", err) + } for i := 0; i < 10; i++ { log.Printf("Doing really hard work (%d / 10)\n", i+1) @@ -170,7 +174,8 @@ func Example_withDifferentSignalCollectors() { controller.WithExporter(exp), controller.WithCollectPeriod(2*time.Second), ) - global.SetMeterProvider(pusher) + // TODO Bring back Global package + // global.SetMeterProvider(pusher) if err := pusher.Start(ctx); err != nil { log.Fatalf("could not start metric controoler: %v", err) @@ -184,14 +189,15 @@ func Example_withDifferentSignalCollectors() { } }() - meter := global.Meter("test-meter") + // TODO Bring back Global package + // meter := global.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") + meter := pusher.Meter("go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc_test") // Recorder metric example - counter := metric.Must(meter). - NewFloat64Counter( - "an_important_metric", - metric.WithDescription("Measures the cumulative epicness of the app"), - ) + counter, err := meter.SyncFloat64().Counter("an_important_metric", instrument.WithDescription("Measures the cumulative epicness of the app")) + if err != nil { + log.Fatalf("Failed to create the instrument: %v", err) + } for i := 0; i < 10; i++ { log.Printf("Doing really hard work (%d / 10)\n", i+1) diff --git a/exporters/prometheus/prometheus.go b/exporters/prometheus/prometheus.go index 08595526263..7d31514fb5f 100644 --- a/exporters/prometheus/prometheus.go +++ b/exporters/prometheus/prometheus.go @@ -29,12 +29,12 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/exporters/prometheus/prometheus_test.go b/exporters/prometheus/prometheus_test.go index b05d9232117..587c8633e78 100644 --- a/exporters/prometheus/prometheus_test.go +++ b/exporters/prometheus/prometheus_test.go @@ -26,7 +26,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/prometheus" - "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" @@ -107,9 +107,12 @@ func TestPrometheusExporter(t *testing.T) { require.NoError(t, err) meter := exporter.MeterProvider().Meter("test") - upDownCounter := metric.Must(meter).NewFloat64UpDownCounter("updowncounter") - counter := metric.Must(meter).NewFloat64Counter("counter") - histogram := metric.Must(meter).NewFloat64Histogram("histogram") + upDownCounter, err := meter.SyncFloat64().UpDownCounter("updowncounter") + require.NoError(t, err) + counter, err := meter.SyncFloat64().Counter("counter") + require.NoError(t, err) + histogram, err := meter.SyncFloat64().Histogram("histogram") + require.NoError(t, err) labels := []attribute.KeyValue{ attribute.Key("A").String("B"), @@ -124,9 +127,13 @@ func TestPrometheusExporter(t *testing.T) { expected = append(expected, expectCounter("counter", `counter{A="B",C="D",R="V"} 15.3`)) - _ = metric.Must(meter).NewInt64GaugeObserver("intgaugeobserver", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(1, labels...) + gaugeObserver, err := meter.AsyncInt64().Gauge("intgaugeobserver") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{gaugeObserver}, func(ctx context.Context) { + gaugeObserver.Observe(ctx, 1, labels...) }) + require.NoError(t, err) expected = append(expected, expectGauge("intgaugeobserver", `intgaugeobserver{A="B",C="D",R="V"} 1`)) @@ -148,15 +155,23 @@ func TestPrometheusExporter(t *testing.T) { expected = append(expected, expectGauge("updowncounter", `updowncounter{A="B",C="D",R="V"} 6.8`)) - _ = metric.Must(meter).NewFloat64CounterObserver("floatcounterobserver", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(7.7, labels...) + counterObserver, err := meter.AsyncFloat64().Counter("floatcounterobserver") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + counterObserver.Observe(ctx, 7.7, labels...) }) + require.NoError(t, err) expected = append(expected, expectCounter("floatcounterobserver", `floatcounterobserver{A="B",C="D",R="V"} 7.7`)) - _ = metric.Must(meter).NewFloat64UpDownCounterObserver("floatupdowncounterobserver", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(-7.7, labels...) + upDownCounterObserver, err := meter.AsyncFloat64().UpDownCounter("floatupdowncounterobserver") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{upDownCounterObserver}, func(ctx context.Context) { + upDownCounterObserver.Observe(ctx, -7.7, labels...) }) + require.NoError(t, err) expected = append(expected, expectGauge("floatupdowncounterobserver", `floatupdowncounterobserver{A="B",C="D",R="V"} -7.7`)) @@ -196,10 +211,8 @@ func TestPrometheusStatefulness(t *testing.T) { ctx := context.Background() - counter := metric.Must(meter).NewInt64Counter( - "a.counter", - metric.WithDescription("Counts things"), - ) + counter, err := meter.SyncInt64().Counter("a.counter", instrument.WithDescription("Counts things")) + require.NoError(t, err) counter.Add(ctx, 100, attribute.String("key", "value")) diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index c367b767040..1250a463a8c 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -21,7 +21,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument/syncint64" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" @@ -33,13 +33,15 @@ const ( ) var ( - meter = global.GetMeterProvider().Meter( - instrumentationName, - metric.WithInstrumentationVersion(instrumentationVersion), - ) + // TODO Bring back Global package + // meter = global.GetMeterProvider().Meter( + // instrumentationName, + // metric.WithInstrumentationVersion(instrumentationVersion), + // ) + meter metric.Meter - loopCounter = metric.Must(meter).NewInt64Counter("function.loops") - paramValue = metric.Must(meter).NewInt64Histogram("function.param") + loopCounter syncint64.Counter + paramValue syncint64.Histogram nameKey = attribute.Key("function.name") ) @@ -80,7 +82,18 @@ func InstallExportPipeline(ctx context.Context) func() { if err = pusher.Start(ctx); err != nil { log.Fatalf("starting push controller: %v", err) } - global.SetMeterProvider(pusher) + // TODO Bring back Global package + // global.SetMeterProvider(pusher) + meter = pusher.Meter(instrumentationName, metric.WithInstrumentationVersion(instrumentationVersion)) + + loopCounter, err = meter.SyncInt64().Counter("function.loops") + if err != nil { + log.Fatalf("creating instrument: %v", err) + } + paramValue, err = meter.SyncInt64().Histogram("function.param") + if err != nil { + log.Fatalf("creating instrument: %v", err) + } return func() { if err := pusher.Stop(ctx); err != nil { @@ -92,7 +105,7 @@ func InstallExportPipeline(ctx context.Context) func() { func Example() { ctx := context.Background() - // Registers a meter Provider globally. + // TODO: Registers a meter Provider globally. cleanup := InstallExportPipeline(ctx) defer cleanup() diff --git a/exporters/stdout/stdoutmetric/metric.go b/exporters/stdout/stdoutmetric/metric.go index 56e449de20d..c816c035b1c 100644 --- a/exporters/stdout/stdoutmetric/metric.go +++ b/exporters/stdout/stdoutmetric/metric.go @@ -22,10 +22,10 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/exporters/stdout/stdoutmetric/metric_test.go b/exporters/stdout/stdoutmetric/metric_test.go index e9153fcfd0a..33e2831dbd7 100644 --- a/exporters/stdout/stdoutmetric/metric_test.go +++ b/exporters/stdout/stdoutmetric/metric_test.go @@ -101,7 +101,8 @@ func TestStdoutTimestamp(t *testing.T) { require.NoError(t, cont.Start(ctx)) meter := cont.Meter("test") - counter := metric.Must(meter).NewInt64Counter("name.lastvalue") + counter, err := meter.SyncInt64().Counter("name.lastvalue") + require.NoError(t, err) before := time.Now() // Ensure the timestamp is after before. @@ -137,7 +138,8 @@ func TestStdoutTimestamp(t *testing.T) { func TestStdoutCounterFormat(t *testing.T) { fix := newFixture(t) - counter := metric.Must(fix.meter).NewInt64Counter("name.sum") + counter, err := fix.meter.SyncInt64().Counter("name.sum") + require.NoError(t, err) counter.Add(fix.ctx, 123, attribute.String("A", "B"), attribute.String("C", "D")) require.NoError(t, fix.cont.Stop(fix.ctx)) @@ -148,7 +150,8 @@ func TestStdoutCounterFormat(t *testing.T) { func TestStdoutLastValueFormat(t *testing.T) { fix := newFixture(t) - counter := metric.Must(fix.meter).NewFloat64Counter("name.lastvalue") + counter, err := fix.meter.SyncFloat64().Counter("name.lastvalue") + require.NoError(t, err) counter.Add(fix.ctx, 123.456, attribute.String("A", "B"), attribute.String("C", "D")) require.NoError(t, fix.cont.Stop(fix.ctx)) @@ -159,7 +162,8 @@ func TestStdoutLastValueFormat(t *testing.T) { func TestStdoutHistogramFormat(t *testing.T) { fix := newFixture(t, stdoutmetric.WithPrettyPrint()) - inst := metric.Must(fix.meter).NewFloat64Histogram("name.histogram") + inst, err := fix.meter.SyncFloat64().Histogram("name.histogram") + require.NoError(t, err) for i := 0; i < 1000; i++ { inst.Record(fix.ctx, float64(i)+0.5, attribute.String("A", "B"), attribute.String("C", "D")) @@ -181,7 +185,8 @@ func TestStdoutNoData(t *testing.T) { t.Parallel() fix := newFixture(t) - _ = metric.Must(fix.meter).NewFloat64Counter(fmt.Sprint("name.", aggName)) + _, err := fix.meter.SyncFloat64().Counter(fmt.Sprint("name.", aggName)) + require.NoError(t, err) require.NoError(t, fix.cont.Stop(fix.ctx)) require.Equal(t, "", fix.Output()) @@ -243,7 +248,8 @@ func TestStdoutResource(t *testing.T) { ctx := context.Background() fix := newFixtureWithResource(t, tc.res) - counter := metric.Must(fix.meter).NewFloat64Counter("name.lastvalue") + counter, err := fix.meter.SyncFloat64().Counter("name.lastvalue") + require.NoError(t, err) counter.Add(ctx, 123.456, tc.attrs...) require.NoError(t, fix.cont.Stop(fix.ctx)) diff --git a/internal/metric/async.go b/internal/metric/async.go deleted file mode 100644 index 94801b7ff3c..00000000000 --- a/internal/metric/async.go +++ /dev/null @@ -1,149 +0,0 @@ -// 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 metric // import "go.opentelemetry.io/otel/internal/metric" - -import ( - "context" - "errors" - "fmt" - "sync" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -//nolint:revive // ignoring missing comments for exported error in an internal package -var ErrInvalidAsyncRunner = errors.New("unknown async runner type") - -// AsyncCollector is an interface used between the MeterImpl and the -// AsyncInstrumentState helper below. This interface is implemented by -// the SDK to provide support for running observer callbacks. -type AsyncCollector interface { - // CollectAsync passes a batch of observations to the MeterImpl. - CollectAsync(labels []attribute.KeyValue, observation ...sdkapi.Observation) -} - -// AsyncInstrumentState manages an ordered set of asynchronous -// instruments and the distinct runners, taking into account batch -// observer callbacks. -type AsyncInstrumentState struct { - lock sync.Mutex - - // errorOnce will use the otel.Handler to report an error - // once in case of an invalid runner attempting to run. - errorOnce sync.Once - - // runnerMap keeps the set of runners that will run each - // collection interval. Singletons are entered with a real - // instrument each, batch observers are entered with a nil - // instrument, ensuring that when a singleton callback is used - // repeatedly, it is executed repeatedly in the interval, while - // when a batch callback is used repeatedly, it only executes - // once per interval. - runnerMap map[asyncRunnerPair]struct{} - - // runners maintains the set of runners in the order they were - // registered. - runners []asyncRunnerPair - - // instruments maintains the set of instruments in the order - // they were registered. - instruments []sdkapi.AsyncImpl -} - -// asyncRunnerPair is a map entry for Observer callback runners. -type asyncRunnerPair struct { - // runner is used as a map key here. The API ensures - // that all callbacks are pointers for this reason. - runner sdkapi.AsyncRunner - - // inst refers to a non-nil instrument when `runner` is a - // AsyncSingleRunner. - inst sdkapi.AsyncImpl -} - -// NewAsyncInstrumentState returns a new *AsyncInstrumentState, for -// use by MeterImpl to manage running the set of observer callbacks in -// the correct order. -func NewAsyncInstrumentState() *AsyncInstrumentState { - return &AsyncInstrumentState{ - runnerMap: map[asyncRunnerPair]struct{}{}, - } -} - -// Instruments returns the asynchronous instruments managed by this -// object, the set that should be checkpointed after observers are -// run. -func (a *AsyncInstrumentState) Instruments() []sdkapi.AsyncImpl { - a.lock.Lock() - defer a.lock.Unlock() - return a.instruments -} - -// Register adds a new asynchronous instrument to by managed by this -// object. This should be called during NewAsyncInstrument() and -// assumes that errors (e.g., duplicate registration) have already -// been checked. -func (a *AsyncInstrumentState) Register(inst sdkapi.AsyncImpl, runner sdkapi.AsyncRunner) { - a.lock.Lock() - defer a.lock.Unlock() - - a.instruments = append(a.instruments, inst) - - // asyncRunnerPair reflects this callback in the asyncRunners - // list. If this is a batch runner, the instrument is nil. - // If this is a single-Observer runner, the instrument is - // included. This ensures that batch callbacks are called - // once and single callbacks are called once per instrument. - rp := asyncRunnerPair{ - runner: runner, - } - if _, ok := runner.(sdkapi.AsyncSingleRunner); ok { - rp.inst = inst - } - - if _, ok := a.runnerMap[rp]; !ok { - a.runnerMap[rp] = struct{}{} - a.runners = append(a.runners, rp) - } -} - -// Run executes the complete set of observer callbacks. -func (a *AsyncInstrumentState) Run(ctx context.Context, collector AsyncCollector) { - a.lock.Lock() - runners := a.runners - a.lock.Unlock() - - for _, rp := range runners { - // The runner must be a single or batch runner, no - // other implementations are possible because the - // interface has un-exported methods. - - if singleRunner, ok := rp.runner.(sdkapi.AsyncSingleRunner); ok { - singleRunner.Run(ctx, rp.inst, collector.CollectAsync) - continue - } - - if multiRunner, ok := rp.runner.(sdkapi.AsyncBatchRunner); ok { - multiRunner.Run(ctx, collector.CollectAsync) - continue - } - - a.errorOnce.Do(func() { - otel.Handle(fmt.Errorf("%w: type %T (reported once)", ErrInvalidAsyncRunner, rp)) - }) - } -} diff --git a/internal/metric/global/benchmark_test.go b/internal/metric/global/benchmark_test.go deleted file mode 100644 index 05cab62d2dc..00000000000 --- a/internal/metric/global/benchmark_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// 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 global_test - -import ( - "context" - "testing" - - "go.opentelemetry.io/otel/attribute" - internalglobal "go.opentelemetry.io/otel/internal/metric/global" - metricglobal "go.opentelemetry.io/otel/metric/global" -) - -func BenchmarkGlobalInt64CounterAddNoSDK(b *testing.B) { - // Compare with BenchmarkGlobalInt64CounterAddWithSDK() in - // ../../sdk/metric/benchmark_test.go to see the overhead of the - // global no-op system against a registered SDK. - internalglobal.ResetForTest() - ctx := context.Background() - sdk := metricglobal.Meter("test") - labs := []attribute.KeyValue{attribute.String("A", "B")} - cnt := Must(sdk).NewInt64Counter("int64.counter") - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - cnt.Add(ctx, 1, labs...) - } -} diff --git a/internal/metric/global/internal_test.go b/internal/metric/global/internal_test.go deleted file mode 100644 index 44e1904aefe..00000000000 --- a/internal/metric/global/internal_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// 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 global_test - -import ( - "os" - "testing" - - ottest "go.opentelemetry.io/otel/internal/internaltest" - "go.opentelemetry.io/otel/internal/metric/global" -) - -// Ensure struct alignment prior to running tests. -func TestMain(m *testing.M) { - fieldsMap := global.AtomicFieldOffsets() - fields := make([]ottest.FieldOffset, 0, len(fieldsMap)) - for name, offset := range fieldsMap { - fields = append(fields, ottest.FieldOffset{ - Name: name, - Offset: offset, - }) - } - if !ottest.Aligned8Byte(fields, os.Stderr) { - os.Exit(1) - } - - os.Exit(m.Run()) -} diff --git a/internal/metric/global/meter.go b/internal/metric/global/meter.go deleted file mode 100644 index 77781ead118..00000000000 --- a/internal/metric/global/meter.go +++ /dev/null @@ -1,287 +0,0 @@ -// 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 global // import "go.opentelemetry.io/otel/internal/metric/global" - -import ( - "context" - "sync" - "sync/atomic" - "unsafe" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/internal/metric/registry" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -// This file contains the forwarding implementation of MeterProvider used as -// the default global instance. Metric events using instruments provided by -// this implementation are no-ops until the first Meter implementation is set -// as the global provider. -// -// The implementation here uses Mutexes to maintain a list of active Meters in -// the MeterProvider and Instruments in each Meter, under the assumption that -// these interfaces are not performance-critical. -// -// We have the invariant that setDelegate() will be called before a new -// MeterProvider implementation is registered as the global provider. Mutexes -// in the MeterProvider and Meters ensure that each instrument has a delegate -// before the global provider is set. -// -// Metric uniqueness checking is implemented by calling the exported -// methods of the api/metric/registry package. - -type meterKey struct { - InstrumentationName string - InstrumentationVersion string - SchemaURL string -} - -type meterProvider struct { - delegate metric.MeterProvider - - // lock protects `delegate` and `meters`. - lock sync.Mutex - - // meters maintains a unique entry for every named Meter - // that has been registered through the global instance. - meters map[meterKey]*meterEntry -} - -type meterImpl struct { - delegate unsafe.Pointer // (*metric.MeterImpl) - - lock sync.Mutex - syncInsts []*syncImpl - asyncInsts []*asyncImpl -} - -type meterEntry struct { - unique sdkapi.MeterImpl - impl meterImpl -} - -type instrument struct { - descriptor sdkapi.Descriptor -} - -type syncImpl struct { - delegate unsafe.Pointer // (*sdkapi.SyncImpl) - - instrument -} - -type asyncImpl struct { - delegate unsafe.Pointer // (*sdkapi.AsyncImpl) - - instrument - - runner sdkapi.AsyncRunner -} - -var _ metric.MeterProvider = &meterProvider{} -var _ sdkapi.MeterImpl = &meterImpl{} -var _ sdkapi.InstrumentImpl = &syncImpl{} -var _ sdkapi.AsyncImpl = &asyncImpl{} - -func (inst *instrument) Descriptor() sdkapi.Descriptor { - return inst.descriptor -} - -// MeterProvider interface and delegation - -func newMeterProvider() *meterProvider { - return &meterProvider{ - meters: map[meterKey]*meterEntry{}, - } -} - -func (p *meterProvider) setDelegate(provider metric.MeterProvider) { - p.lock.Lock() - defer p.lock.Unlock() - - p.delegate = provider - for key, entry := range p.meters { - entry.impl.setDelegate(key, provider) - } - p.meters = nil -} - -func (p *meterProvider) Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter { - p.lock.Lock() - defer p.lock.Unlock() - - if p.delegate != nil { - return p.delegate.Meter(instrumentationName, opts...) - } - - cfg := metric.NewMeterConfig(opts...) - key := meterKey{ - InstrumentationName: instrumentationName, - InstrumentationVersion: cfg.InstrumentationVersion(), - SchemaURL: cfg.SchemaURL(), - } - entry, ok := p.meters[key] - if !ok { - entry = &meterEntry{} - // Note: This code implements its own MeterProvider - // name-uniqueness logic because there is - // synchronization required at the moment of - // delegation. We use the same instrument-uniqueness - // checking the real SDK uses here: - entry.unique = registry.NewUniqueInstrumentMeterImpl(&entry.impl) - p.meters[key] = entry - } - return metric.WrapMeterImpl(entry.unique) -} - -// Meter interface and delegation - -func (m *meterImpl) setDelegate(key meterKey, provider metric.MeterProvider) { - m.lock.Lock() - defer m.lock.Unlock() - - d := new(sdkapi.MeterImpl) - *d = provider.Meter( - key.InstrumentationName, - metric.WithInstrumentationVersion(key.InstrumentationVersion), - metric.WithSchemaURL(key.SchemaURL), - ).MeterImpl() - m.delegate = unsafe.Pointer(d) - - for _, inst := range m.syncInsts { - inst.setDelegate(*d) - } - m.syncInsts = nil - for _, obs := range m.asyncInsts { - obs.setDelegate(*d) - } - m.asyncInsts = nil -} - -func (m *meterImpl) NewSyncInstrument(desc sdkapi.Descriptor) (sdkapi.SyncImpl, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if meterPtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil { - return (*meterPtr).NewSyncInstrument(desc) - } - - inst := &syncImpl{ - instrument: instrument{ - descriptor: desc, - }, - } - m.syncInsts = append(m.syncInsts, inst) - return inst, nil -} - -// Synchronous delegation - -func (inst *syncImpl) setDelegate(d sdkapi.MeterImpl) { - implPtr := new(sdkapi.SyncImpl) - - var err error - *implPtr, err = d.NewSyncInstrument(inst.descriptor) - - if err != nil { - // TODO: There is no standard way to deliver this error to the user. - // See https://github.com/open-telemetry/opentelemetry-go/issues/514 - // Note that the default SDK will not generate any errors yet, this is - // only for added safety. - panic(err) - } - - atomic.StorePointer(&inst.delegate, unsafe.Pointer(implPtr)) -} - -func (inst *syncImpl) Implementation() interface{} { - if implPtr := (*sdkapi.SyncImpl)(atomic.LoadPointer(&inst.delegate)); implPtr != nil { - return (*implPtr).Implementation() - } - return inst -} - -// Async delegation - -func (m *meterImpl) NewAsyncInstrument( - desc sdkapi.Descriptor, - runner sdkapi.AsyncRunner, -) (sdkapi.AsyncImpl, error) { - - m.lock.Lock() - defer m.lock.Unlock() - - if meterPtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil { - return (*meterPtr).NewAsyncInstrument(desc, runner) - } - - inst := &asyncImpl{ - instrument: instrument{ - descriptor: desc, - }, - runner: runner, - } - m.asyncInsts = append(m.asyncInsts, inst) - return inst, nil -} - -func (obs *asyncImpl) Implementation() interface{} { - if implPtr := (*sdkapi.AsyncImpl)(atomic.LoadPointer(&obs.delegate)); implPtr != nil { - return (*implPtr).Implementation() - } - return obs -} - -func (obs *asyncImpl) setDelegate(d sdkapi.MeterImpl) { - implPtr := new(sdkapi.AsyncImpl) - - var err error - *implPtr, err = d.NewAsyncInstrument(obs.descriptor, obs.runner) - - if err != nil { - // TODO: There is no standard way to deliver this error to the user. - // See https://github.com/open-telemetry/opentelemetry-go/issues/514 - // Note that the default SDK will not generate any errors yet, this is - // only for added safety. - panic(err) - } - - atomic.StorePointer(&obs.delegate, unsafe.Pointer(implPtr)) -} - -// Metric updates - -func (m *meterImpl) RecordBatch(ctx context.Context, labels []attribute.KeyValue, measurements ...sdkapi.Measurement) { - if delegatePtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); delegatePtr != nil { - (*delegatePtr).RecordBatch(ctx, labels, measurements...) - } -} - -func (inst *syncImpl) RecordOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) { - if instPtr := (*sdkapi.SyncImpl)(atomic.LoadPointer(&inst.delegate)); instPtr != nil { - (*instPtr).RecordOne(ctx, number, labels) - } -} - -func AtomicFieldOffsets() map[string]uintptr { - return map[string]uintptr{ - "meterProvider.delegate": unsafe.Offsetof(meterProvider{}.delegate), - "meterImpl.delegate": unsafe.Offsetof(meterImpl{}.delegate), - "syncImpl.delegate": unsafe.Offsetof(syncImpl{}.delegate), - "asyncImpl.delegate": unsafe.Offsetof(asyncImpl{}.delegate), - } -} diff --git a/internal/metric/global/meter_test.go b/internal/metric/global/meter_test.go deleted file mode 100644 index 2062ecf8b07..00000000000 --- a/internal/metric/global/meter_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// 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 global_test - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/internal/metric/global" - "go.opentelemetry.io/otel/metric" - metricglobal "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -var Must = metric.Must - -var asInt = number.NewInt64Number -var asFloat = number.NewFloat64Number - -func TestDirect(t *testing.T) { - global.ResetForTest() - - ctx := context.Background() - meter1 := metricglobal.Meter("test1", metric.WithInstrumentationVersion("semver:v1.0.0")) - meter2 := metricglobal.Meter("test2", metric.WithSchemaURL("hello")) - - library1 := metrictest.Library{ - InstrumentationName: "test1", - InstrumentationVersion: "semver:v1.0.0", - } - library2 := metrictest.Library{ - InstrumentationName: "test2", - SchemaURL: "hello", - } - - labels1 := []attribute.KeyValue{attribute.String("A", "B")} - labels2 := []attribute.KeyValue{attribute.String("C", "D")} - labels3 := []attribute.KeyValue{attribute.String("E", "F")} - - counter := Must(meter1).NewInt64Counter("test.counter") - counter.Add(ctx, 1, labels1...) - counter.Add(ctx, 1, labels1...) - - histogram := Must(meter1).NewFloat64Histogram("test.histogram") - histogram.Record(ctx, 1, labels1...) - histogram.Record(ctx, 2, labels1...) - - _ = Must(meter1).NewFloat64GaugeObserver("test.gauge.float", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(1., labels1...) - result.Observe(2., labels2...) - }) - - _ = Must(meter1).NewInt64GaugeObserver("test.gauge.int", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(1, labels1...) - result.Observe(2, labels2...) - }) - - second := Must(meter2).NewFloat64Histogram("test.second") - second.Record(ctx, 1, labels3...) - second.Record(ctx, 2, labels3...) - - provider := metrictest.NewMeterProvider() - metricglobal.SetMeterProvider(provider) - - counter.Add(ctx, 1, labels1...) - histogram.Record(ctx, 3, labels1...) - second.Record(ctx, 3, labels3...) - - provider.RunAsyncInstruments() - - measurements := metrictest.AsStructs(provider.MeasurementBatches) - - require.EqualValues(t, - []metrictest.Measured{ - { - Name: "test.counter", - Library: library1, - Labels: metrictest.LabelsToMap(labels1...), - Number: asInt(1), - }, - { - Name: "test.histogram", - Library: library1, - Labels: metrictest.LabelsToMap(labels1...), - Number: asFloat(3), - }, - { - Name: "test.second", - Library: library2, - Labels: metrictest.LabelsToMap(labels3...), - Number: asFloat(3), - }, - { - Name: "test.gauge.float", - Library: library1, - Labels: metrictest.LabelsToMap(labels1...), - Number: asFloat(1), - }, - { - Name: "test.gauge.float", - Library: library1, - Labels: metrictest.LabelsToMap(labels2...), - Number: asFloat(2), - }, - { - Name: "test.gauge.int", - Library: library1, - Labels: metrictest.LabelsToMap(labels1...), - Number: asInt(1), - }, - { - Name: "test.gauge.int", - Library: library1, - Labels: metrictest.LabelsToMap(labels2...), - Number: asInt(2), - }, - }, - measurements, - ) -} - -type meterProviderWithConstructorError struct { - metric.MeterProvider -} - -type meterWithConstructorError struct { - sdkapi.MeterImpl -} - -func (m *meterProviderWithConstructorError) Meter(iName string, opts ...metric.MeterOption) metric.Meter { - return metric.WrapMeterImpl(&meterWithConstructorError{m.MeterProvider.Meter(iName, opts...).MeterImpl()}) -} - -func (m *meterWithConstructorError) NewSyncInstrument(_ sdkapi.Descriptor) (sdkapi.SyncImpl, error) { - return sdkapi.NewNoopSyncInstrument(), errors.New("constructor error") -} - -func TestErrorInDeferredConstructor(t *testing.T) { - global.ResetForTest() - - ctx := context.Background() - meter := metricglobal.GetMeterProvider().Meter("builtin") - - c1 := Must(meter).NewInt64Counter("test") - c2 := Must(meter).NewInt64Counter("test") - - provider := metrictest.NewMeterProvider() - sdk := &meterProviderWithConstructorError{provider} - - require.Panics(t, func() { - metricglobal.SetMeterProvider(sdk) - }) - - c1.Add(ctx, 1) - c2.Add(ctx, 2) -} - -func TestImplementationIndirection(t *testing.T) { - global.ResetForTest() - - // Test that Implementation() does the proper indirection, i.e., - // returns the implementation interface not the global, after - // registered. - - meter1 := metricglobal.Meter("test1") - - // Sync: no SDK yet - counter := Must(meter1).NewInt64Counter("interface.counter") - - ival := counter.Measurement(1).SyncImpl().Implementation() - require.NotNil(t, ival) - - _, ok := ival.(*metrictest.Sync) - require.False(t, ok) - - // Async: no SDK yet - gauge := Must(meter1).NewFloat64GaugeObserver( - "interface.gauge", - func(_ context.Context, result metric.Float64ObserverResult) {}, - ) - - ival = gauge.AsyncImpl().Implementation() - require.NotNil(t, ival) - - _, ok = ival.(*metrictest.Async) - require.False(t, ok) - - // Register the SDK - provider := metrictest.NewMeterProvider() - metricglobal.SetMeterProvider(provider) - - // Repeat the above tests - - // Sync - ival = counter.Measurement(1).SyncImpl().Implementation() - require.NotNil(t, ival) - - _, ok = ival.(*metrictest.Sync) - require.True(t, ok) - - // Async - ival = gauge.AsyncImpl().Implementation() - require.NotNil(t, ival) - - _, ok = ival.(*metrictest.Async) - require.True(t, ok) -} - -func TestRecordBatchMock(t *testing.T) { - global.ResetForTest() - - meter := metricglobal.GetMeterProvider().Meter("builtin") - - counter := Must(meter).NewInt64Counter("test.counter") - - meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) - - provider := metrictest.NewMeterProvider() - metricglobal.SetMeterProvider(provider) - - meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) - - require.EqualValues(t, - []metrictest.Measured{ - { - Name: "test.counter", - Library: metrictest.Library{ - InstrumentationName: "builtin", - }, - Labels: metrictest.LabelsToMap(), - Number: asInt(1), - }, - }, - metrictest.AsStructs(provider.MeasurementBatches)) -} diff --git a/internal/metric/global/metric.go b/internal/metric/global/metric.go deleted file mode 100644 index 896c6dd1c30..00000000000 --- a/internal/metric/global/metric.go +++ /dev/null @@ -1,66 +0,0 @@ -// 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 global // import "go.opentelemetry.io/otel/internal/metric/global" - -import ( - "sync" - "sync/atomic" - - "go.opentelemetry.io/otel/metric" -) - -type meterProviderHolder struct { - mp metric.MeterProvider -} - -var ( - globalMeter = defaultMeterValue() - - delegateMeterOnce sync.Once -) - -// MeterProvider is the internal implementation for global.MeterProvider. -func MeterProvider() metric.MeterProvider { - return globalMeter.Load().(meterProviderHolder).mp -} - -// SetMeterProvider is the internal implementation for global.SetMeterProvider. -func SetMeterProvider(mp metric.MeterProvider) { - delegateMeterOnce.Do(func() { - current := MeterProvider() - - if current == mp { - // Setting the provider to the prior default is nonsense, panic. - // Panic is acceptable because we are likely still early in the - // process lifetime. - panic("invalid MeterProvider, the global instance cannot be reinstalled") - } else if def, ok := current.(*meterProvider); ok { - def.setDelegate(mp) - } - }) - globalMeter.Store(meterProviderHolder{mp: mp}) -} - -func defaultMeterValue() *atomic.Value { - v := &atomic.Value{} - v.Store(meterProviderHolder{mp: newMeterProvider()}) - return v -} - -// ResetForTest restores the initial global state, for testing purposes. -func ResetForTest() { - globalMeter = defaultMeterValue() - delegateMeterOnce = sync.Once{} -} diff --git a/internal/metric/global/registry_test.go b/internal/metric/global/registry_test.go deleted file mode 100644 index 0d92c044ef1..00000000000 --- a/internal/metric/global/registry_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// 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 global - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/internal/metric/registry" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -type ( - newFunc func(name, libraryName string) (sdkapi.InstrumentImpl, error) -) - -var ( - allNew = map[string]newFunc{ - "counter.int64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewInt64Counter(name)) - }, - "counter.float64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewFloat64Counter(name)) - }, - "histogram.int64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewInt64Histogram(name)) - }, - "histogram.float64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewFloat64Histogram(name)) - }, - "gauge.int64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewInt64GaugeObserver(name, func(context.Context, metric.Int64ObserverResult) {})) - }, - "gauge.float64": func(name, libraryName string) (sdkapi.InstrumentImpl, error) { - return unwrap(MeterProvider().Meter(libraryName).NewFloat64GaugeObserver(name, func(context.Context, metric.Float64ObserverResult) {})) - }, - } -) - -func unwrap(impl interface{}, err error) (sdkapi.InstrumentImpl, error) { - if impl == nil { - return nil, err - } - if s, ok := impl.(interface { - SyncImpl() sdkapi.SyncImpl - }); ok { - return s.SyncImpl(), err - } - if a, ok := impl.(interface { - AsyncImpl() sdkapi.AsyncImpl - }); ok { - return a.AsyncImpl(), err - } - return nil, err -} - -func TestRegistrySameInstruments(t *testing.T) { - for _, nf := range allNew { - ResetForTest() - inst1, err1 := nf("this", "meter") - inst2, err2 := nf("this", "meter") - - require.NoError(t, err1) - require.NoError(t, err2) - require.Equal(t, inst1, inst2) - - SetMeterProvider(metrictest.NewMeterProvider()) - - require.Equal(t, inst1, inst2) - } -} - -func TestRegistryDifferentNamespace(t *testing.T) { - for _, nf := range allNew { - ResetForTest() - inst1, err1 := nf("this", "meter1") - inst2, err2 := nf("this", "meter2") - - require.NoError(t, err1) - require.NoError(t, err2) - - if inst1.Descriptor().InstrumentKind().Synchronous() { - // They're equal because of a `nil` pointer at this point. - // (Only for synchronous instruments, which lack callacks.) - require.EqualValues(t, inst1, inst2) - } - - SetMeterProvider(metrictest.NewMeterProvider()) - - // They're different after the deferred setup. - require.NotEqual(t, inst1, inst2) - } -} - -func TestRegistryDiffInstruments(t *testing.T) { - for origName, origf := range allNew { - ResetForTest() - - _, err := origf("this", "super") - require.NoError(t, err) - - for newName, nf := range allNew { - if newName == origName { - continue - } - - other, err := nf("this", "super") - require.Error(t, err) - require.NotNil(t, other) - require.True(t, errors.Is(err, registry.ErrMetricKindMismatch)) - require.Contains(t, err.Error(), "by this name with another kind or number type") - } - } -} diff --git a/internal/metric/global/state_test.go b/internal/metric/global/state_test.go deleted file mode 100644 index 526eac7fa69..00000000000 --- a/internal/metric/global/state_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// 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 global_test - -import ( - "testing" - - internalglobal "go.opentelemetry.io/otel/internal/metric/global" - metricglobal "go.opentelemetry.io/otel/metric/global" -) - -func TestResetsOfGlobalsPanic(t *testing.T) { - internalglobal.ResetForTest() - tests := map[string]func(){ - "SetMeterProvider": func() { - metricglobal.SetMeterProvider(metricglobal.GetMeterProvider()) - }, - } - - for name, test := range tests { - shouldPanic(t, name, test) - } -} - -func shouldPanic(t *testing.T, name string, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("calling %s with default global did not panic", name) - } - }() - - f() -} diff --git a/internal/metric/go.mod b/internal/metric/go.mod deleted file mode 100644 index 7e92e2dcb10..00000000000 --- a/internal/metric/go.mod +++ /dev/null @@ -1,73 +0,0 @@ -module go.opentelemetry.io/otel/internal/metric - -go 1.16 - -require ( - github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v1.4.1 - go.opentelemetry.io/otel/metric v0.27.0 -) - -replace go.opentelemetry.io/otel => ../.. - -replace go.opentelemetry.io/otel/metric => ../../metric - -replace go.opentelemetry.io/otel/internal/metric => ./ - -replace go.opentelemetry.io/otel/bridge/opencensus => ../../bridge/opencensus - -replace go.opentelemetry.io/otel/bridge/opentracing => ../../bridge/opentracing - -replace go.opentelemetry.io/otel/example/jaeger => ../../example/jaeger - -replace go.opentelemetry.io/otel/example/namedtracer => ../../example/namedtracer - -replace go.opentelemetry.io/otel/example/opencensus => ../../example/opencensus - -replace go.opentelemetry.io/otel/example/otel-collector => ../../example/otel-collector - -replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough - -replace go.opentelemetry.io/otel/example/prometheus => ../../example/prometheus - -replace go.opentelemetry.io/otel/example/zipkin => ../../example/zipkin - -replace go.opentelemetry.io/otel/exporters/prometheus => ../../exporters/prometheus - -replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace - -replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc - -replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../../exporters/otlp/otlptrace/otlptracehttp - -replace go.opentelemetry.io/otel/exporters/jaeger => ../../exporters/jaeger - -replace go.opentelemetry.io/otel/exporters/zipkin => ../../exporters/zipkin - -replace go.opentelemetry.io/otel/internal/tools => ../tools - -replace go.opentelemetry.io/otel/sdk => ../../sdk - -replace go.opentelemetry.io/otel/sdk/export/metric => ../../sdk/export/metric - -replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric - -replace go.opentelemetry.io/otel/trace => ../../trace - -replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../../exporters/otlp/otlpmetric - -replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../exporters/otlp/otlpmetric/otlpmetricgrpc - -replace go.opentelemetry.io/otel/exporters/stdout/stdoutmetric => ../../exporters/stdout/stdoutmetric - -replace go.opentelemetry.io/otel/exporters/stdout/stdouttrace => ../../exporters/stdout/stdouttrace - -replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp => ../../exporters/otlp/otlpmetric/otlpmetrichttp - -replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencensus/test - -replace go.opentelemetry.io/otel/example/fib => ../../example/fib - -replace go.opentelemetry.io/otel/schema => ../../schema - -replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/internal/metric/go.sum b/internal/metric/go.sum deleted file mode 100644 index 4f1776cd92b..00000000000 --- a/internal/metric/go.sum +++ /dev/null @@ -1,18 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/metric/config.go b/metric/config.go index 3f722344fa7..621e4c5fcb8 100644 --- a/metric/config.go +++ b/metric/config.go @@ -14,65 +14,6 @@ package metric // import "go.opentelemetry.io/otel/metric" -import ( - "go.opentelemetry.io/otel/metric/unit" -) - -// InstrumentConfig contains options for metric instrument descriptors. -type InstrumentConfig struct { - description string - unit unit.Unit -} - -// Description describes the instrument in human-readable terms. -func (cfg *InstrumentConfig) Description() string { - return cfg.description -} - -// Unit describes the measurement unit for a instrument. -func (cfg *InstrumentConfig) Unit() unit.Unit { - return cfg.unit -} - -// InstrumentOption is an interface for applying metric instrument options. -type InstrumentOption interface { - // ApplyMeter is used to set a InstrumentOption value of a - // InstrumentConfig. - applyInstrument(InstrumentConfig) InstrumentConfig -} - -// NewInstrumentConfig creates a new InstrumentConfig -// and applies all the given options. -func NewInstrumentConfig(opts ...InstrumentOption) InstrumentConfig { - var config InstrumentConfig - for _, o := range opts { - config = o.applyInstrument(config) - } - return config -} - -type instrumentOptionFunc func(InstrumentConfig) InstrumentConfig - -func (fn instrumentOptionFunc) applyInstrument(cfg InstrumentConfig) InstrumentConfig { - return fn(cfg) -} - -// WithDescription applies provided description. -func WithDescription(desc string) InstrumentOption { - return instrumentOptionFunc(func(cfg InstrumentConfig) InstrumentConfig { - cfg.description = desc - return cfg - }) -} - -// WithUnit applies provided unit. -func WithUnit(unit unit.Unit) InstrumentOption { - return instrumentOptionFunc(func(cfg InstrumentConfig) InstrumentConfig { - cfg.unit = unit - return cfg - }) -} - // MeterConfig contains options for Meters. type MeterConfig struct { instrumentationVersion string @@ -80,18 +21,18 @@ type MeterConfig struct { } // InstrumentationVersion is the version of the library providing instrumentation. -func (cfg *MeterConfig) InstrumentationVersion() string { +func (cfg MeterConfig) InstrumentationVersion() string { return cfg.instrumentationVersion } // SchemaURL is the schema_url of the library providing instrumentation. -func (cfg *MeterConfig) SchemaURL() string { +func (cfg MeterConfig) SchemaURL() string { return cfg.schemaURL } // MeterOption is an interface for applying Meter options. type MeterOption interface { - // ApplyMeter is used to set a MeterOption value of a MeterConfig. + // applyMeter is used to set a MeterOption value of a MeterConfig. applyMeter(MeterConfig) MeterConfig } diff --git a/metric/doc.go b/metric/doc.go index 4baf0719fcc..bd6f4343720 100644 --- a/metric/doc.go +++ b/metric/doc.go @@ -19,49 +19,5 @@ OpenTelemetry API. This package is currently in a pre-GA phase. Backwards incompatible changes may be introduced in subsequent minor version releases as we work to track the evolving OpenTelemetry specification and user feedback. - -Measurements can be made about an operation being performed or the state of a -system in general. These measurements can be crucial to the reliable operation -of code and provide valuable insights about the inner workings of a system. - -Measurements are made using instruments provided by this package. The type of -instrument used will depend on the type of measurement being made and of what -part of a system is being measured. - -Instruments are categorized as Synchronous or Asynchronous and independently -as Adding or Grouping. Synchronous instruments are called by the user with a -Context. Asynchronous instruments are called by the SDK during collection. -Adding instruments are semantically intended for capturing a sum. Grouping -instruments are intended for capturing a distribution. - -Adding instruments may be monotonic, in which case they are non-decreasing -and naturally define a rate. - -The synchronous instrument names are: - - Counter: adding, monotonic - UpDownCounter: adding - Histogram: grouping - -and the asynchronous instruments are: - - CounterObserver: adding, monotonic - UpDownCounterObserver: adding - GaugeObserver: grouping - -All instruments are provided with support for either float64 or int64 input -values. - -An instrument is created using a Meter. Additionally, a Meter is used to -record batches of synchronous measurements or asynchronous observations. A -Meter is obtained using a MeterProvider. A Meter, like a Tracer, is unique to -the instrumentation it instruments and must be named and versioned when -created with a MeterProvider with the name and version of the instrumentation -library. - -Instrumentation should be designed to accept a MeterProvider from which it can -create its own unique Meter. Alternatively, the registered global -MeterProvider from the go.opentelemetry.io/otel package can be used as a -default. */ package metric // import "go.opentelemetry.io/otel/metric" diff --git a/metric/example_test.go b/metric/example_test.go new file mode 100644 index 00000000000..92e0e2cd958 --- /dev/null +++ b/metric/example_test.go @@ -0,0 +1,116 @@ +// 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 metric_test + +import ( + "context" + "fmt" + "runtime" + "time" + + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/nonrecording" + "go.opentelemetry.io/otel/metric/unit" +) + +//nolint:govet // Meter doesn't register for go vet +func ExampleMeter_synchronous() { + // In a library or program this would be provided by otel.GetMeterProvider(). + meterProvider := nonrecording.NewNoopMeterProvider() + + workDuration, err := meterProvider.Meter("go.opentelemetry.io/otel/metric#SyncExample").SyncInt64().Histogram( + "workDuration", + instrument.WithUnit(unit.Milliseconds)) + if err != nil { + fmt.Println("Failed to register instrument") + panic(err) + } + + startTime := time.Now() + ctx := context.Background() + // Do work + // ... + workDuration.Record(ctx, time.Since(startTime).Milliseconds()) + +} + +//nolint:govet // Meter doesn't register for go vet +func ExampleMeter_asynchronous_single() { + // In a library or program this would be provided by otel.GetMeterProvider(). + meterProvider := nonrecording.NewNoopMeterProvider() + meter := meterProvider.Meter("go.opentelemetry.io/otel/metric#AsyncExample") + + memoryUsage, err := meter.AsyncInt64().Gauge( + "MemoryUsage", + instrument.WithUnit(unit.Bytes), + ) + if err != nil { + fmt.Println("Failed to register instrument") + panic(err) + } + + err = meter.RegisterCallback([]instrument.Asynchronous{memoryUsage}, + func(ctx context.Context) { + // instrument.WithCallbackFunc(func(ctx context.Context) { + //Do Work to get the real memoryUsage + // mem := GatherMemory(ctx) + mem := 75000 + + memoryUsage.Observe(ctx, int64(mem)) + }) + if err != nil { + fmt.Println("Failed to register callback") + panic(err) + } +} + +//nolint:govet // Meter doesn't register for go vet +func ExampleMeter_asynchronous_multiple() { + meterProvider := nonrecording.NewNoopMeterProvider() + meter := meterProvider.Meter("go.opentelemetry.io/otel/metric#MultiAsyncExample") + + // This is just a sample of memory stats to record from the Memstats + heapAlloc, _ := meter.AsyncInt64().UpDownCounter("heapAllocs") + gcCount, _ := meter.AsyncInt64().Counter("gcCount") + gcPause, _ := meter.SyncFloat64().Histogram("gcPause") + + err := meter.RegisterCallback([]instrument.Asynchronous{ + heapAlloc, + gcCount, + }, + func(ctx context.Context) { + memStats := &runtime.MemStats{} + // This call does work + runtime.ReadMemStats(memStats) + + heapAlloc.Observe(ctx, int64(memStats.HeapAlloc)) + gcCount.Observe(ctx, int64(memStats.NumGC)) + + // This function synchronously records the pauses + computeGCPauses(ctx, gcPause, memStats.PauseNs[:]) + }, + ) + + if err != nil { + fmt.Println("Failed to register callback") + panic(err) + } +} + +//This is just an example, see the the contrib runtime instrumentation for real implementation +func computeGCPauses(ctx context.Context, recorder syncfloat64.Histogram, pauseBuff []uint64) { + +} diff --git a/metric/global/metric.go b/metric/global/metric.go deleted file mode 100644 index 14ba862002a..00000000000 --- a/metric/global/metric.go +++ /dev/null @@ -1,49 +0,0 @@ -// 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 global // import "go.opentelemetry.io/otel/metric/global" - -import ( - "go.opentelemetry.io/otel/internal/metric/global" - "go.opentelemetry.io/otel/metric" -) - -// Meter creates an implementation of the Meter interface from the global -// MeterProvider. The instrumentationName must be the name of the library -// providing instrumentation. This name may be the same as the instrumented -// code only if that code provides built-in instrumentation. If the -// instrumentationName is empty, then a implementation defined default name -// will be used instead. -// -// This is short for MeterProvider().Meter(name) -func Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter { - return GetMeterProvider().Meter(instrumentationName, opts...) -} - -// GetMeterProvider returns the registered global meter provider. If -// none is registered then a default meter provider is returned that -// forwards the Meter interface to the first registered Meter. -// -// Use the meter provider to create a named meter. E.g. -// meter := global.MeterProvider().Meter("example.com/foo") -// or -// meter := global.Meter("example.com/foo") -func GetMeterProvider() metric.MeterProvider { - return global.MeterProvider() -} - -// SetMeterProvider registers `mp` as the global meter provider. -func SetMeterProvider(mp metric.MeterProvider) { - global.SetMeterProvider(mp) -} diff --git a/metric/global/metric_test.go b/metric/global/metric_test.go deleted file mode 100644 index dc4d9370a92..00000000000 --- a/metric/global/metric_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// 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 global - -import ( - "testing" - - "go.opentelemetry.io/otel/metric" -) - -type testMeterProvider struct{} - -var _ metric.MeterProvider = &testMeterProvider{} - -func (*testMeterProvider) Meter(_ string, _ ...metric.MeterOption) metric.Meter { - return metric.Meter{} -} - -func TestMultipleGlobalMeterProvider(t *testing.T) { - p1 := testMeterProvider{} - p2 := metric.NewNoopMeterProvider() - SetMeterProvider(&p1) - SetMeterProvider(p2) - - got := GetMeterProvider() - want := p2 - if got != want { - t.Fatalf("MeterProvider: got %p, want %p\n", got, want) - } -} diff --git a/metric/go.mod b/metric/go.mod index 17780384f4c..7d510be1f32 100644 --- a/metric/go.mod +++ b/metric/go.mod @@ -2,6 +2,8 @@ module go.opentelemetry.io/otel/metric go 1.16 +require go.opentelemetry.io/otel v1.4.1 + replace go.opentelemetry.io/otel => ../ replace go.opentelemetry.io/otel/bridge/opencensus => ../bridge/opencensus @@ -38,13 +40,6 @@ replace go.opentelemetry.io/otel/sdk/metric => ../sdk/metric replace go.opentelemetry.io/otel/trace => ../trace -require ( - github.com/google/go-cmp v0.5.7 - github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v1.4.1 - go.opentelemetry.io/otel/internal/metric v0.27.0 -) - replace go.opentelemetry.io/otel/example/passthrough => ../example/passthrough replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../exporters/otlp/otlptrace @@ -53,8 +48,6 @@ replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../ex replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../exporters/otlp/otlptrace/otlptracehttp -replace go.opentelemetry.io/otel/internal/metric => ../internal/metric - replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../exporters/otlp/otlpmetric replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../exporters/otlp/otlpmetric/otlpmetricgrpc diff --git a/metric/go.sum b/metric/go.sum index 531cae722ed..5457c7626c5 100644 --- a/metric/go.sum +++ b/metric/go.sum @@ -1,8 +1,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -11,9 +9,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/metric/instrument/asyncfloat64/asyncfloat64.go b/metric/instrument/asyncfloat64/asyncfloat64.go new file mode 100644 index 00000000000..91c034fb2a0 --- /dev/null +++ b/metric/instrument/asyncfloat64/asyncfloat64.go @@ -0,0 +1,70 @@ +// 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 asyncfloat64 // import "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" +) + +// InstrumentProvider provides access to individual instruments. +type InstrumentProvider interface { + // Counter creates an instrument for recording increasing values. + Counter(name string, opts ...instrument.Option) (Counter, error) + + // UpDownCounter creates an instrument for recording changes of a value. + UpDownCounter(name string, opts ...instrument.Option) (UpDownCounter, error) + + // Gauge creates an instrument for recording the current value. + Gauge(name string, opts ...instrument.Option) (Gauge, error) +} + +// Counter is an instrument that records increasing values. +type Counter interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x float64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} + +// UpDownCounter is an instrument that records increasing or decresing values. +type UpDownCounter interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x float64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} + +// Gauge is an instrument that records independent readings. +type Gauge interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x float64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} diff --git a/metric/instrument/asyncint64/asyncint64.go b/metric/instrument/asyncint64/asyncint64.go new file mode 100644 index 00000000000..9dfba553d78 --- /dev/null +++ b/metric/instrument/asyncint64/asyncint64.go @@ -0,0 +1,70 @@ +// 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 asyncint64 // import "go.opentelemetry.io/otel/metric/instrument/asyncint64" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" +) + +// InstrumentProvider provides access to individual instruments. +type InstrumentProvider interface { + // Counter creates an instrument for recording increasing values. + Counter(name string, opts ...instrument.Option) (Counter, error) + + // UpDownCounter creates an instrument for recording changes of a value. + UpDownCounter(name string, opts ...instrument.Option) (UpDownCounter, error) + + // Gauge creates an instrument for recording the current value. + Gauge(name string, opts ...instrument.Option) (Gauge, error) +} + +// Counter is an instrument that records increasing values. +type Counter interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x int64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} + +// UpDownCounter is an instrument that records increasing or decresing values. +type UpDownCounter interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x int64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} + +// Gauge is an instrument that records independent readings. +type Gauge interface { + // Observe records the state of the instrument. + // + // It is only valid to call this within a callback. If called outside of the + // registered callback it should have no effect on the instrument, and an + // error will be reported via the error handler. + Observe(ctx context.Context, x int64, attrs ...attribute.KeyValue) + + instrument.Asynchronous +} diff --git a/metric/instrument/config.go b/metric/instrument/config.go new file mode 100644 index 00000000000..d6ea25a8da2 --- /dev/null +++ b/metric/instrument/config.go @@ -0,0 +1,69 @@ +// 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 "go.opentelemetry.io/otel/metric/unit" + +// Config contains options for metric instrument descriptors. +type Config struct { + description string + unit unit.Unit +} + +// Description describes the instrument in human-readable terms. +func (cfg Config) Description() string { + return cfg.description +} + +// Unit describes the measurement unit for a instrument. +func (cfg Config) Unit() unit.Unit { + return cfg.unit +} + +// Option is an interface for applying metric instrument options. +type Option interface { + applyInstrument(Config) Config +} + +// NewConfig creates a new Config and applies all the given options. +func NewConfig(opts ...Option) Config { + var config Config + for _, o := range opts { + config = o.applyInstrument(config) + } + return config +} + +type optionFunc func(Config) Config + +func (fn optionFunc) applyInstrument(cfg Config) Config { + return fn(cfg) +} + +// WithDescription applies provided description. +func WithDescription(desc string) Option { + return optionFunc(func(cfg Config) Config { + cfg.description = desc + return cfg + }) +} + +// WithUnit applies provided unit. +func WithUnit(unit unit.Unit) Option { + return optionFunc(func(cfg Config) Config { + cfg.unit = unit + return cfg + }) +} diff --git a/metric/noop_test.go b/metric/instrument/instrument.go similarity index 54% rename from metric/noop_test.go rename to metric/instrument/instrument.go index e5a7528a217..e1bbb850d76 100644 --- a/metric/noop_test.go +++ b/metric/instrument/instrument.go @@ -12,23 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package metric +package instrument // import "go.opentelemetry.io/otel/metric/instrument" -import ( - "testing" -) - -func TestNewNoopMeterProvider(t *testing.T) { - got, want := NewNoopMeterProvider(), noopMeterProvider{} - if got != want { - t.Errorf("NewNoopMeterProvider() returned %#v, want %#v", got, want) - } +// Asynchronous instruments are instruments that are updated within a Callback. +// If an instrument is observed outside of it's callback it should be an error. +// +// This interface is used as a grouping mechanism. +type Asynchronous interface { + asynchronous() } -func TestNoopMeterProviderMeter(t *testing.T) { - mp := NewNoopMeterProvider() - got, want := mp.Meter(""), Meter{} - if got != want { - t.Errorf("noopMeterProvider.Meter() returned %#v, want %#v", got, want) - } +// Synchronous instruments are updated in line with application code. +// +// This interface is used as a grouping mechanism. +type Synchronous interface { + synchronous() } diff --git a/metric/instrument/syncfloat64/syncfloat64.go b/metric/instrument/syncfloat64/syncfloat64.go new file mode 100644 index 00000000000..1989292ecf8 --- /dev/null +++ b/metric/instrument/syncfloat64/syncfloat64.go @@ -0,0 +1,56 @@ +// 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 syncfloat64 // import "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" +) + +// InstrumentProvider provides access to individual instruments. +type InstrumentProvider interface { + // Counter creates an instrument for recording increasing values. + Counter(name string, opts ...instrument.Option) (Counter, error) + // UpDownCounter creates an instrument for recording changes of a value. + UpDownCounter(name string, opts ...instrument.Option) (UpDownCounter, error) + // Histogram creates an instrument for recording a distribution of values. + Histogram(name string, opts ...instrument.Option) (Histogram, error) +} + +// Counter is an instrument that records increasing values. +type Counter interface { + // Add records a change to the counter. + Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} + +// UpDownCounter is an instrument that records increasing or decresing values. +type UpDownCounter interface { + // Add records a change to the counter. + Add(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} + +// Histogram is an instrument that records a distribution of values. +type Histogram interface { + // Record adds an additional value to the distribution. + Record(ctx context.Context, incr float64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} diff --git a/metric/instrument/syncint64/syncint64.go b/metric/instrument/syncint64/syncint64.go new file mode 100644 index 00000000000..ee882bcd922 --- /dev/null +++ b/metric/instrument/syncint64/syncint64.go @@ -0,0 +1,56 @@ +// 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 syncint64 // import "go.opentelemetry.io/otel/metric/instrument/syncint64" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" +) + +// InstrumentProvider provides access to individual instruments. +type InstrumentProvider interface { + // Counter creates an instrument for recording increasing values. + Counter(name string, opts ...instrument.Option) (Counter, error) + // UpDownCounter creates an instrument for recording changes of a value. + UpDownCounter(name string, opts ...instrument.Option) (UpDownCounter, error) + // Histogram creates an instrument for recording a distribution of values. + Histogram(name string, opts ...instrument.Option) (Histogram, error) +} + +// Counter is an instrument that records increasing values. +type Counter interface { + // Add records a change to the counter. + Add(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} + +// UpDownCounter is an instrument that records increasing or decresing values. +type UpDownCounter interface { + // Add records a change to the counter. + Add(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} + +// Histogram is an instrument that records a distribution of values. +type Histogram interface { + // Record adds an additional value to the distribution. + Record(ctx context.Context, incr int64, attrs ...attribute.KeyValue) + + instrument.Synchronous +} diff --git a/metric/meter.go b/metric/meter.go new file mode 100644 index 00000000000..21fc1c499fb --- /dev/null +++ b/metric/meter.go @@ -0,0 +1,60 @@ +// 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "context" + + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" +) + +// MeterProvider provides access to named Meter instances, for instrumenting +// an application or library. +type MeterProvider interface { + // Meter creates an instance of a `Meter` interface. The instrumentationName + // must be the name of the library providing instrumentation. This name may + // be the same as the instrumented code only if that code provides built-in + // instrumentation. If the instrumentationName is empty, then a + // implementation defined default name will be used instead. + Meter(instrumentationName string, opts ...MeterOption) Meter +} + +// Meter provides access to instrument instances for recording metrics. +type Meter interface { + // AsyncInt64 is the namespace for the Asynchronous Integer instruments. + // + // To Observe data with instruments it must be registered in a callback. + AsyncInt64() asyncint64.InstrumentProvider + + // AsyncFloat64 is the namespace for the Asynchronous Float instruments + // + // To Observe data with instruments it must be registered in a callback. + AsyncFloat64() asyncfloat64.InstrumentProvider + + // RegisterCallback captures the function that will be called during Collect. + // + // It is only valid to call Observe within the scope of the passed function, + // and only on the instruments that were registered with this call. + RegisterCallback(insts []instrument.Asynchronous, function func(context.Context)) error + + // SyncInt64 is the namespace for the Synchronous Integer instruments + SyncInt64() syncint64.InstrumentProvider + // SyncFloat64 is the namespace for the Synchronous Float instruments + SyncFloat64() syncfloat64.InstrumentProvider +} diff --git a/metric/metric.go b/metric/metric.go deleted file mode 100644 index d8c5a6b3f35..00000000000 --- a/metric/metric.go +++ /dev/null @@ -1,538 +0,0 @@ -// 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 metric // import "go.opentelemetry.io/otel/metric" - -import ( - "context" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -// MeterProvider supports named Meter instances. -type MeterProvider interface { - // Meter creates an implementation of the Meter interface. - // The instrumentationName must be the name of the library providing - // instrumentation. This name may be the same as the instrumented code - // only if that code provides built-in instrumentation. If the - // instrumentationName is empty, then a implementation defined default - // name will be used instead. - Meter(instrumentationName string, opts ...MeterOption) Meter -} - -// Meter is the creator of metric instruments. -// -// An uninitialized Meter is a no-op implementation. -type Meter struct { - impl sdkapi.MeterImpl -} - -// WrapMeterImpl constructs a `Meter` implementation from a -// `MeterImpl` implementation. -func WrapMeterImpl(impl sdkapi.MeterImpl) Meter { - return Meter{ - impl: impl, - } -} - -// Measurement is used for reporting a synchronous batch of metric -// values. Instances of this type should be created by synchronous -// instruments (e.g., Int64Counter.Measurement()). -// -// Note: This is an alias because it is a first-class member of the -// API but is also part of the lower-level sdkapi interface. -type Measurement = sdkapi.Measurement - -// Observation is used for reporting an asynchronous batch of metric -// values. Instances of this type should be created by asynchronous -// instruments (e.g., Int64GaugeObserver.Observation()). -// -// Note: This is an alias because it is a first-class member of the -// API but is also part of the lower-level sdkapi interface. -type Observation = sdkapi.Observation - -// RecordBatch atomically records a batch of measurements. -func (m Meter) RecordBatch(ctx context.Context, ls []attribute.KeyValue, ms ...Measurement) { - if m.impl == nil { - return - } - m.impl.RecordBatch(ctx, ls, ms...) -} - -// NewBatchObserver creates a new BatchObserver that supports -// making batches of observations for multiple instruments. -func (m Meter) NewBatchObserver(callback BatchObserverFunc) BatchObserver { - return BatchObserver{ - meter: m, - runner: newBatchAsyncRunner(callback), - } -} - -// NewInt64Counter creates a new integer Counter instrument with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewInt64Counter(name string, options ...InstrumentOption) (Int64Counter, error) { - return wrapInt64CounterInstrument( - m.newSync(name, sdkapi.CounterInstrumentKind, number.Int64Kind, options)) -} - -// NewFloat64Counter creates a new floating point Counter with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewFloat64Counter(name string, options ...InstrumentOption) (Float64Counter, error) { - return wrapFloat64CounterInstrument( - m.newSync(name, sdkapi.CounterInstrumentKind, number.Float64Kind, options)) -} - -// NewInt64UpDownCounter creates a new integer UpDownCounter instrument with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewInt64UpDownCounter(name string, options ...InstrumentOption) (Int64UpDownCounter, error) { - return wrapInt64UpDownCounterInstrument( - m.newSync(name, sdkapi.UpDownCounterInstrumentKind, number.Int64Kind, options)) -} - -// NewFloat64UpDownCounter creates a new floating point UpDownCounter with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewFloat64UpDownCounter(name string, options ...InstrumentOption) (Float64UpDownCounter, error) { - return wrapFloat64UpDownCounterInstrument( - m.newSync(name, sdkapi.UpDownCounterInstrumentKind, number.Float64Kind, options)) -} - -// NewInt64Histogram creates a new integer Histogram instrument with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewInt64Histogram(name string, opts ...InstrumentOption) (Int64Histogram, error) { - return wrapInt64HistogramInstrument( - m.newSync(name, sdkapi.HistogramInstrumentKind, number.Int64Kind, opts)) -} - -// NewFloat64Histogram creates a new floating point Histogram with the -// given name, customized with options. May return an error if the -// name is invalid (e.g., empty) or improperly registered (e.g., -// duplicate registration). -func (m Meter) NewFloat64Histogram(name string, opts ...InstrumentOption) (Float64Histogram, error) { - return wrapFloat64HistogramInstrument( - m.newSync(name, sdkapi.HistogramInstrumentKind, number.Float64Kind, opts)) -} - -// NewInt64GaugeObserver creates a new integer GaugeObserver instrument -// with the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewInt64GaugeObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64GaugeObserver, error) { - if callback == nil { - return wrapInt64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64GaugeObserverInstrument( - m.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, opts, - newInt64AsyncRunner(callback))) -} - -// NewFloat64GaugeObserver creates a new floating point GaugeObserver with -// the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewFloat64GaugeObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64GaugeObserver, error) { - if callback == nil { - return wrapFloat64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64GaugeObserverInstrument( - m.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, opts, - newFloat64AsyncRunner(callback))) -} - -// NewInt64CounterObserver creates a new integer CounterObserver instrument -// with the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewInt64CounterObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64CounterObserver, error) { - if callback == nil { - return wrapInt64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64CounterObserverInstrument( - m.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Int64Kind, opts, - newInt64AsyncRunner(callback))) -} - -// NewFloat64CounterObserver creates a new floating point CounterObserver with -// the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewFloat64CounterObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64CounterObserver, error) { - if callback == nil { - return wrapFloat64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64CounterObserverInstrument( - m.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Float64Kind, opts, - newFloat64AsyncRunner(callback))) -} - -// NewInt64UpDownCounterObserver creates a new integer UpDownCounterObserver instrument -// with the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewInt64UpDownCounterObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64UpDownCounterObserver, error) { - if callback == nil { - return wrapInt64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64UpDownCounterObserverInstrument( - m.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Int64Kind, opts, - newInt64AsyncRunner(callback))) -} - -// NewFloat64UpDownCounterObserver creates a new floating point UpDownCounterObserver with -// the given name, running a given callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (m Meter) NewFloat64UpDownCounterObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64UpDownCounterObserver, error) { - if callback == nil { - return wrapFloat64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64UpDownCounterObserverInstrument( - m.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Float64Kind, opts, - newFloat64AsyncRunner(callback))) -} - -// NewInt64GaugeObserver creates a new integer GaugeObserver instrument -// with the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewInt64GaugeObserver(name string, opts ...InstrumentOption) (Int64GaugeObserver, error) { - if b.runner == nil { - return wrapInt64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64GaugeObserverInstrument( - b.meter.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, opts, b.runner)) -} - -// NewFloat64GaugeObserver creates a new floating point GaugeObserver with -// the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewFloat64GaugeObserver(name string, opts ...InstrumentOption) (Float64GaugeObserver, error) { - if b.runner == nil { - return wrapFloat64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64GaugeObserverInstrument( - b.meter.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, opts, - b.runner)) -} - -// NewInt64CounterObserver creates a new integer CounterObserver instrument -// with the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewInt64CounterObserver(name string, opts ...InstrumentOption) (Int64CounterObserver, error) { - if b.runner == nil { - return wrapInt64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64CounterObserverInstrument( - b.meter.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Int64Kind, opts, b.runner)) -} - -// NewFloat64CounterObserver creates a new floating point CounterObserver with -// the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewFloat64CounterObserver(name string, opts ...InstrumentOption) (Float64CounterObserver, error) { - if b.runner == nil { - return wrapFloat64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64CounterObserverInstrument( - b.meter.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Float64Kind, opts, - b.runner)) -} - -// NewInt64UpDownCounterObserver creates a new integer UpDownCounterObserver instrument -// with the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewInt64UpDownCounterObserver(name string, opts ...InstrumentOption) (Int64UpDownCounterObserver, error) { - if b.runner == nil { - return wrapInt64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapInt64UpDownCounterObserverInstrument( - b.meter.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Int64Kind, opts, b.runner)) -} - -// NewFloat64UpDownCounterObserver creates a new floating point UpDownCounterObserver with -// the given name, running in a batch callback, and customized with -// options. May return an error if the name is invalid (e.g., empty) -// or improperly registered (e.g., duplicate registration). -func (b BatchObserver) NewFloat64UpDownCounterObserver(name string, opts ...InstrumentOption) (Float64UpDownCounterObserver, error) { - if b.runner == nil { - return wrapFloat64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil) - } - return wrapFloat64UpDownCounterObserverInstrument( - b.meter.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Float64Kind, opts, - b.runner)) -} - -// MeterImpl returns the underlying MeterImpl of this Meter. -func (m Meter) MeterImpl() sdkapi.MeterImpl { - return m.impl -} - -// newAsync constructs one new asynchronous instrument. -func (m Meter) newAsync( - name string, - mkind sdkapi.InstrumentKind, - nkind number.Kind, - opts []InstrumentOption, - runner sdkapi.AsyncRunner, -) ( - sdkapi.AsyncImpl, - error, -) { - if m.impl == nil { - return sdkapi.NewNoopAsyncInstrument(), nil - } - cfg := NewInstrumentConfig(opts...) - desc := sdkapi.NewDescriptor(name, mkind, nkind, cfg.description, cfg.unit) - return m.impl.NewAsyncInstrument(desc, runner) -} - -// newSync constructs one new synchronous instrument. -func (m Meter) newSync( - name string, - metricKind sdkapi.InstrumentKind, - numberKind number.Kind, - opts []InstrumentOption, -) ( - sdkapi.SyncImpl, - error, -) { - if m.impl == nil { - return sdkapi.NewNoopSyncInstrument(), nil - } - cfg := NewInstrumentConfig(opts...) - desc := sdkapi.NewDescriptor(name, metricKind, numberKind, cfg.description, cfg.unit) - return m.impl.NewSyncInstrument(desc) -} - -// MeterMust is a wrapper for Meter interfaces that panics when any -// instrument constructor encounters an error. -type MeterMust struct { - meter Meter -} - -// BatchObserverMust is a wrapper for BatchObserver that panics when -// any instrument constructor encounters an error. -type BatchObserverMust struct { - batch BatchObserver -} - -// Must constructs a MeterMust implementation from a Meter, allowing -// the application to panic when any instrument constructor yields an -// error. -func Must(meter Meter) MeterMust { - return MeterMust{meter: meter} -} - -// NewInt64Counter calls `Meter.NewInt64Counter` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64Counter(name string, cos ...InstrumentOption) Int64Counter { - if inst, err := mm.meter.NewInt64Counter(name, cos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64Counter calls `Meter.NewFloat64Counter` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64Counter(name string, cos ...InstrumentOption) Float64Counter { - if inst, err := mm.meter.NewFloat64Counter(name, cos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64UpDownCounter calls `Meter.NewInt64UpDownCounter` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64UpDownCounter(name string, cos ...InstrumentOption) Int64UpDownCounter { - if inst, err := mm.meter.NewInt64UpDownCounter(name, cos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64UpDownCounter calls `Meter.NewFloat64UpDownCounter` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64UpDownCounter(name string, cos ...InstrumentOption) Float64UpDownCounter { - if inst, err := mm.meter.NewFloat64UpDownCounter(name, cos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64Histogram calls `Meter.NewInt64Histogram` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64Histogram(name string, mos ...InstrumentOption) Int64Histogram { - if inst, err := mm.meter.NewInt64Histogram(name, mos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64Histogram calls `Meter.NewFloat64Histogram` and returns the -// instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64Histogram(name string, mos ...InstrumentOption) Float64Histogram { - if inst, err := mm.meter.NewFloat64Histogram(name, mos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64GaugeObserver calls `Meter.NewInt64GaugeObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64GaugeObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64GaugeObserver { - if inst, err := mm.meter.NewInt64GaugeObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64GaugeObserver calls `Meter.NewFloat64GaugeObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64GaugeObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64GaugeObserver { - if inst, err := mm.meter.NewFloat64GaugeObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64CounterObserver calls `Meter.NewInt64CounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64CounterObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64CounterObserver { - if inst, err := mm.meter.NewInt64CounterObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64CounterObserver calls `Meter.NewFloat64CounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64CounterObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64CounterObserver { - if inst, err := mm.meter.NewFloat64CounterObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64UpDownCounterObserver calls `Meter.NewInt64UpDownCounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewInt64UpDownCounterObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64UpDownCounterObserver { - if inst, err := mm.meter.NewInt64UpDownCounterObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64UpDownCounterObserver calls `Meter.NewFloat64UpDownCounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (mm MeterMust) NewFloat64UpDownCounterObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64UpDownCounterObserver { - if inst, err := mm.meter.NewFloat64UpDownCounterObserver(name, callback, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewBatchObserver returns a wrapper around BatchObserver that panics -// when any instrument constructor returns an error. -func (mm MeterMust) NewBatchObserver(callback BatchObserverFunc) BatchObserverMust { - return BatchObserverMust{ - batch: mm.meter.NewBatchObserver(callback), - } -} - -// NewInt64GaugeObserver calls `BatchObserver.NewInt64GaugeObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewInt64GaugeObserver(name string, oos ...InstrumentOption) Int64GaugeObserver { - if inst, err := bm.batch.NewInt64GaugeObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64GaugeObserver calls `BatchObserver.NewFloat64GaugeObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewFloat64GaugeObserver(name string, oos ...InstrumentOption) Float64GaugeObserver { - if inst, err := bm.batch.NewFloat64GaugeObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64CounterObserver calls `BatchObserver.NewInt64CounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewInt64CounterObserver(name string, oos ...InstrumentOption) Int64CounterObserver { - if inst, err := bm.batch.NewInt64CounterObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64CounterObserver calls `BatchObserver.NewFloat64CounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewFloat64CounterObserver(name string, oos ...InstrumentOption) Float64CounterObserver { - if inst, err := bm.batch.NewFloat64CounterObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewInt64UpDownCounterObserver calls `BatchObserver.NewInt64UpDownCounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewInt64UpDownCounterObserver(name string, oos ...InstrumentOption) Int64UpDownCounterObserver { - if inst, err := bm.batch.NewInt64UpDownCounterObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} - -// NewFloat64UpDownCounterObserver calls `BatchObserver.NewFloat64UpDownCounterObserver` and -// returns the instrument, panicking if it encounters an error. -func (bm BatchObserverMust) NewFloat64UpDownCounterObserver(name string, oos ...InstrumentOption) Float64UpDownCounterObserver { - if inst, err := bm.batch.NewFloat64UpDownCounterObserver(name, oos...); err != nil { - panic(err) - } else { - return inst - } -} diff --git a/metric/metric_instrument.go b/metric/metric_instrument.go deleted file mode 100644 index 2da24c8f211..00000000000 --- a/metric/metric_instrument.go +++ /dev/null @@ -1,464 +0,0 @@ -// 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 metric // import "go.opentelemetry.io/otel/metric" - -import ( - "context" - "errors" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -// ErrSDKReturnedNilImpl is returned when a new `MeterImpl` returns nil. -var ErrSDKReturnedNilImpl = errors.New("SDK returned a nil implementation") - -// Int64ObserverFunc is a type of callback that integral -// observers run. -type Int64ObserverFunc func(context.Context, Int64ObserverResult) - -// Float64ObserverFunc is a type of callback that floating point -// observers run. -type Float64ObserverFunc func(context.Context, Float64ObserverResult) - -// BatchObserverFunc is a callback argument for use with any -// Observer instrument that will be reported as a batch of -// observations. -type BatchObserverFunc func(context.Context, BatchObserverResult) - -// Int64ObserverResult is passed to an observer callback to capture -// observations for one asynchronous integer metric instrument. -type Int64ObserverResult struct { - instrument sdkapi.AsyncImpl - function func([]attribute.KeyValue, ...Observation) -} - -// Float64ObserverResult is passed to an observer callback to capture -// observations for one asynchronous floating point metric instrument. -type Float64ObserverResult struct { - instrument sdkapi.AsyncImpl - function func([]attribute.KeyValue, ...Observation) -} - -// BatchObserverResult is passed to a batch observer callback to -// capture observations for multiple asynchronous instruments. -type BatchObserverResult struct { - function func([]attribute.KeyValue, ...Observation) -} - -// Observe captures a single integer value from the associated -// instrument callback, with the given labels. -func (ir Int64ObserverResult) Observe(value int64, labels ...attribute.KeyValue) { - ir.function(labels, sdkapi.NewObservation(ir.instrument, number.NewInt64Number(value))) -} - -// Observe captures a single floating point value from the associated -// instrument callback, with the given labels. -func (fr Float64ObserverResult) Observe(value float64, labels ...attribute.KeyValue) { - fr.function(labels, sdkapi.NewObservation(fr.instrument, number.NewFloat64Number(value))) -} - -// Observe captures a multiple observations from the associated batch -// instrument callback, with the given labels. -func (br BatchObserverResult) Observe(labels []attribute.KeyValue, obs ...Observation) { - br.function(labels, obs...) -} - -var _ sdkapi.AsyncSingleRunner = (*Int64ObserverFunc)(nil) -var _ sdkapi.AsyncSingleRunner = (*Float64ObserverFunc)(nil) -var _ sdkapi.AsyncBatchRunner = (*BatchObserverFunc)(nil) - -// newInt64AsyncRunner returns a single-observer callback for integer Observer instruments. -func newInt64AsyncRunner(c Int64ObserverFunc) sdkapi.AsyncSingleRunner { - return &c -} - -// newFloat64AsyncRunner returns a single-observer callback for floating point Observer instruments. -func newFloat64AsyncRunner(c Float64ObserverFunc) sdkapi.AsyncSingleRunner { - return &c -} - -// newBatchAsyncRunner returns a batch-observer callback use with multiple Observer instruments. -func newBatchAsyncRunner(c BatchObserverFunc) sdkapi.AsyncBatchRunner { - return &c -} - -// AnyRunner implements AsyncRunner. -func (*Int64ObserverFunc) AnyRunner() {} - -// AnyRunner implements AsyncRunner. -func (*Float64ObserverFunc) AnyRunner() {} - -// AnyRunner implements AsyncRunner. -func (*BatchObserverFunc) AnyRunner() {} - -// Run implements AsyncSingleRunner. -func (i *Int64ObserverFunc) Run(ctx context.Context, impl sdkapi.AsyncImpl, function func([]attribute.KeyValue, ...Observation)) { - (*i)(ctx, Int64ObserverResult{ - instrument: impl, - function: function, - }) -} - -// Run implements AsyncSingleRunner. -func (f *Float64ObserverFunc) Run(ctx context.Context, impl sdkapi.AsyncImpl, function func([]attribute.KeyValue, ...Observation)) { - (*f)(ctx, Float64ObserverResult{ - instrument: impl, - function: function, - }) -} - -// Run implements AsyncBatchRunner. -func (b *BatchObserverFunc) Run(ctx context.Context, function func([]attribute.KeyValue, ...Observation)) { - (*b)(ctx, BatchObserverResult{ - function: function, - }) -} - -// wrapInt64GaugeObserverInstrument converts an AsyncImpl into Int64GaugeObserver. -func wrapInt64GaugeObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64GaugeObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Int64GaugeObserver{asyncInstrument: common}, err -} - -// wrapFloat64GaugeObserverInstrument converts an AsyncImpl into Float64GaugeObserver. -func wrapFloat64GaugeObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64GaugeObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Float64GaugeObserver{asyncInstrument: common}, err -} - -// wrapInt64CounterObserverInstrument converts an AsyncImpl into Int64CounterObserver. -func wrapInt64CounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64CounterObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Int64CounterObserver{asyncInstrument: common}, err -} - -// wrapFloat64CounterObserverInstrument converts an AsyncImpl into Float64CounterObserver. -func wrapFloat64CounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64CounterObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Float64CounterObserver{asyncInstrument: common}, err -} - -// wrapInt64UpDownCounterObserverInstrument converts an AsyncImpl into Int64UpDownCounterObserver. -func wrapInt64UpDownCounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64UpDownCounterObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Int64UpDownCounterObserver{asyncInstrument: common}, err -} - -// wrapFloat64UpDownCounterObserverInstrument converts an AsyncImpl into Float64UpDownCounterObserver. -func wrapFloat64UpDownCounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64UpDownCounterObserver, error) { - common, err := checkNewAsync(asyncInst, err) - return Float64UpDownCounterObserver{asyncInstrument: common}, err -} - -// BatchObserver represents an Observer callback that can report -// observations for multiple instruments. -type BatchObserver struct { - meter Meter - runner sdkapi.AsyncBatchRunner -} - -// Int64GaugeObserver is a metric that captures a set of int64 values at a -// point in time. -type Int64GaugeObserver struct { - asyncInstrument -} - -// Float64GaugeObserver is a metric that captures a set of float64 values -// at a point in time. -type Float64GaugeObserver struct { - asyncInstrument -} - -// Int64CounterObserver is a metric that captures a precomputed sum of -// int64 values at a point in time. -type Int64CounterObserver struct { - asyncInstrument -} - -// Float64CounterObserver is a metric that captures a precomputed sum of -// float64 values at a point in time. -type Float64CounterObserver struct { - asyncInstrument -} - -// Int64UpDownCounterObserver is a metric that captures a precomputed sum of -// int64 values at a point in time. -type Int64UpDownCounterObserver struct { - asyncInstrument -} - -// Float64UpDownCounterObserver is a metric that captures a precomputed sum of -// float64 values at a point in time. -type Float64UpDownCounterObserver struct { - asyncInstrument -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (i Int64GaugeObserver) Observation(v int64) Observation { - return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v)) -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (f Float64GaugeObserver) Observation(v float64) Observation { - return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v)) -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (i Int64CounterObserver) Observation(v int64) Observation { - return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v)) -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (f Float64CounterObserver) Observation(v float64) Observation { - return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v)) -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (i Int64UpDownCounterObserver) Observation(v int64) Observation { - return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v)) -} - -// Observation returns an Observation, a BatchObserverFunc -// argument, for an asynchronous integer instrument. -// This returns an implementation-level object for use by the SDK, -// users should not refer to this. -func (f Float64UpDownCounterObserver) Observation(v float64) Observation { - return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v)) -} - -// syncInstrument contains a SyncImpl. -type syncInstrument struct { - instrument sdkapi.SyncImpl -} - -// asyncInstrument contains a AsyncImpl. -type asyncInstrument struct { - instrument sdkapi.AsyncImpl -} - -// AsyncImpl implements AsyncImpl. -func (a asyncInstrument) AsyncImpl() sdkapi.AsyncImpl { - return a.instrument -} - -// SyncImpl returns the implementation object for synchronous instruments. -func (s syncInstrument) SyncImpl() sdkapi.SyncImpl { - return s.instrument -} - -func (s syncInstrument) float64Measurement(value float64) Measurement { - return sdkapi.NewMeasurement(s.instrument, number.NewFloat64Number(value)) -} - -func (s syncInstrument) int64Measurement(value int64) Measurement { - return sdkapi.NewMeasurement(s.instrument, number.NewInt64Number(value)) -} - -func (s syncInstrument) directRecord(ctx context.Context, number number.Number, labels []attribute.KeyValue) { - s.instrument.RecordOne(ctx, number, labels) -} - -// checkNewAsync receives an AsyncImpl and potential -// error, and returns the same types, checking for and ensuring that -// the returned interface is not nil. -func checkNewAsync(instrument sdkapi.AsyncImpl, err error) (asyncInstrument, error) { - if instrument == nil { - if err == nil { - err = ErrSDKReturnedNilImpl - } - instrument = sdkapi.NewNoopAsyncInstrument() - } - return asyncInstrument{ - instrument: instrument, - }, err -} - -// checkNewSync receives an SyncImpl and potential -// error, and returns the same types, checking for and ensuring that -// the returned interface is not nil. -func checkNewSync(instrument sdkapi.SyncImpl, err error) (syncInstrument, error) { - if instrument == nil { - if err == nil { - err = ErrSDKReturnedNilImpl - } - // Note: an alternate behavior would be to synthesize a new name - // or group all duplicately-named instruments of a certain type - // together and use a tag for the original name, e.g., - // name = 'invalid.counter.int64' - // label = 'original-name=duplicate-counter-name' - instrument = sdkapi.NewNoopSyncInstrument() - } - return syncInstrument{ - instrument: instrument, - }, err -} - -// wrapInt64CounterInstrument converts a SyncImpl into Int64Counter. -func wrapInt64CounterInstrument(syncInst sdkapi.SyncImpl, err error) (Int64Counter, error) { - common, err := checkNewSync(syncInst, err) - return Int64Counter{syncInstrument: common}, err -} - -// wrapFloat64CounterInstrument converts a SyncImpl into Float64Counter. -func wrapFloat64CounterInstrument(syncInst sdkapi.SyncImpl, err error) (Float64Counter, error) { - common, err := checkNewSync(syncInst, err) - return Float64Counter{syncInstrument: common}, err -} - -// wrapInt64UpDownCounterInstrument converts a SyncImpl into Int64UpDownCounter. -func wrapInt64UpDownCounterInstrument(syncInst sdkapi.SyncImpl, err error) (Int64UpDownCounter, error) { - common, err := checkNewSync(syncInst, err) - return Int64UpDownCounter{syncInstrument: common}, err -} - -// wrapFloat64UpDownCounterInstrument converts a SyncImpl into Float64UpDownCounter. -func wrapFloat64UpDownCounterInstrument(syncInst sdkapi.SyncImpl, err error) (Float64UpDownCounter, error) { - common, err := checkNewSync(syncInst, err) - return Float64UpDownCounter{syncInstrument: common}, err -} - -// wrapInt64HistogramInstrument converts a SyncImpl into Int64Histogram. -func wrapInt64HistogramInstrument(syncInst sdkapi.SyncImpl, err error) (Int64Histogram, error) { - common, err := checkNewSync(syncInst, err) - return Int64Histogram{syncInstrument: common}, err -} - -// wrapFloat64HistogramInstrument converts a SyncImpl into Float64Histogram. -func wrapFloat64HistogramInstrument(syncInst sdkapi.SyncImpl, err error) (Float64Histogram, error) { - common, err := checkNewSync(syncInst, err) - return Float64Histogram{syncInstrument: common}, err -} - -// Float64Counter is a metric that accumulates float64 values. -type Float64Counter struct { - syncInstrument -} - -// Int64Counter is a metric that accumulates int64 values. -type Int64Counter struct { - syncInstrument -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Float64Counter) Measurement(value float64) Measurement { - return c.float64Measurement(value) -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Int64Counter) Measurement(value int64) Measurement { - return c.int64Measurement(value) -} - -// Add adds the value to the counter's sum. The labels should contain -// the keys and values to be associated with this value. -func (c Float64Counter) Add(ctx context.Context, value float64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewFloat64Number(value), labels) -} - -// Add adds the value to the counter's sum. The labels should contain -// the keys and values to be associated with this value. -func (c Int64Counter) Add(ctx context.Context, value int64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewInt64Number(value), labels) -} - -// Float64UpDownCounter is a metric instrument that sums floating -// point values. -type Float64UpDownCounter struct { - syncInstrument -} - -// Int64UpDownCounter is a metric instrument that sums integer values. -type Int64UpDownCounter struct { - syncInstrument -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Float64UpDownCounter) Measurement(value float64) Measurement { - return c.float64Measurement(value) -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Int64UpDownCounter) Measurement(value int64) Measurement { - return c.int64Measurement(value) -} - -// Add adds the value to the counter's sum. The labels should contain -// the keys and values to be associated with this value. -func (c Float64UpDownCounter) Add(ctx context.Context, value float64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewFloat64Number(value), labels) -} - -// Add adds the value to the counter's sum. The labels should contain -// the keys and values to be associated with this value. -func (c Int64UpDownCounter) Add(ctx context.Context, value int64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewInt64Number(value), labels) -} - -// Float64Histogram is a metric that records float64 values. -type Float64Histogram struct { - syncInstrument -} - -// Int64Histogram is a metric that records int64 values. -type Int64Histogram struct { - syncInstrument -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Float64Histogram) Measurement(value float64) Measurement { - return c.float64Measurement(value) -} - -// Measurement creates a Measurement object to use with batch -// recording. -func (c Int64Histogram) Measurement(value int64) Measurement { - return c.int64Measurement(value) -} - -// Record adds a new value to the list of Histogram's records. The -// labels should contain the keys and values to be associated with -// this value. -func (c Float64Histogram) Record(ctx context.Context, value float64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewFloat64Number(value), labels) -} - -// Record adds a new value to the Histogram's distribution. The -// labels should contain the keys and values to be associated with -// this value. -func (c Int64Histogram) Record(ctx context.Context, value int64, labels ...attribute.KeyValue) { - c.directRecord(ctx, number.NewInt64Number(value), labels) -} diff --git a/metric/metric_test.go b/metric/metric_test.go deleted file mode 100644 index ab8e916b829..00000000000 --- a/metric/metric_test.go +++ /dev/null @@ -1,497 +0,0 @@ -// 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 metric_test - -import ( - "context" - "errors" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" - "go.opentelemetry.io/otel/metric/unit" -) - -var Must = metric.Must - -var ( - syncKinds = []sdkapi.InstrumentKind{ - sdkapi.HistogramInstrumentKind, - sdkapi.CounterInstrumentKind, - sdkapi.UpDownCounterInstrumentKind, - } - asyncKinds = []sdkapi.InstrumentKind{ - sdkapi.GaugeObserverInstrumentKind, - sdkapi.CounterObserverInstrumentKind, - sdkapi.UpDownCounterObserverInstrumentKind, - } - addingKinds = []sdkapi.InstrumentKind{ - sdkapi.CounterInstrumentKind, - sdkapi.UpDownCounterInstrumentKind, - sdkapi.CounterObserverInstrumentKind, - sdkapi.UpDownCounterObserverInstrumentKind, - } - groupingKinds = []sdkapi.InstrumentKind{ - sdkapi.HistogramInstrumentKind, - sdkapi.GaugeObserverInstrumentKind, - } - - monotonicKinds = []sdkapi.InstrumentKind{ - sdkapi.CounterInstrumentKind, - sdkapi.CounterObserverInstrumentKind, - } - - nonMonotonicKinds = []sdkapi.InstrumentKind{ - sdkapi.UpDownCounterInstrumentKind, - sdkapi.UpDownCounterObserverInstrumentKind, - sdkapi.HistogramInstrumentKind, - sdkapi.GaugeObserverInstrumentKind, - } - - precomputedSumKinds = []sdkapi.InstrumentKind{ - sdkapi.CounterObserverInstrumentKind, - sdkapi.UpDownCounterObserverInstrumentKind, - } - - nonPrecomputedSumKinds = []sdkapi.InstrumentKind{ - sdkapi.CounterInstrumentKind, - sdkapi.UpDownCounterInstrumentKind, - sdkapi.HistogramInstrumentKind, - sdkapi.GaugeObserverInstrumentKind, - } -) - -func TestSynchronous(t *testing.T) { - for _, k := range syncKinds { - require.True(t, k.Synchronous()) - require.False(t, k.Asynchronous()) - } - for _, k := range asyncKinds { - require.True(t, k.Asynchronous()) - require.False(t, k.Synchronous()) - } -} - -func TestGrouping(t *testing.T) { - for _, k := range groupingKinds { - require.True(t, k.Grouping()) - require.False(t, k.Adding()) - } - for _, k := range addingKinds { - require.True(t, k.Adding()) - require.False(t, k.Grouping()) - } -} - -func TestMonotonic(t *testing.T) { - for _, k := range monotonicKinds { - require.True(t, k.Monotonic()) - } - for _, k := range nonMonotonicKinds { - require.False(t, k.Monotonic()) - } -} - -func TestPrecomputedSum(t *testing.T) { - for _, k := range precomputedSumKinds { - require.True(t, k.PrecomputedSum()) - } - for _, k := range nonPrecomputedSumKinds { - require.False(t, k.PrecomputedSum()) - } -} - -func checkSyncBatches(ctx context.Context, t *testing.T, labels []attribute.KeyValue, provider *metrictest.MeterProvider, nkind number.Kind, mkind sdkapi.InstrumentKind, instrument sdkapi.InstrumentImpl, expected ...float64) { - t.Helper() - - batchesCount := len(provider.MeasurementBatches) - if len(provider.MeasurementBatches) != len(expected) { - t.Errorf("Expected %d recorded measurement batches, got %d", batchesCount, len(provider.MeasurementBatches)) - } - recorded := metrictest.AsStructs(provider.MeasurementBatches) - - for i, batch := range provider.MeasurementBatches { - if len(batch.Measurements) != 1 { - t.Errorf("Expected 1 measurement in batch %d, got %d", i, len(batch.Measurements)) - } - - measurement := batch.Measurements[0] - descriptor := measurement.Instrument.Descriptor() - - expected := metrictest.Measured{ - Name: descriptor.Name(), - Library: metrictest.Library{ - InstrumentationName: "apitest", - }, - Labels: metrictest.LabelsToMap(labels...), - Number: metrictest.ResolveNumberByKind(t, nkind, expected[i]), - } - require.Equal(t, expected, recorded[i]) - } -} - -func TestOptions(t *testing.T) { - type testcase struct { - name string - opts []metric.InstrumentOption - desc string - unit unit.Unit - } - testcases := []testcase{ - { - name: "no opts", - opts: nil, - desc: "", - unit: "", - }, - { - name: "description", - opts: []metric.InstrumentOption{ - metric.WithDescription("stuff"), - }, - desc: "stuff", - unit: "", - }, - { - name: "description override", - opts: []metric.InstrumentOption{ - metric.WithDescription("stuff"), - metric.WithDescription("things"), - }, - desc: "things", - unit: "", - }, - { - name: "unit", - opts: []metric.InstrumentOption{ - metric.WithUnit("s"), - }, - desc: "", - unit: "s", - }, - { - name: "description override", - opts: []metric.InstrumentOption{ - metric.WithDescription("stuff"), - metric.WithDescription("things"), - }, - desc: "things", - unit: "", - }, - { - name: "unit", - opts: []metric.InstrumentOption{ - metric.WithUnit("s"), - }, - desc: "", - unit: "s", - }, - - { - name: "unit override", - opts: []metric.InstrumentOption{ - metric.WithUnit("s"), - metric.WithUnit("h"), - }, - desc: "", - unit: "h", - }, - { - name: "all", - opts: []metric.InstrumentOption{ - metric.WithDescription("stuff"), - metric.WithUnit("s"), - }, - desc: "stuff", - unit: "s", - }, - } - for idx, tt := range testcases { - t.Logf("Testing counter case %s (%d)", tt.name, idx) - cfg := metric.NewInstrumentConfig(tt.opts...) - if diff := cmp.Diff(cfg.Description(), tt.desc); diff != "" { - t.Errorf("Compare Description: -got +want %s", diff) - } - if diff := cmp.Diff(cfg.Unit(), tt.unit); diff != "" { - t.Errorf("Compare Unit: -got +want %s", diff) - } - } -} -func testPair() (*metrictest.MeterProvider, metric.Meter) { - provider := metrictest.NewMeterProvider() - return provider, provider.Meter("apitest") -} - -func TestCounter(t *testing.T) { - // N.B. the API does not check for negative - // values, that's the SDK's responsibility. - t.Run("float64 counter", func(t *testing.T) { - provider, meter := testPair() - c := Must(meter).NewFloat64Counter("test.counter.float") - ctx := context.Background() - labels := []attribute.KeyValue{attribute.String("A", "B")} - c.Add(ctx, 1994.1, labels...) - meter.RecordBatch(ctx, labels, c.Measurement(42)) - checkSyncBatches(ctx, t, labels, provider, number.Float64Kind, sdkapi.CounterInstrumentKind, c.SyncImpl(), - 1994.1, 42, - ) - }) - t.Run("int64 counter", func(t *testing.T) { - provider, meter := testPair() - c := Must(meter).NewInt64Counter("test.counter.int") - ctx := context.Background() - labels := []attribute.KeyValue{attribute.String("A", "B"), attribute.String("C", "D")} - c.Add(ctx, 42, labels...) - meter.RecordBatch(ctx, labels, c.Measurement(420000)) - checkSyncBatches(ctx, t, labels, provider, number.Int64Kind, sdkapi.CounterInstrumentKind, c.SyncImpl(), - 42, 420000, - ) - - }) - t.Run("int64 updowncounter", func(t *testing.T) { - provider, meter := testPair() - c := Must(meter).NewInt64UpDownCounter("test.updowncounter.int") - ctx := context.Background() - labels := []attribute.KeyValue{attribute.String("A", "B"), attribute.String("C", "D")} - c.Add(ctx, 100, labels...) - meter.RecordBatch(ctx, labels, c.Measurement(42)) - checkSyncBatches(ctx, t, labels, provider, number.Int64Kind, sdkapi.UpDownCounterInstrumentKind, c.SyncImpl(), - 100, 42, - ) - }) - t.Run("float64 updowncounter", func(t *testing.T) { - provider, meter := testPair() - c := Must(meter).NewFloat64UpDownCounter("test.updowncounter.float") - ctx := context.Background() - labels := []attribute.KeyValue{attribute.String("A", "B"), attribute.String("C", "D")} - c.Add(ctx, 100.1, labels...) - meter.RecordBatch(ctx, labels, c.Measurement(-100.1)) - checkSyncBatches(ctx, t, labels, provider, number.Float64Kind, sdkapi.UpDownCounterInstrumentKind, c.SyncImpl(), - 100.1, -100.1, - ) - }) -} - -func TestHistogram(t *testing.T) { - t.Run("float64 histogram", func(t *testing.T) { - provider, meter := testPair() - m := Must(meter).NewFloat64Histogram("test.histogram.float") - ctx := context.Background() - labels := []attribute.KeyValue{} - m.Record(ctx, 42, labels...) - meter.RecordBatch(ctx, labels, m.Measurement(-100.5)) - checkSyncBatches(ctx, t, labels, provider, number.Float64Kind, sdkapi.HistogramInstrumentKind, m.SyncImpl(), - 42, -100.5, - ) - }) - t.Run("int64 histogram", func(t *testing.T) { - provider, meter := testPair() - m := Must(meter).NewInt64Histogram("test.histogram.int") - ctx := context.Background() - labels := []attribute.KeyValue{attribute.Int("I", 1)} - m.Record(ctx, 173, labels...) - meter.RecordBatch(ctx, labels, m.Measurement(0)) - checkSyncBatches(ctx, t, labels, provider, number.Int64Kind, sdkapi.HistogramInstrumentKind, m.SyncImpl(), - 173, 0, - ) - }) -} - -func TestObserverInstruments(t *testing.T) { - t.Run("float gauge", func(t *testing.T) { - labels := []attribute.KeyValue{attribute.String("O", "P")} - provider, meter := testPair() - o := Must(meter).NewFloat64GaugeObserver("test.gauge.float", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(42.1, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Float64Kind, sdkapi.GaugeObserverInstrumentKind, o.AsyncImpl(), - 42.1, - ) - }) - t.Run("int gauge", func(t *testing.T) { - labels := []attribute.KeyValue{} - provider, meter := testPair() - o := Must(meter).NewInt64GaugeObserver("test.gauge.int", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(-142, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Int64Kind, sdkapi.GaugeObserverInstrumentKind, o.AsyncImpl(), - -142, - ) - }) - t.Run("float counterobserver", func(t *testing.T) { - labels := []attribute.KeyValue{attribute.String("O", "P")} - provider, meter := testPair() - o := Must(meter).NewFloat64CounterObserver("test.counter.float", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(42.1, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Float64Kind, sdkapi.CounterObserverInstrumentKind, o.AsyncImpl(), - 42.1, - ) - }) - t.Run("int counterobserver", func(t *testing.T) { - labels := []attribute.KeyValue{} - provider, meter := testPair() - o := Must(meter).NewInt64CounterObserver("test.counter.int", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(-142, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Int64Kind, sdkapi.CounterObserverInstrumentKind, o.AsyncImpl(), - -142, - ) - }) - t.Run("float updowncounterobserver", func(t *testing.T) { - labels := []attribute.KeyValue{attribute.String("O", "P")} - provider, meter := testPair() - o := Must(meter).NewFloat64UpDownCounterObserver("test.updowncounter.float", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(42.1, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Float64Kind, sdkapi.UpDownCounterObserverInstrumentKind, o.AsyncImpl(), - 42.1, - ) - }) - t.Run("int updowncounterobserver", func(t *testing.T) { - labels := []attribute.KeyValue{} - provider, meter := testPair() - o := Must(meter).NewInt64UpDownCounterObserver("test..int", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(-142, labels...) - }) - provider.RunAsyncInstruments() - checkObserverBatch(t, labels, provider, number.Int64Kind, sdkapi.UpDownCounterObserverInstrumentKind, o.AsyncImpl(), - -142, - ) - }) -} - -func TestBatchObserverInstruments(t *testing.T) { - provider, meter := testPair() - - var obs1 metric.Int64GaugeObserver - var obs2 metric.Float64GaugeObserver - - labels := []attribute.KeyValue{ - attribute.String("A", "B"), - attribute.String("C", "D"), - } - - cb := Must(meter).NewBatchObserver( - func(_ context.Context, result metric.BatchObserverResult) { - result.Observe(labels, - obs1.Observation(42), - obs2.Observation(42.0), - ) - }, - ) - obs1 = cb.NewInt64GaugeObserver("test.gauge.int") - obs2 = cb.NewFloat64GaugeObserver("test.gauge.float") - - provider.RunAsyncInstruments() - - require.Len(t, provider.MeasurementBatches, 1) - - impl1 := obs1.AsyncImpl().Implementation().(*metrictest.Async) - impl2 := obs2.AsyncImpl().Implementation().(*metrictest.Async) - - require.NotNil(t, impl1) - require.NotNil(t, impl2) - - got := provider.MeasurementBatches[0] - require.Equal(t, labels, got.Labels) - require.Len(t, got.Measurements, 2) - - m1 := got.Measurements[0] - require.Equal(t, impl1, m1.Instrument.Implementation().(*metrictest.Async)) - require.Equal(t, 0, m1.Number.CompareNumber(number.Int64Kind, metrictest.ResolveNumberByKind(t, number.Int64Kind, 42))) - - m2 := got.Measurements[1] - require.Equal(t, impl2, m2.Instrument.Implementation().(*metrictest.Async)) - require.Equal(t, 0, m2.Number.CompareNumber(number.Float64Kind, metrictest.ResolveNumberByKind(t, number.Float64Kind, 42))) -} - -func checkObserverBatch(t *testing.T, labels []attribute.KeyValue, provider *metrictest.MeterProvider, nkind number.Kind, mkind sdkapi.InstrumentKind, observer sdkapi.AsyncImpl, expected float64) { - t.Helper() - assert.Len(t, provider.MeasurementBatches, 1) - if len(provider.MeasurementBatches) < 1 { - return - } - o := observer.Implementation().(*metrictest.Async) - if !assert.NotNil(t, o) { - return - } - got := provider.MeasurementBatches[0] - assert.Equal(t, labels, got.Labels) - assert.Len(t, got.Measurements, 1) - if len(got.Measurements) < 1 { - return - } - measurement := got.Measurements[0] - require.Equal(t, mkind, measurement.Instrument.Descriptor().InstrumentKind()) - assert.Equal(t, o, measurement.Instrument.Implementation().(*metrictest.Async)) - ft := metrictest.ResolveNumberByKind(t, nkind, expected) - assert.Equal(t, 0, measurement.Number.CompareNumber(nkind, ft)) -} - -type testWrappedMeter struct { -} - -var _ sdkapi.MeterImpl = testWrappedMeter{} - -func (testWrappedMeter) RecordBatch(context.Context, []attribute.KeyValue, ...sdkapi.Measurement) { -} - -func (testWrappedMeter) NewSyncInstrument(_ sdkapi.Descriptor) (sdkapi.SyncImpl, error) { - return nil, nil -} - -func (testWrappedMeter) NewAsyncInstrument(_ sdkapi.Descriptor, _ sdkapi.AsyncRunner) (sdkapi.AsyncImpl, error) { - return nil, errors.New("Test wrap error") -} - -func TestWrappedInstrumentError(t *testing.T) { - impl := &testWrappedMeter{} - meter := metric.WrapMeterImpl(impl) - - histogram, err := meter.NewInt64Histogram("test.histogram") - - require.Equal(t, err, metric.ErrSDKReturnedNilImpl) - require.NotNil(t, histogram.SyncImpl()) - - observer, err := meter.NewInt64GaugeObserver("test.observer", func(_ context.Context, result metric.Int64ObserverResult) {}) - - require.NotNil(t, err) - require.NotNil(t, observer.AsyncImpl()) -} - -func TestNilCallbackObserverNoop(t *testing.T) { - // Tests that a nil callback yields a no-op observer without error. - _, meter := testPair() - - observer := Must(meter).NewInt64GaugeObserver("test.observer", nil) - - impl := observer.AsyncImpl().Implementation() - desc := observer.AsyncImpl().Descriptor() - require.Equal(t, nil, impl) - require.Equal(t, "", desc.Name()) -} diff --git a/metric/metrictest/meter.go b/metric/metrictest/meter.go deleted file mode 100644 index 759bb04baec..00000000000 --- a/metric/metrictest/meter.go +++ /dev/null @@ -1,290 +0,0 @@ -// 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 metrictest // import "go.opentelemetry.io/otel/metric/metrictest" - -import ( - "context" - "sync" - "testing" - - "go.opentelemetry.io/otel/attribute" - internalmetric "go.opentelemetry.io/otel/internal/metric" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" -) - -type ( - Handle struct { - Instrument *Sync - Labels []attribute.KeyValue - } - - // Library is the same as "sdk/instrumentation".Library but there is - // a package cycle to use it. - Library struct { - InstrumentationName string - InstrumentationVersion string - SchemaURL string - } - - Batch struct { - // Measurement needs to be aligned for 64-bit atomic operations. - Measurements []Measurement - Ctx context.Context - Labels []attribute.KeyValue - Library Library - } - - // MeterImpl is an OpenTelemetry Meter implementation used for testing. - MeterImpl struct { - library Library - provider *MeterProvider - asyncInstruments *internalmetric.AsyncInstrumentState - } - - // MeterProvider is a collection of named MeterImpls used for testing. - MeterProvider struct { - lock sync.Mutex - - MeasurementBatches []Batch - impls []*MeterImpl - } - - Measurement struct { - // Number needs to be aligned for 64-bit atomic operations. - Number number.Number - Instrument sdkapi.InstrumentImpl - } - - Instrument struct { - meter *MeterImpl - descriptor sdkapi.Descriptor - } - - Async struct { - Instrument - - runner sdkapi.AsyncRunner - } - - Sync struct { - Instrument - } -) - -var ( - _ sdkapi.SyncImpl = &Sync{} - _ sdkapi.MeterImpl = &MeterImpl{} - _ sdkapi.AsyncImpl = &Async{} -) - -// NewDescriptor is a test helper for constructing test metric -// descriptors using standard options. -func NewDescriptor(name string, ikind sdkapi.InstrumentKind, nkind number.Kind, opts ...metric.InstrumentOption) sdkapi.Descriptor { - cfg := metric.NewInstrumentConfig(opts...) - return sdkapi.NewDescriptor(name, ikind, nkind, cfg.Description(), cfg.Unit()) -} - -func (i Instrument) Descriptor() sdkapi.Descriptor { - return i.descriptor -} - -func (a *Async) Implementation() interface{} { - return a -} - -func (s *Sync) Implementation() interface{} { - return s -} - -func (s *Sync) RecordOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) { - s.meter.doRecordSingle(ctx, labels, s, number) -} - -func (h *Handle) RecordOne(ctx context.Context, number number.Number) { - h.Instrument.meter.doRecordSingle(ctx, h.Labels, h.Instrument, number) -} - -func (h *Handle) Unbind() { -} - -func (m *MeterImpl) doRecordSingle(ctx context.Context, labels []attribute.KeyValue, instrument sdkapi.InstrumentImpl, number number.Number) { - m.collect(ctx, labels, []Measurement{{ - Instrument: instrument, - Number: number, - }}) -} - -// NewMeterProvider returns a MeterProvider suitable for testing. -// When the test is complete, consult MeterProvider.MeasurementBatches. -func NewMeterProvider() *MeterProvider { - return &MeterProvider{} -} - -// Meter implements metric.MeterProvider. -func (p *MeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter { - p.lock.Lock() - defer p.lock.Unlock() - cfg := metric.NewMeterConfig(opts...) - impl := &MeterImpl{ - library: Library{ - InstrumentationName: name, - InstrumentationVersion: cfg.InstrumentationVersion(), - SchemaURL: cfg.SchemaURL(), - }, - provider: p, - asyncInstruments: internalmetric.NewAsyncInstrumentState(), - } - p.impls = append(p.impls, impl) - return metric.WrapMeterImpl(impl) -} - -// NewSyncInstrument implements sdkapi.MeterImpl. -func (m *MeterImpl) NewSyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.SyncImpl, error) { - return &Sync{ - Instrument{ - descriptor: descriptor, - meter: m, - }, - }, nil -} - -// NewAsyncInstrument implements sdkapi.MeterImpl. -func (m *MeterImpl) NewAsyncInstrument(descriptor sdkapi.Descriptor, runner sdkapi.AsyncRunner) (sdkapi.AsyncImpl, error) { - a := &Async{ - Instrument: Instrument{ - descriptor: descriptor, - meter: m, - }, - runner: runner, - } - m.provider.registerAsyncInstrument(a, m, runner) - return a, nil -} - -// RecordBatch implements sdkapi.MeterImpl. -func (m *MeterImpl) RecordBatch(ctx context.Context, labels []attribute.KeyValue, measurements ...sdkapi.Measurement) { - mm := make([]Measurement, len(measurements)) - for i := 0; i < len(measurements); i++ { - m := measurements[i] - mm[i] = Measurement{ - Instrument: m.SyncImpl().Implementation().(*Sync), - Number: m.Number(), - } - } - m.collect(ctx, labels, mm) -} - -// CollectAsync is called from asyncInstruments.Run() with the lock held. -func (m *MeterImpl) CollectAsync(labels []attribute.KeyValue, obs ...sdkapi.Observation) { - mm := make([]Measurement, len(obs)) - for i := 0; i < len(obs); i++ { - o := obs[i] - mm[i] = Measurement{ - Instrument: o.AsyncImpl(), - Number: o.Number(), - } - } - m.collect(context.Background(), labels, mm) -} - -// collect is called from CollectAsync() or RecordBatch() with the lock held. -func (m *MeterImpl) collect(ctx context.Context, labels []attribute.KeyValue, measurements []Measurement) { - m.provider.addMeasurement(Batch{ - Ctx: ctx, - Labels: labels, - Measurements: measurements, - Library: m.library, - }) -} - -// registerAsyncInstrument locks the provider and registers the new Async instrument. -func (p *MeterProvider) registerAsyncInstrument(a *Async, m *MeterImpl, runner sdkapi.AsyncRunner) { - p.lock.Lock() - defer p.lock.Unlock() - - m.asyncInstruments.Register(a, runner) -} - -// addMeasurement locks the provider and adds the new measurement batch. -func (p *MeterProvider) addMeasurement(b Batch) { - p.lock.Lock() - defer p.lock.Unlock() - p.MeasurementBatches = append(p.MeasurementBatches, b) -} - -// copyImpls locks the provider and copies the current list of *MeterImpls. -func (p *MeterProvider) copyImpls() []*MeterImpl { - p.lock.Lock() - defer p.lock.Unlock() - cpy := make([]*MeterImpl, len(p.impls)) - copy(cpy, p.impls) - return cpy -} - -// RunAsyncInstruments is used in tests to trigger collection from -// asynchronous instruments. -func (p *MeterProvider) RunAsyncInstruments() { - for _, impl := range p.copyImpls() { - impl.asyncInstruments.Run(context.Background(), impl) - } -} - -// Measured is the helper struct which provides flat representation of recorded measurements -// to simplify testing -type Measured struct { - Name string - Labels map[attribute.Key]attribute.Value - Number number.Number - Library Library -} - -// LabelsToMap converts label set to keyValue map, to be easily used in tests -func LabelsToMap(kvs ...attribute.KeyValue) map[attribute.Key]attribute.Value { - m := map[attribute.Key]attribute.Value{} - for _, label := range kvs { - m[label.Key] = label.Value - } - return m -} - -// AsStructs converts recorded batches to array of flat, readable Measured helper structures -func AsStructs(batches []Batch) []Measured { - var r []Measured - for _, batch := range batches { - for _, m := range batch.Measurements { - r = append(r, Measured{ - Name: m.Instrument.Descriptor().Name(), - Labels: LabelsToMap(batch.Labels...), - Number: m.Number, - Library: batch.Library, - }) - } - } - return r -} - -// ResolveNumberByKind takes defined metric descriptor creates a concrete typed metric number -func ResolveNumberByKind(t *testing.T, kind number.Kind, value float64) number.Number { - t.Helper() - switch kind { - case number.Int64Kind: - return number.NewInt64Number(int64(value)) - case number.Float64Kind: - return number.NewFloat64Number(value) - } - panic("invalid number kind") -} diff --git a/metric/nonrecording/instruments.go b/metric/nonrecording/instruments.go new file mode 100644 index 00000000000..84a6aa89c31 --- /dev/null +++ b/metric/nonrecording/instruments.go @@ -0,0 +1,138 @@ +// 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 nonrecording // import "go.opentelemetry.io/otel/metric/nonrecording" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" +) + +type nonrecordingAsyncFloat64Instrument struct { + instrument.Asynchronous +} + +var ( + _ asyncfloat64.InstrumentProvider = nonrecordingAsyncFloat64Instrument{} + _ asyncfloat64.Counter = nonrecordingAsyncFloat64Instrument{} + _ asyncfloat64.UpDownCounter = nonrecordingAsyncFloat64Instrument{} + _ asyncfloat64.Gauge = nonrecordingAsyncFloat64Instrument{} +) + +func (n nonrecordingAsyncFloat64Instrument) Counter(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) { + return n, nil +} + +func (n nonrecordingAsyncFloat64Instrument) UpDownCounter(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) { + return n, nil +} + +func (n nonrecordingAsyncFloat64Instrument) Gauge(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { + return n, nil +} + +func (nonrecordingAsyncFloat64Instrument) Observe(context.Context, float64, ...attribute.KeyValue) { + +} + +type nonrecordingAsyncInt64Instrument struct { + instrument.Asynchronous +} + +var ( + _ asyncint64.InstrumentProvider = nonrecordingAsyncInt64Instrument{} + _ asyncint64.Counter = nonrecordingAsyncInt64Instrument{} + _ asyncint64.UpDownCounter = nonrecordingAsyncInt64Instrument{} + _ asyncint64.Gauge = nonrecordingAsyncInt64Instrument{} +) + +func (n nonrecordingAsyncInt64Instrument) Counter(name string, opts ...instrument.Option) (asyncint64.Counter, error) { + return n, nil +} + +func (n nonrecordingAsyncInt64Instrument) UpDownCounter(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) { + return n, nil +} + +func (n nonrecordingAsyncInt64Instrument) Gauge(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { + return n, nil +} + +func (nonrecordingAsyncInt64Instrument) Observe(context.Context, int64, ...attribute.KeyValue) { +} + +type nonrecordingSyncFloat64Instrument struct { + instrument.Synchronous +} + +var ( + _ syncfloat64.InstrumentProvider = nonrecordingSyncFloat64Instrument{} + _ syncfloat64.Counter = nonrecordingSyncFloat64Instrument{} + _ syncfloat64.UpDownCounter = nonrecordingSyncFloat64Instrument{} + _ syncfloat64.Histogram = nonrecordingSyncFloat64Instrument{} +) + +func (n nonrecordingSyncFloat64Instrument) Counter(name string, opts ...instrument.Option) (syncfloat64.Counter, error) { + return n, nil +} + +func (n nonrecordingSyncFloat64Instrument) UpDownCounter(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) { + return n, nil +} + +func (n nonrecordingSyncFloat64Instrument) Histogram(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) { + return n, nil +} + +func (nonrecordingSyncFloat64Instrument) Add(context.Context, float64, ...attribute.KeyValue) { + +} + +func (nonrecordingSyncFloat64Instrument) Record(context.Context, float64, ...attribute.KeyValue) { + +} + +type nonrecordingSyncInt64Instrument struct { + instrument.Synchronous +} + +var ( + _ syncint64.InstrumentProvider = nonrecordingSyncInt64Instrument{} + _ syncint64.Counter = nonrecordingSyncInt64Instrument{} + _ syncint64.UpDownCounter = nonrecordingSyncInt64Instrument{} + _ syncint64.Histogram = nonrecordingSyncInt64Instrument{} +) + +func (n nonrecordingSyncInt64Instrument) Counter(name string, opts ...instrument.Option) (syncint64.Counter, error) { + return n, nil +} + +func (n nonrecordingSyncInt64Instrument) UpDownCounter(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) { + return n, nil +} + +func (n nonrecordingSyncInt64Instrument) Histogram(name string, opts ...instrument.Option) (syncint64.Histogram, error) { + return n, nil +} + +func (nonrecordingSyncInt64Instrument) Add(context.Context, int64, ...attribute.KeyValue) { +} +func (nonrecordingSyncInt64Instrument) Record(context.Context, int64, ...attribute.KeyValue) { +} diff --git a/metric/nonrecording/meter.go b/metric/nonrecording/meter.go new file mode 100644 index 00000000000..2acea49d8a1 --- /dev/null +++ b/metric/nonrecording/meter.go @@ -0,0 +1,64 @@ +// 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 nonrecording // import "go.opentelemetry.io/otel/metric/nonrecording" + +import ( + "context" + + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" +) + +// NewNoopMeterProvider creates a MeterProvider that does not record any metrics. +func NewNoopMeterProvider() metric.MeterProvider { + return noopMeterProvider{} +} + +type noopMeterProvider struct{} + +var _ metric.MeterProvider = noopMeterProvider{} + +func (noopMeterProvider) Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter { + return noopMeter{} +} + +// NewNoopMeter creates a Meter that does not record any metrics. +func NewNoopMeter() metric.Meter { + return noopMeter{} +} + +type noopMeter struct{} + +var _ metric.Meter = noopMeter{} + +func (noopMeter) AsyncInt64() asyncint64.InstrumentProvider { + return nonrecordingAsyncInt64Instrument{} +} +func (noopMeter) AsyncFloat64() asyncfloat64.InstrumentProvider { + return nonrecordingAsyncFloat64Instrument{} +} +func (noopMeter) SyncInt64() syncint64.InstrumentProvider { + return nonrecordingSyncInt64Instrument{} +} +func (noopMeter) SyncFloat64() syncfloat64.InstrumentProvider { + return nonrecordingSyncFloat64Instrument{} +} +func (noopMeter) RegisterCallback([]instrument.Asynchronous, func(context.Context)) error { + return nil +} diff --git a/metric/noop.go b/metric/noop.go deleted file mode 100644 index 37c653f51a1..00000000000 --- a/metric/noop.go +++ /dev/null @@ -1,30 +0,0 @@ -// 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 metric // import "go.opentelemetry.io/otel/metric" - -type noopMeterProvider struct{} - -// NewNoopMeterProvider returns an implementation of MeterProvider that -// performs no operations. The Meter and Instrument created from the returned -// MeterProvider also perform no operations. -func NewNoopMeterProvider() MeterProvider { - return noopMeterProvider{} -} - -var _ MeterProvider = noopMeterProvider{} - -func (noopMeterProvider) Meter(instrumentationName string, opts ...MeterOption) Meter { - return Meter{} -} diff --git a/sdk/export/metric/aggregation/aggregation.go b/sdk/export/metric/aggregation/aggregation.go index 09b20306051..702c5b2bc82 100644 --- a/sdk/export/metric/aggregation/aggregation.go +++ b/sdk/export/metric/aggregation/aggregation.go @@ -15,8 +15,8 @@ package aggregation // import "go.opentelemetry.io/otel/sdk/export/metric/aggregation" import ( - "go.opentelemetry.io/otel/metric/number" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" ) // Deprecated: use module "go.opentelemetry.io/otel/sdk/metric/export/aggregation" diff --git a/sdk/export/metric/go.mod b/sdk/export/metric/go.mod index 8f728464774..415e71a87f9 100644 --- a/sdk/export/metric/go.mod +++ b/sdk/export/metric/go.mod @@ -41,7 +41,6 @@ replace go.opentelemetry.io/otel/trace => ../../../trace require ( go.opentelemetry.io/otel v1.4.1 - go.opentelemetry.io/otel/metric v0.27.0 go.opentelemetry.io/otel/sdk/metric v0.27.0 ) diff --git a/sdk/export/metric/metric.go b/sdk/export/metric/metric.go index 00d3f67b3bc..b3dccccd9dc 100644 --- a/sdk/export/metric/metric.go +++ b/sdk/export/metric/metric.go @@ -18,10 +18,10 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // Deprecated: use module "go.opentelemetry.io/otel/sdk/metric/export" diff --git a/sdk/metric/aggregator/aggregator.go b/sdk/metric/aggregator/aggregator.go index 85d2b3fbdb3..59d42b1a80a 100644 --- a/sdk/metric/aggregator/aggregator.go +++ b/sdk/metric/aggregator/aggregator.go @@ -19,9 +19,9 @@ import ( "fmt" "math" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // Aggregator implements a specific aggregation behavior, e.g., a diff --git a/sdk/metric/aggregator/aggregator_test.go b/sdk/metric/aggregator/aggregator_test.go index bf405b5c3f8..aab8393c932 100644 --- a/sdk/metric/aggregator/aggregator_test.go +++ b/sdk/metric/aggregator/aggregator_test.go @@ -21,13 +21,13 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) func TestInconsistentAggregatorErr(t *testing.T) { diff --git a/sdk/metric/aggregator/aggregatortest/test.go b/sdk/metric/aggregator/aggregatortest/test.go index 2d2a197e635..b9ea62da9bd 100644 --- a/sdk/metric/aggregator/aggregatortest/test.go +++ b/sdk/metric/aggregator/aggregatortest/test.go @@ -26,11 +26,11 @@ import ( "github.com/stretchr/testify/require" ottest "go.opentelemetry.io/otel/internal/internaltest" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) const Magnitude = 1000 diff --git a/sdk/metric/aggregator/histogram/benchmark_test.go b/sdk/metric/aggregator/histogram/benchmark_test.go index 4902c11b7e5..597af3eb714 100644 --- a/sdk/metric/aggregator/histogram/benchmark_test.go +++ b/sdk/metric/aggregator/histogram/benchmark_test.go @@ -19,10 +19,10 @@ import ( "math/rand" "testing" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) const inputRange = 1e6 diff --git a/sdk/metric/aggregator/histogram/histogram.go b/sdk/metric/aggregator/histogram/histogram.go index 142ca24ebef..f4d94b38c5e 100644 --- a/sdk/metric/aggregator/histogram/histogram.go +++ b/sdk/metric/aggregator/histogram/histogram.go @@ -19,10 +19,10 @@ import ( "sort" "sync" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // Note: This code uses a Mutex to govern access to the exclusive diff --git a/sdk/metric/aggregator/histogram/histogram_test.go b/sdk/metric/aggregator/histogram/histogram_test.go index 00acf2e772d..b22bd149e5e 100644 --- a/sdk/metric/aggregator/histogram/histogram_test.go +++ b/sdk/metric/aggregator/histogram/histogram_test.go @@ -23,11 +23,11 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) const count = 100 diff --git a/sdk/metric/aggregator/lastvalue/lastvalue.go b/sdk/metric/aggregator/lastvalue/lastvalue.go index 59a7ac8236a..7e88f6b8db3 100644 --- a/sdk/metric/aggregator/lastvalue/lastvalue.go +++ b/sdk/metric/aggregator/lastvalue/lastvalue.go @@ -20,10 +20,10 @@ import ( "time" "unsafe" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( diff --git a/sdk/metric/aggregator/lastvalue/lastvalue_test.go b/sdk/metric/aggregator/lastvalue/lastvalue_test.go index 3d105a75cd2..16f9614c25a 100644 --- a/sdk/metric/aggregator/lastvalue/lastvalue_test.go +++ b/sdk/metric/aggregator/lastvalue/lastvalue_test.go @@ -25,11 +25,11 @@ import ( "github.com/stretchr/testify/require" ottest "go.opentelemetry.io/otel/internal/internaltest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) const count = 100 diff --git a/sdk/metric/aggregator/sum/sum.go b/sdk/metric/aggregator/sum/sum.go index be3c6dfff89..d5c70e59bdf 100644 --- a/sdk/metric/aggregator/sum/sum.go +++ b/sdk/metric/aggregator/sum/sum.go @@ -17,10 +17,10 @@ package sum // import "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" import ( "context" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // Aggregator aggregates counter events. diff --git a/sdk/metric/aggregator/sum/sum_test.go b/sdk/metric/aggregator/sum/sum_test.go index 59480e2c8cc..c92594a460c 100644 --- a/sdk/metric/aggregator/sum/sum_test.go +++ b/sdk/metric/aggregator/sum/sum_test.go @@ -22,10 +22,10 @@ import ( "github.com/stretchr/testify/require" ottest "go.opentelemetry.io/otel/internal/internaltest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) const count = 100 diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go index c4ecf306293..fd5f49bd1ef 100644 --- a/sdk/metric/benchmark_test.go +++ b/sdk/metric/benchmark_test.go @@ -22,11 +22,13 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" sdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type benchFixture struct { @@ -44,7 +46,7 @@ func newFixture(b *testing.B) *benchFixture { } bf.accumulator = sdk.NewAccumulator(bf) - bf.meter = metric.WrapMeterImpl(bf.accumulator) + bf.meter = sdkapi.WrapMeterImpl(bf.accumulator) return bf } @@ -56,8 +58,33 @@ func (f *benchFixture) Meter(_ string, _ ...metric.MeterOption) metric.Meter { return f.meter } -func (f *benchFixture) meterMust() metric.MeterMust { - return metric.Must(f.meter) +func (f *benchFixture) iCounter(name string) syncint64.Counter { + ctr, err := f.meter.SyncInt64().Counter(name) + if err != nil { + f.B.Error(err) + } + return ctr +} +func (f *benchFixture) fCounter(name string) syncfloat64.Counter { + ctr, err := f.meter.SyncFloat64().Counter(name) + if err != nil { + f.B.Error(err) + } + return ctr +} +func (f *benchFixture) iHistogram(name string) syncint64.Histogram { + ctr, err := f.meter.SyncInt64().Histogram(name) + if err != nil { + f.B.Error(err) + } + return ctr +} +func (f *benchFixture) fHistogram(name string) syncfloat64.Histogram { + ctr, err := f.meter.SyncFloat64().Histogram(name) + if err != nil { + f.B.Error(err) + } + return ctr } func makeLabels(n int) []attribute.KeyValue { @@ -81,7 +108,7 @@ func benchmarkLabels(b *testing.B, n int) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(n) - cnt := fix.meterMust().NewInt64Counter("int64.sum") + cnt := fix.iCounter("int64.sum") b.ResetTimer() @@ -154,31 +181,33 @@ func BenchmarkIterator_16(b *testing.B) { // Counters -func BenchmarkGlobalInt64CounterAddWithSDK(b *testing.B) { - // Compare with BenchmarkInt64CounterAdd() to see overhead of global - // package. This is in the SDK to avoid the API from depending on the - // SDK. - ctx := context.Background() - fix := newFixture(b) +// TODO readd global - sdk := global.Meter("test") - global.SetMeterProvider(fix) +// func BenchmarkGlobalInt64CounterAddWithSDK(b *testing.B) { +// // Compare with BenchmarkInt64CounterAdd() to see overhead of global +// // package. This is in the SDK to avoid the API from depending on the +// // SDK. +// ctx := context.Background() +// fix := newFixture(b) - labs := []attribute.KeyValue{attribute.String("A", "B")} - cnt := Must(sdk).NewInt64Counter("int64.sum") +// sdk := global.Meter("test") +// global.SetMeterProvider(fix) - b.ResetTimer() +// labs := []attribute.KeyValue{attribute.String("A", "B")} +// cnt := Must(sdk).NewInt64Counter("int64.sum") - for i := 0; i < b.N; i++ { - cnt.Add(ctx, 1, labs...) - } -} +// b.ResetTimer() + +// for i := 0; i < b.N; i++ { +// cnt.Add(ctx, 1, labs...) +// } +// } func BenchmarkInt64CounterAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - cnt := fix.meterMust().NewInt64Counter("int64.sum") + cnt := fix.iCounter("int64.sum") b.ResetTimer() @@ -191,7 +220,7 @@ func BenchmarkFloat64CounterAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - cnt := fix.meterMust().NewFloat64Counter("float64.sum") + cnt := fix.fCounter("float64.sum") b.ResetTimer() @@ -206,7 +235,7 @@ func BenchmarkInt64LastValueAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - mea := fix.meterMust().NewInt64Histogram("int64.lastvalue") + mea := fix.iHistogram("int64.lastvalue") b.ResetTimer() @@ -219,7 +248,7 @@ func BenchmarkFloat64LastValueAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - mea := fix.meterMust().NewFloat64Histogram("float64.lastvalue") + mea := fix.fHistogram("float64.lastvalue") b.ResetTimer() @@ -234,7 +263,7 @@ func BenchmarkInt64HistogramAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - mea := fix.meterMust().NewInt64Histogram("int64.histogram") + mea := fix.iHistogram("int64.histogram") b.ResetTimer() @@ -247,7 +276,7 @@ func BenchmarkFloat64HistogramAdd(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - mea := fix.meterMust().NewFloat64Histogram("float64.histogram") + mea := fix.fHistogram("float64.histogram") b.ResetTimer() @@ -264,12 +293,12 @@ func BenchmarkObserverRegistration(b *testing.B) { for i := 0; i < b.N; i++ { names = append(names, fmt.Sprintf("test.%d.lastvalue", i)) } - cb := func(_ context.Context, result metric.Int64ObserverResult) {} b.ResetTimer() for i := 0; i < b.N; i++ { - fix.meterMust().NewInt64GaugeObserver(names[i], cb) + ctr, _ := fix.meter.AsyncInt64().Counter(names[i]) + _ = fix.meter.RegisterCallback([]instrument.Asynchronous{ctr}, func(context.Context) {}) } } @@ -277,11 +306,16 @@ func BenchmarkGaugeObserverObservationInt64(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - _ = fix.meterMust().NewInt64GaugeObserver("test.lastvalue", func(_ context.Context, result metric.Int64ObserverResult) { + ctr, _ := fix.meter.AsyncInt64().Counter("test.lastvalue") + err := fix.meter.RegisterCallback([]instrument.Asynchronous{ctr}, func(ctx context.Context) { for i := 0; i < b.N; i++ { - result.Observe((int64)(i), labs...) + ctr.Observe(ctx, (int64)(i), labs...) } }) + if err != nil { + b.Errorf("could not register callback: %v", err) + b.FailNow() + } b.ResetTimer() @@ -292,11 +326,16 @@ func BenchmarkGaugeObserverObservationFloat64(b *testing.B) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(1) - _ = fix.meterMust().NewFloat64GaugeObserver("test.lastvalue", func(_ context.Context, result metric.Float64ObserverResult) { + ctr, _ := fix.meter.AsyncFloat64().Counter("test.lastvalue") + err := fix.meter.RegisterCallback([]instrument.Asynchronous{ctr}, func(ctx context.Context) { for i := 0; i < b.N; i++ { - result.Observe((float64)(i), labs...) + ctr.Observe(ctx, (float64)(i), labs...) } }) + if err != nil { + b.Errorf("could not register callback: %v", err) + b.FailNow() + } b.ResetTimer() @@ -310,17 +349,18 @@ func benchmarkBatchRecord8Labels(b *testing.B, numInst int) { ctx := context.Background() fix := newFixture(b) labs := makeLabels(numLabels) - var meas []sdkapi.Measurement + var meas []syncint64.Counter for i := 0; i < numInst; i++ { - inst := fix.meterMust().NewInt64Counter(fmt.Sprintf("int64.%d.sum", i)) - meas = append(meas, inst.Measurement(1)) + meas = append(meas, fix.iCounter(fmt.Sprintf("int64.%d.sum", i))) } b.ResetTimer() for i := 0; i < b.N; i++ { - fix.accumulator.RecordBatch(ctx, labs, meas...) + for _, ctr := range meas { + ctr.Add(ctx, 1, labs...) + } } } @@ -346,7 +386,7 @@ func BenchmarkRepeatedDirectCalls(b *testing.B) { ctx := context.Background() fix := newFixture(b) - c := fix.meterMust().NewInt64Counter("int64.sum") + c := fix.iCounter("int64.sum") k := attribute.String("bench", "true") b.ResetTimer() diff --git a/sdk/metric/controller/basic/controller.go b/sdk/metric/controller/basic/controller.go index ee694056228..de0da5484c9 100644 --- a/sdk/metric/controller/basic/controller.go +++ b/sdk/metric/controller/basic/controller.go @@ -21,12 +21,13 @@ import ( "time" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/internal/metric/registry" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdk "go.opentelemetry.io/otel/sdk/metric" controllerTime "go.opentelemetry.io/otel/sdk/metric/controller/time" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/registry" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -99,7 +100,7 @@ func (c *Controller) Meter(instrumentationName string, opts ...metric.MeterOptio library: library, })) } - return metric.WrapMeterImpl(m.(*registry.UniqueInstrumentMeterImpl)) + return sdkapi.WrapMeterImpl(m.(*registry.UniqueInstrumentMeterImpl)) } type accumulatorCheckpointer struct { @@ -108,6 +109,8 @@ type accumulatorCheckpointer struct { library instrumentation.Library } +var _ sdkapi.MeterImpl = &accumulatorCheckpointer{} + // New constructs a Controller using the provided checkpointer factory // and options (including optional exporter) to configure a metric // export pipeline. diff --git a/sdk/metric/controller/basic/controller_test.go b/sdk/metric/controller/basic/controller_test.go index 503a278148c..26dcf4bb286 100644 --- a/sdk/metric/controller/basic/controller_test.go +++ b/sdk/metric/controller/basic/controller_test.go @@ -25,8 +25,7 @@ import ( "go.opentelemetry.io/otel/attribute" ottest "go.opentelemetry.io/otel/internal/internaltest" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/instrumentation" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/controller/controllertest" @@ -34,6 +33,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/export/aggregation" processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -127,7 +127,7 @@ func TestControllerUsesResource(t *testing.T) { ctx := context.Background() require.NoError(t, cont.Start(ctx)) - ctr := metric.Must(cont.Meter("named")).NewFloat64Counter("calls.sum") + ctr, _ := cont.Meter("named").SyncFloat64().Counter("calls.sum") ctr.Add(context.Background(), 1.) // Collect once @@ -152,16 +152,19 @@ func TestStartNoExporter(t *testing.T) { ) mock := controllertest.NewMockClock() cont.SetClock(mock) + meter := cont.Meter("go.opentelemetry.io/otel/sdk/metric/controller/basic_test#StartNoExporter") calls := int64(0) - _ = metric.Must(cont.Meter("named")).NewInt64CounterObserver("calls.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - calls++ - checkTestContext(t, ctx) - result.Observe(calls, attribute.String("A", "B")) - }, - ) + counterObserver, err := meter.AsyncInt64().Counter("calls.lastvalue") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + calls++ + checkTestContext(t, ctx) + counterObserver.Observe(ctx, calls, attribute.String("A", "B")) + }) + require.NoError(t, err) // Collect() has not been called. The controller is unstarted. expect := map[string]float64{} @@ -220,18 +223,22 @@ func TestObserverCanceled(t *testing.T) { controller.WithCollectTimeout(time.Millisecond), controller.WithResource(resource.Empty()), ) + meter := cont.Meter("go.opentelemetry.io/otel/sdk/metric/controller/basic_test#ObserverCanceled") calls := int64(0) - _ = metric.Must(cont.Meter("named")).NewInt64CounterObserver("done.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - <-ctx.Done() - calls++ - result.Observe(calls) - }, - ) + counterObserver, err := meter.AsyncInt64().Counter("done.lastvalue") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + <-ctx.Done() + calls++ + counterObserver.Observe(ctx, calls) + }) + require.NoError(t, err) + // This relies on the context timing out - err := cont.Collect(context.Background()) + err = cont.Collect(context.Background()) require.Error(t, err) require.True(t, errors.Is(err, context.DeadlineExceeded)) @@ -251,14 +258,18 @@ func TestObserverContext(t *testing.T) { controller.WithCollectTimeout(0), controller.WithResource(resource.Empty()), ) + meter := cont.Meter("go.opentelemetry.io/otel/sdk/metric/controller/basic_test#ObserverContext") + + counterObserver, err := meter.AsyncInt64().Counter("done.lastvalue") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + time.Sleep(10 * time.Millisecond) + checkTestContext(t, ctx) + counterObserver.Observe(ctx, 1) + }) + require.NoError(t, err) - _ = metric.Must(cont.Meter("named")).NewInt64CounterObserver("done.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - time.Sleep(10 * time.Millisecond) - checkTestContext(t, ctx) - result.Observe(1) - }, - ) ctx := testContext() require.NoError(t, cont.Collect(ctx)) @@ -314,14 +325,17 @@ func TestExportTimeout(t *testing.T) { ) mock := controllertest.NewMockClock() cont.SetClock(mock) + meter := cont.Meter("go.opentelemetry.io/otel/sdk/metric/controller/basic_test#ExportTimeout") calls := int64(0) - _ = metric.Must(cont.Meter("named")).NewInt64CounterObserver("one.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - calls++ - result.Observe(calls) - }, - ) + counterObserver, err := meter.AsyncInt64().Counter("one.lastvalue") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + calls++ + counterObserver.Observe(ctx, calls) + }) + require.NoError(t, err) require.NoError(t, cont.Start(context.Background())) @@ -332,7 +346,7 @@ func TestExportTimeout(t *testing.T) { // Collect after 1s, timeout mock.Add(time.Second) - err := testHandler.Flush() + err = testHandler.Flush() require.Error(t, err) require.True(t, errors.Is(err, context.DeadlineExceeded)) @@ -369,13 +383,17 @@ func TestCollectAfterStopThenStartAgain(t *testing.T) { mock := controllertest.NewMockClock() cont.SetClock(mock) + meter := cont.Meter("go.opentelemetry.io/otel/sdk/metric/controller/basic_test#CollectAfterStopThenStartAgain") + calls := 0 - _ = metric.Must(cont.Meter("named")).NewInt64CounterObserver("one.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - calls++ - result.Observe(int64(calls)) - }, - ) + counterObserver, err := meter.AsyncInt64().Counter("one.lastvalue") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + calls++ + counterObserver.Observe(ctx, int64(calls)) + }) + require.NoError(t, err) // No collections happen (because mock clock does not advance): require.NoError(t, cont.Start(context.Background())) @@ -403,7 +421,7 @@ func TestCollectAfterStopThenStartAgain(t *testing.T) { // explicit collection should still fail. require.NoError(t, cont.Start(context.Background())) require.True(t, cont.IsRunning()) - err := cont.Collect(context.Background()) + err = cont.Collect(context.Background()) require.Error(t, err) require.Equal(t, controller.ErrControllerStarted, err) @@ -452,10 +470,10 @@ func TestRegistryFunction(t *testing.T) { require.NotNil(t, m1) require.Equal(t, m1, m2) - c1, err := m1.NewInt64Counter("counter.sum") + c1, err := m1.SyncInt64().Counter("counter.sum") require.NoError(t, err) - c2, err := m1.NewInt64Counter("counter.sum") + c2, err := m1.SyncInt64().Counter("counter.sum") require.NoError(t, err) require.Equal(t, c1, c2) diff --git a/sdk/metric/controller/basic/pull_test.go b/sdk/metric/controller/basic/pull_test.go index 96d1e1adad1..2284e68a71d 100644 --- a/sdk/metric/controller/basic/pull_test.go +++ b/sdk/metric/controller/basic/pull_test.go @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/controller/controllertest" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" @@ -45,7 +44,8 @@ func TestPullNoCollect(t *testing.T) { ctx := context.Background() meter := puller.Meter("nocache") - counter := metric.Must(meter).NewInt64Counter("counter.sum") + counter, err := meter.SyncInt64().Counter("counter.sum") + require.NoError(t, err) counter.Add(ctx, 10, attribute.String("A", "B")) @@ -83,7 +83,8 @@ func TestPullWithCollect(t *testing.T) { ctx := context.Background() meter := puller.Meter("nocache") - counter := metric.Must(meter).NewInt64Counter("counter.sum") + counter, err := meter.SyncInt64().Counter("counter.sum") + require.NoError(t, err) counter.Add(ctx, 10, attribute.String("A", "B")) diff --git a/sdk/metric/controller/basic/push_test.go b/sdk/metric/controller/basic/push_test.go index 0a6679b29de..67bbddfdc84 100644 --- a/sdk/metric/controller/basic/push_test.go +++ b/sdk/metric/controller/basic/push_test.go @@ -27,7 +27,6 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" "go.opentelemetry.io/otel/sdk/metric/controller/controllertest" "go.opentelemetry.io/otel/sdk/metric/export" @@ -117,7 +116,8 @@ func TestPushTicker(t *testing.T) { ctx := context.Background() - counter := metric.Must(meter).NewInt64Counter("counter.sum") + counter, err := meter.SyncInt64().Counter("counter.sum") + require.NoError(t, err) require.NoError(t, p.Start(ctx)) @@ -197,8 +197,10 @@ func TestPushExportError(t *testing.T) { ctx := context.Background() meter := p.Meter("name") - counter1 := metric.Must(meter).NewInt64Counter("counter1.sum") - counter2 := metric.Must(meter).NewInt64Counter("counter2.sum") + counter1, err := meter.SyncInt64().Counter("counter1.sum") + require.NoError(t, err) + counter2, err := meter.SyncInt64().Counter("counter2.sum") + require.NoError(t, err) require.NoError(t, p.Start(ctx)) runtime.Gosched() diff --git a/sdk/metric/correct_test.go b/sdk/metric/correct_test.go index 25e7fc60875..1b24a209c76 100644 --- a/sdk/metric/correct_test.go +++ b/sdk/metric/correct_test.go @@ -26,16 +26,17 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/nonrecording" metricsdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) -var Must = metric.Must - type handler struct { sync.Mutex err error @@ -88,7 +89,7 @@ func newSDK(t *testing.T) (metric.Meter, *metricsdk.Accumulator, *testSelector, accum := metricsdk.NewAccumulator( processor, ) - meter := metric.WrapMeterImpl(accum) + meter := sdkapi.WrapMeterImpl(accum) return meter, accum, testSelector, processor } @@ -96,7 +97,8 @@ func TestInputRangeCounter(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - counter := Must(meter).NewInt64Counter("name.sum") + counter, err := meter.SyncInt64().Counter("name.sum") + require.NoError(t, err) counter.Add(ctx, -1) require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) @@ -118,7 +120,8 @@ func TestInputRangeUpDownCounter(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - counter := Must(meter).NewInt64UpDownCounter("name.sum") + counter, err := meter.SyncInt64().UpDownCounter("name.sum") + require.NoError(t, err) counter.Add(ctx, -1) counter.Add(ctx, -1) @@ -137,7 +140,8 @@ func TestInputRangeHistogram(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - histogram := Must(meter).NewFloat64Histogram("name.histogram") + histogram, err := meter.SyncFloat64().Histogram("name.histogram") + require.NoError(t, err) histogram.Record(ctx, math.NaN()) require.Equal(t, aggregation.ErrNaNInput, testHandler.Flush()) @@ -162,7 +166,8 @@ func TestDisabledInstrument(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - histogram := Must(meter).NewFloat64Histogram("name.disabled") + histogram, err := meter.SyncFloat64().Histogram("name.disabled") + require.NoError(t, err) histogram.Record(ctx, -1) checkpointed := sdk.Collect(ctx) @@ -175,7 +180,8 @@ func TestRecordNaN(t *testing.T) { ctx := context.Background() meter, _, _, _ := newSDK(t) - c := Must(meter).NewFloat64Counter("name.sum") + c, err := meter.SyncFloat64().Counter("name.sum") + require.NoError(t, err) require.Nil(t, testHandler.Flush()) c.Add(ctx, math.NaN()) @@ -186,7 +192,8 @@ func TestSDKLabelsDeduplication(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - counter := Must(meter).NewInt64Counter("name.sum") + counter, err := meter.SyncInt64().Counter("name.sum") + require.NoError(t, err) const ( maxKeys = 21 @@ -277,48 +284,86 @@ func TestObserverCollection(t *testing.T) { meter, sdk, _, processor := newSDK(t) mult := 1 - _ = Must(meter).NewFloat64GaugeObserver("float.gauge.lastvalue", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(float64(mult), attribute.String("A", "B")) + gaugeF, err := meter.AsyncFloat64().Gauge("float.gauge.lastvalue") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + gaugeF, + }, func(ctx context.Context) { + gaugeF.Observe(ctx, float64(mult), attribute.String("A", "B")) // last value wins - result.Observe(float64(-mult), attribute.String("A", "B")) - result.Observe(float64(-mult), attribute.String("C", "D")) + gaugeF.Observe(ctx, float64(-mult), attribute.String("A", "B")) + gaugeF.Observe(ctx, float64(-mult), attribute.String("C", "D")) }) - _ = Must(meter).NewInt64GaugeObserver("int.gauge.lastvalue", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(int64(-mult), attribute.String("A", "B")) - result.Observe(int64(mult)) + require.NoError(t, err) + + gaugeI, err := meter.AsyncInt64().Gauge("int.gauge.lastvalue") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + gaugeI, + }, func(ctx context.Context) { + gaugeI.Observe(ctx, int64(-mult), attribute.String("A", "B")) + gaugeI.Observe(ctx, int64(mult)) // last value wins - result.Observe(int64(mult), attribute.String("A", "B")) - result.Observe(int64(mult)) + gaugeI.Observe(ctx, int64(mult), attribute.String("A", "B")) + gaugeI.Observe(ctx, int64(mult)) }) - - _ = Must(meter).NewFloat64CounterObserver("float.counterobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(float64(mult), attribute.String("A", "B")) - result.Observe(float64(2*mult), attribute.String("A", "B")) - result.Observe(float64(mult), attribute.String("C", "D")) + require.NoError(t, err) + + counterF, err := meter.AsyncFloat64().Counter("float.counterobserver.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + counterF, + }, func(ctx context.Context) { + counterF.Observe(ctx, float64(mult), attribute.String("A", "B")) + counterF.Observe(ctx, float64(2*mult), attribute.String("A", "B")) + counterF.Observe(ctx, float64(mult), attribute.String("C", "D")) }) - _ = Must(meter).NewInt64CounterObserver("int.counterobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(int64(2*mult), attribute.String("A", "B")) - result.Observe(int64(mult)) + require.NoError(t, err) + + counterI, err := meter.AsyncInt64().Counter("int.counterobserver.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + counterI, + }, func(ctx context.Context) { + counterI.Observe(ctx, int64(2*mult), attribute.String("A", "B")) + counterI.Observe(ctx, int64(mult)) // last value wins - result.Observe(int64(mult), attribute.String("A", "B")) - result.Observe(int64(mult)) + counterI.Observe(ctx, int64(mult), attribute.String("A", "B")) + counterI.Observe(ctx, int64(mult)) }) - - _ = Must(meter).NewFloat64UpDownCounterObserver("float.updowncounterobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(float64(mult), attribute.String("A", "B")) - result.Observe(float64(-2*mult), attribute.String("A", "B")) - result.Observe(float64(mult), attribute.String("C", "D")) + require.NoError(t, err) + + updowncounterF, err := meter.AsyncFloat64().UpDownCounter("float.updowncounterobserver.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + updowncounterF, + }, func(ctx context.Context) { + updowncounterF.Observe(ctx, float64(mult), attribute.String("A", "B")) + updowncounterF.Observe(ctx, float64(-2*mult), attribute.String("A", "B")) + updowncounterF.Observe(ctx, float64(mult), attribute.String("C", "D")) }) - _ = Must(meter).NewInt64UpDownCounterObserver("int.updowncounterobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(int64(2*mult), attribute.String("A", "B")) - result.Observe(int64(mult)) + require.NoError(t, err) + + updowncounterI, err := meter.AsyncInt64().UpDownCounter("int.updowncounterobserver.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + updowncounterI, + }, func(ctx context.Context) { + updowncounterI.Observe(ctx, int64(2*mult), attribute.String("A", "B")) + updowncounterI.Observe(ctx, int64(mult)) // last value wins - result.Observe(int64(mult), attribute.String("A", "B")) - result.Observe(int64(-mult)) + updowncounterI.Observe(ctx, int64(mult), attribute.String("A", "B")) + updowncounterI.Observe(ctx, int64(-mult)) }) + require.NoError(t, err) - _ = Must(meter).NewInt64GaugeObserver("empty.gauge.sum", func(_ context.Context, result metric.Int64ObserverResult) { + unused, err := meter.AsyncInt64().Gauge("empty.gauge.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ + unused, + }, func(ctx context.Context) { }) + require.NoError(t, err) for mult = 0; mult < 3; mult++ { processor.Reset() @@ -333,15 +378,15 @@ func TestObserverCollection(t *testing.T) { "int.gauge.lastvalue//": mult, "int.gauge.lastvalue/A=B/": mult, - "float.counterobserver.sum/A=B/": 2 * mult, + "float.counterobserver.sum/A=B/": 3 * mult, "float.counterobserver.sum/C=D/": mult, - "int.counterobserver.sum//": mult, - "int.counterobserver.sum/A=B/": mult, + "int.counterobserver.sum//": 2 * mult, + "int.counterobserver.sum/A=B/": 3 * mult, - "float.updowncounterobserver.sum/A=B/": -2 * mult, + "float.updowncounterobserver.sum/A=B/": -mult, "float.updowncounterobserver.sum/C=D/": mult, - "int.updowncounterobserver.sum//": -mult, - "int.updowncounterobserver.sum/A=B/": mult, + "int.updowncounterobserver.sum//": 0, + "int.updowncounterobserver.sum/A=B/": 3 * mult, }, processor.Values()) } } @@ -351,18 +396,26 @@ func TestCounterObserverInputRange(t *testing.T) { meter, sdk, _, processor := newSDK(t) // TODO: these tests are testing for negative values, not for _descending values_. Fix. - _ = Must(meter).NewFloat64CounterObserver("float.counterobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { - result.Observe(-2, attribute.String("A", "B")) + counterF, _ := meter.AsyncFloat64().Counter("float.counterobserver.sum") + err := meter.RegisterCallback([]instrument.Asynchronous{ + counterF, + }, func(ctx context.Context) { + counterF.Observe(ctx, -2, attribute.String("A", "B")) require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) - result.Observe(-1, attribute.String("C", "D")) + counterF.Observe(ctx, -1, attribute.String("C", "D")) require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) }) - _ = Must(meter).NewInt64CounterObserver("int.counterobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(-1, attribute.String("A", "B")) + require.NoError(t, err) + counterI, _ := meter.AsyncInt64().Counter("int.counterobserver.sum") + err = meter.RegisterCallback([]instrument.Asynchronous{ + counterI, + }, func(ctx context.Context) { + counterI.Observe(ctx, -1, attribute.String("A", "B")) require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) - result.Observe(-1) + counterI.Observe(ctx, -1) require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) }) + require.NoError(t, err) collected := sdk.Collect(ctx) @@ -377,51 +430,43 @@ func TestObserverBatch(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - var floatGaugeObs metric.Float64GaugeObserver - var intGaugeObs metric.Int64GaugeObserver - var floatCounterObs metric.Float64CounterObserver - var intCounterObs metric.Int64CounterObserver - var floatUpDownCounterObs metric.Float64UpDownCounterObserver - var intUpDownCounterObs metric.Int64UpDownCounterObserver - - var batch = Must(meter).NewBatchObserver( - func(_ context.Context, result metric.BatchObserverResult) { - result.Observe( - []attribute.KeyValue{ - attribute.String("A", "B"), - }, - floatGaugeObs.Observation(1), - floatGaugeObs.Observation(-1), - intGaugeObs.Observation(-1), - intGaugeObs.Observation(1), - floatCounterObs.Observation(1000), - intCounterObs.Observation(100), - floatUpDownCounterObs.Observation(-1000), - intUpDownCounterObs.Observation(-100), - ) - result.Observe( - []attribute.KeyValue{ - attribute.String("C", "D"), - }, - floatGaugeObs.Observation(-1), - floatCounterObs.Observation(-1), - floatUpDownCounterObs.Observation(-1), - ) - result.Observe( - nil, - intGaugeObs.Observation(1), - intGaugeObs.Observation(1), - intCounterObs.Observation(10), - floatCounterObs.Observation(1.1), - intUpDownCounterObs.Observation(10), - ) - }) - floatGaugeObs = batch.NewFloat64GaugeObserver("float.gauge.lastvalue") - intGaugeObs = batch.NewInt64GaugeObserver("int.gauge.lastvalue") - floatCounterObs = batch.NewFloat64CounterObserver("float.counterobserver.sum") - intCounterObs = batch.NewInt64CounterObserver("int.counterobserver.sum") - floatUpDownCounterObs = batch.NewFloat64UpDownCounterObserver("float.updowncounterobserver.sum") - intUpDownCounterObs = batch.NewInt64UpDownCounterObserver("int.updowncounterobserver.sum") + floatGaugeObs, _ := meter.AsyncFloat64().Gauge("float.gauge.lastvalue") + intGaugeObs, _ := meter.AsyncInt64().Gauge("int.gauge.lastvalue") + floatCounterObs, _ := meter.AsyncFloat64().Counter("float.counterobserver.sum") + intCounterObs, _ := meter.AsyncInt64().Counter("int.counterobserver.sum") + floatUpDownCounterObs, _ := meter.AsyncFloat64().UpDownCounter("float.updowncounterobserver.sum") + intUpDownCounterObs, _ := meter.AsyncInt64().UpDownCounter("int.updowncounterobserver.sum") + + err := meter.RegisterCallback([]instrument.Asynchronous{ + floatGaugeObs, + intGaugeObs, + floatCounterObs, + intCounterObs, + floatUpDownCounterObs, + intUpDownCounterObs, + }, func(ctx context.Context) { + ab := attribute.String("A", "B") + floatGaugeObs.Observe(ctx, 1, ab) + floatGaugeObs.Observe(ctx, -1, ab) + intGaugeObs.Observe(ctx, -1, ab) + intGaugeObs.Observe(ctx, 1, ab) + floatCounterObs.Observe(ctx, 1000, ab) + intCounterObs.Observe(ctx, 100, ab) + floatUpDownCounterObs.Observe(ctx, -1000, ab) + intUpDownCounterObs.Observe(ctx, -100, ab) + + cd := attribute.String("C", "D") + floatGaugeObs.Observe(ctx, -1, cd) + floatCounterObs.Observe(ctx, -1, cd) + floatUpDownCounterObs.Observe(ctx, -1, cd) + + intGaugeObs.Observe(ctx, 1) + intGaugeObs.Observe(ctx, 1) + intCounterObs.Observe(ctx, 10) + floatCounterObs.Observe(ctx, 1.1) + intUpDownCounterObs.Observe(ctx, 10) + }) + require.NoError(t, err) collected := sdk.Collect(ctx) @@ -445,37 +490,6 @@ func TestObserverBatch(t *testing.T) { }, processor.Values()) } -func TestRecordBatch(t *testing.T) { - ctx := context.Background() - meter, sdk, _, processor := newSDK(t) - - counter1 := Must(meter).NewInt64Counter("int64.sum") - counter2 := Must(meter).NewFloat64Counter("float64.sum") - histogram1 := Must(meter).NewInt64Histogram("int64.histogram") - histogram2 := Must(meter).NewFloat64Histogram("float64.histogram") - - sdk.RecordBatch( - ctx, - []attribute.KeyValue{ - attribute.String("A", "B"), - attribute.String("C", "D"), - }, - counter1.Measurement(1), - counter2.Measurement(2), - histogram1.Measurement(3), - histogram2.Measurement(4), - ) - - sdk.Collect(ctx) - - require.EqualValues(t, map[string]float64{ - "int64.sum/A=B,C=D/": 1, - "float64.sum/A=B,C=D/": 2, - "int64.histogram/A=B,C=D/": 3, - "float64.histogram/A=B,C=D/": 4, - }, processor.Values()) -} - // TestRecordPersistence ensures that a direct-called instrument that // is repeatedly used each interval results in a persistent record, so // that its encoded labels will be cached across collection intervals. @@ -483,7 +497,9 @@ func TestRecordPersistence(t *testing.T) { ctx := context.Background() meter, sdk, selector, _ := newSDK(t) - c := Must(meter).NewFloat64Counter("name.sum") + c, err := meter.SyncFloat64().Counter("name.sum") + require.NoError(t, err) + uk := attribute.String("bound", "false") for i := 0; i < 100; i++ { @@ -497,51 +513,58 @@ func TestRecordPersistence(t *testing.T) { func TestIncorrectInstruments(t *testing.T) { // The Batch observe/record APIs are susceptible to // uninitialized instruments. - var counter metric.Int64Counter - var observer metric.Int64GaugeObserver + var observer asyncint64.Gauge ctx := context.Background() meter, sdk, _, processor := newSDK(t) // Now try with uninitialized instruments. - meter.RecordBatch(ctx, nil, counter.Measurement(1)) - meter.NewBatchObserver(func(_ context.Context, result metric.BatchObserverResult) { - result.Observe(nil, observer.Observation(1)) + err := meter.RegisterCallback([]instrument.Asynchronous{ + observer, + }, func(ctx context.Context) { + observer.Observe(ctx, 1) }) + require.ErrorIs(t, err, metricsdk.ErrBadInstrument) collected := sdk.Collect(ctx) - require.Equal(t, metricsdk.ErrUninitializedInstrument, testHandler.Flush()) + err = testHandler.Flush() + require.NoError(t, err) require.Equal(t, 0, collected) // Now try with instruments from another SDK. - var noopMeter metric.Meter - counter = metric.Must(noopMeter).NewInt64Counter("name.sum") - observer = metric.Must(noopMeter).NewBatchObserver( - func(context.Context, metric.BatchObserverResult) {}, - ).NewInt64GaugeObserver("observer") - - meter.RecordBatch(ctx, nil, counter.Measurement(1)) - meter.NewBatchObserver(func(_ context.Context, result metric.BatchObserverResult) { - result.Observe(nil, observer.Observation(1)) - }) + noopMeter := nonrecording.NewNoopMeter() + observer, _ = noopMeter.AsyncInt64().Gauge("observer") + + err = meter.RegisterCallback( + []instrument.Asynchronous{observer}, + func(ctx context.Context) { + observer.Observe(ctx, 1) + }, + ) + require.ErrorIs(t, err, metricsdk.ErrBadInstrument) collected = sdk.Collect(ctx) require.Equal(t, 0, collected) require.EqualValues(t, map[string]float64{}, processor.Values()) - require.Equal(t, metricsdk.ErrUninitializedInstrument, testHandler.Flush()) + + err = testHandler.Flush() + require.NoError(t, err) } func TestSyncInAsync(t *testing.T) { ctx := context.Background() meter, sdk, _, processor := newSDK(t) - counter := Must(meter).NewFloat64Counter("counter.sum") - _ = Must(meter).NewInt64GaugeObserver("observer.lastvalue", - func(ctx context.Context, result metric.Int64ObserverResult) { - result.Observe(10) - counter.Add(ctx, 100) - }, - ) + counter, _ := meter.SyncFloat64().Counter("counter.sum") + gauge, _ := meter.AsyncInt64().Gauge("observer.lastvalue") + + err := meter.RegisterCallback([]instrument.Asynchronous{ + gauge, + }, func(ctx context.Context) { + gauge.Observe(ctx, 10) + counter.Add(ctx, 100) + }) + require.NoError(t, err) sdk.Collect(ctx) diff --git a/sdk/metric/export/aggregation/aggregation.go b/sdk/metric/export/aggregation/aggregation.go index 44c8c3320e2..ea09fa68344 100644 --- a/sdk/metric/export/aggregation/aggregation.go +++ b/sdk/metric/export/aggregation/aggregation.go @@ -18,7 +18,7 @@ import ( "fmt" "time" - "go.opentelemetry.io/otel/metric/number" + "go.opentelemetry.io/otel/sdk/metric/number" ) // These interfaces describe the various ways to access state from an diff --git a/sdk/metric/export/aggregation/temporality.go b/sdk/metric/export/aggregation/temporality.go index ca71b79d03f..0612fe06af0 100644 --- a/sdk/metric/export/aggregation/temporality.go +++ b/sdk/metric/export/aggregation/temporality.go @@ -17,7 +17,7 @@ package aggregation // import "go.opentelemetry.io/otel/sdk/metric/export/aggregation" import ( - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // Temporality indicates the temporal aggregation exported by an exporter. diff --git a/sdk/metric/export/aggregation/temporality_test.go b/sdk/metric/export/aggregation/temporality_test.go index d5d73e9d049..69b976da773 100644 --- a/sdk/metric/export/aggregation/temporality_test.go +++ b/sdk/metric/export/aggregation/temporality_test.go @@ -19,9 +19,9 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) func TestTemporalityIncludes(t *testing.T) { diff --git a/sdk/metric/export/metric.go b/sdk/metric/export/metric.go index 5251a059e1a..7937995e5cc 100644 --- a/sdk/metric/export/metric.go +++ b/sdk/metric/export/metric.go @@ -20,10 +20,10 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/sdk/metric/go.mod b/sdk/metric/go.mod index fc70fcf873e..33fda254764 100644 --- a/sdk/metric/go.mod +++ b/sdk/metric/go.mod @@ -42,7 +42,6 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.4.1 - go.opentelemetry.io/otel/internal/metric v0.27.0 go.opentelemetry.io/otel/metric v0.27.0 go.opentelemetry.io/otel/sdk v1.4.1 ) @@ -55,8 +54,6 @@ replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../.. replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../../exporters/otlp/otlptrace/otlptracehttp -replace go.opentelemetry.io/otel/internal/metric => ../../internal/metric - replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../../exporters/otlp/otlpmetric replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../exporters/otlp/otlpmetric/otlpmetricgrpc diff --git a/sdk/metric/histogram_stress_test.go b/sdk/metric/histogram_stress_test.go index caca662aa0e..abc8b967c60 100644 --- a/sdk/metric/histogram_stress_test.go +++ b/sdk/metric/histogram_stress_test.go @@ -22,10 +22,10 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) func TestStressInt64Histogram(t *testing.T) { diff --git a/metric/metrictest/alignment_test.go b/sdk/metric/metrictest/alignment_test.go similarity index 100% rename from metric/metrictest/alignment_test.go rename to sdk/metric/metrictest/alignment_test.go diff --git a/sdk/metric/metrictest/meter.go b/sdk/metric/metrictest/meter.go new file mode 100644 index 00000000000..8222441e48f --- /dev/null +++ b/sdk/metric/metrictest/meter.go @@ -0,0 +1,56 @@ +// 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 metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" +) + +type ( + + // Library is the same as "sdk/instrumentation".Library but there is + // a package cycle to use it. + Library struct { + InstrumentationName string + InstrumentationVersion string + SchemaURL string + } + + Batch struct { + // Measurement needs to be aligned for 64-bit atomic operations. + Measurements []Measurement + Ctx context.Context + Labels []attribute.KeyValue + Library Library + } + + Measurement struct { + // Number needs to be aligned for 64-bit atomic operations. + Number number.Number + Instrument sdkapi.InstrumentImpl + } +) + +// NewDescriptor is a test helper for constructing test metric +// descriptors using standard options. +func NewDescriptor(name string, ikind sdkapi.InstrumentKind, nkind number.Kind, opts ...instrument.Option) sdkapi.Descriptor { + cfg := instrument.NewConfig(opts...) + return sdkapi.NewDescriptor(name, ikind, nkind, cfg.Description(), cfg.Unit()) +} diff --git a/metric/number/doc.go b/sdk/metric/number/doc.go similarity index 92% rename from metric/number/doc.go rename to sdk/metric/number/doc.go index 0649ff875e7..6f947400a4b 100644 --- a/metric/number/doc.go +++ b/sdk/metric/number/doc.go @@ -20,4 +20,4 @@ This package is currently in a pre-GA phase. Backwards incompatible changes may be introduced in subsequent minor version releases as we work to track the evolving OpenTelemetry specification and user feedback. */ -package number // import "go.opentelemetry.io/otel/metric/number" +package number // import "go.opentelemetry.io/otel/sdk/metric/number" diff --git a/metric/number/kind_string.go b/sdk/metric/number/kind_string.go similarity index 100% rename from metric/number/kind_string.go rename to sdk/metric/number/kind_string.go diff --git a/metric/number/number.go b/sdk/metric/number/number.go similarity index 99% rename from metric/number/number.go rename to sdk/metric/number/number.go index 3ec95e2014d..5fd2f38f663 100644 --- a/metric/number/number.go +++ b/sdk/metric/number/number.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package number // import "go.opentelemetry.io/otel/metric/number" +package number // import "go.opentelemetry.io/otel/sdk/metric/number" //go:generate stringer -type=Kind diff --git a/metric/number/number_test.go b/sdk/metric/number/number_test.go similarity index 100% rename from metric/number/number_test.go rename to sdk/metric/number/number_test.go diff --git a/sdk/metric/processor/basic/basic.go b/sdk/metric/processor/basic/basic.go index 71101bc4bce..096044c043c 100644 --- a/sdk/metric/processor/basic/basic.go +++ b/sdk/metric/processor/basic/basic.go @@ -21,10 +21,10 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( diff --git a/sdk/metric/processor/basic/basic_test.go b/sdk/metric/processor/basic/basic_test.go index 1378bf60d6e..80d0e2a20d8 100644 --- a/sdk/metric/processor/basic/basic_test.go +++ b/sdk/metric/processor/basic/basic_test.go @@ -25,19 +25,19 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/instrumentation" sdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" processorTest "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -450,15 +450,16 @@ func TestCounterObserverEndToEnd(t *testing.T) { eselector, ) accum := sdk.NewAccumulator(proc) - meter := metric.WrapMeterImpl(accum) + meter := sdkapi.WrapMeterImpl(accum) var calls int64 - metric.Must(meter).NewInt64CounterObserver("observer.sum", - func(_ context.Context, result metric.Int64ObserverResult) { - calls++ - result.Observe(calls) - }, - ) + ctr, err := meter.AsyncInt64().Counter("observer.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{ctr}, func(ctx context.Context) { + calls++ + ctr.Observe(ctx, calls) + }) + require.NoError(t, err) reader := proc.Reader() var startTime [3]time.Time diff --git a/sdk/metric/processor/processortest/test.go b/sdk/metric/processor/processortest/test.go index 6dbad262489..8931fc833f8 100644 --- a/sdk/metric/processor/processortest/test.go +++ b/sdk/metric/processor/processortest/test.go @@ -22,7 +22,6 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" @@ -30,6 +29,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/sdk/metric/processor/processortest/test_test.go b/sdk/metric/processor/processortest/test_test.go index 4cc990c344a..c157ad47e3e 100644 --- a/sdk/metric/processor/processortest/test_test.go +++ b/sdk/metric/processor/processortest/test_test.go @@ -21,33 +21,37 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/instrumentation" metricsdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/export" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" "go.opentelemetry.io/otel/sdk/metric/processor/processortest" processorTest "go.opentelemetry.io/otel/sdk/metric/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) -func generateTestData(proc export.Processor) { +func generateTestData(t *testing.T, proc export.Processor) { ctx := context.Background() accum := metricsdk.NewAccumulator(proc) - meter := metric.WrapMeterImpl(accum) + meter := sdkapi.WrapMeterImpl(accum) - counter := metric.Must(meter).NewFloat64Counter("counter.sum") - - _ = metric.Must(meter).NewInt64CounterObserver("observer.sum", - func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(10, attribute.String("K1", "V1")) - result.Observe(11, attribute.String("K1", "V2")) - }, - ) + counter, err := meter.SyncFloat64().Counter("counter.sum") + require.NoError(t, err) counter.Add(ctx, 100, attribute.String("K1", "V1")) counter.Add(ctx, 101, attribute.String("K1", "V2")) + counterObserver, err := meter.AsyncInt64().Counter("observer.sum") + require.NoError(t, err) + + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + counterObserver.Observe(ctx, 10, attribute.String("K1", "V1")) + counterObserver.Observe(ctx, 11, attribute.String("K1", "V2")) + }) + require.NoError(t, err) + accum.Collect(ctx) } @@ -60,7 +64,7 @@ func TestProcessorTesting(t *testing.T) { attribute.DefaultEncoder(), ), ) - generateTestData(checkpointer) + generateTestData(t, checkpointer) res := resource.NewSchemaless(attribute.String("R", "V")) expect := map[string]float64{ diff --git a/sdk/metric/processor/reducer/reducer.go b/sdk/metric/processor/reducer/reducer.go index b7fb4894036..d26fd55345e 100644 --- a/sdk/metric/processor/reducer/reducer.go +++ b/sdk/metric/processor/reducer/reducer.go @@ -16,8 +16,8 @@ package reducer // import "go.opentelemetry.io/otel/sdk/metric/processor/reducer import ( "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( diff --git a/sdk/metric/processor/reducer/reducer_test.go b/sdk/metric/processor/reducer/reducer_test.go index 3da65d313ef..22fc774f17e 100644 --- a/sdk/metric/processor/reducer/reducer_test.go +++ b/sdk/metric/processor/reducer/reducer_test.go @@ -21,8 +21,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/instrumentation" metricsdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/export/aggregation" @@ -30,6 +29,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/processor/processortest" processorTest "go.opentelemetry.io/otel/sdk/metric/processor/processortest" "go.opentelemetry.io/otel/sdk/metric/processor/reducer" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/resource" ) @@ -54,21 +54,22 @@ func (testFilter) LabelFilterFor(_ *sdkapi.Descriptor) attribute.Filter { } } -func generateData(impl sdkapi.MeterImpl) { +func generateData(t *testing.T, impl sdkapi.MeterImpl) { ctx := context.Background() - meter := metric.WrapMeterImpl(impl) - - counter := metric.Must(meter).NewFloat64Counter("counter.sum") - - _ = metric.Must(meter).NewInt64CounterObserver("observer.sum", - func(_ context.Context, result metric.Int64ObserverResult) { - result.Observe(10, kvs1...) - result.Observe(10, kvs2...) - }, - ) + meter := sdkapi.WrapMeterImpl(impl) + counter, err := meter.SyncFloat64().Counter("counter.sum") + require.NoError(t, err) counter.Add(ctx, 100, kvs1...) counter.Add(ctx, 100, kvs2...) + + counterObserver, err := meter.AsyncInt64().Counter("observer.sum") + require.NoError(t, err) + err = meter.RegisterCallback([]instrument.Asynchronous{counterObserver}, func(ctx context.Context) { + counterObserver.Observe(ctx, 10, kvs1...) + counterObserver.Observe(ctx, 10, kvs2...) + }) + require.NoError(t, err) } func TestFilterProcessor(t *testing.T) { @@ -79,7 +80,7 @@ func TestFilterProcessor(t *testing.T) { accum := metricsdk.NewAccumulator( reducer.New(testFilter{}, processorTest.NewCheckpointer(testProc)), ) - generateData(accum) + generateData(t, accum) accum.Collect(context.Background()) @@ -97,7 +98,7 @@ func TestFilterBasicProcessor(t *testing.T) { ) exporter := processorTest.New(basicProc, attribute.DefaultEncoder()) - generateData(accum) + generateData(t, accum) basicProc.StartCollection() accum.Collect(context.Background()) diff --git a/internal/metric/registry/doc.go b/sdk/metric/registry/doc.go similarity index 92% rename from internal/metric/registry/doc.go rename to sdk/metric/registry/doc.go index 2f17562f0eb..b401408beef 100644 --- a/internal/metric/registry/doc.go +++ b/sdk/metric/registry/doc.go @@ -21,4 +21,4 @@ This package is currently in a pre-GA phase. Backwards incompatible changes may be introduced in subsequent minor version releases as we work to track the evolving OpenTelemetry specification and user feedback. */ -package registry // import "go.opentelemetry.io/otel/internal/metric/registry" +package registry // import "go.opentelemetry.io/otel/sdk/metric/registry" diff --git a/internal/metric/registry/registry.go b/sdk/metric/registry/registry.go similarity index 86% rename from internal/metric/registry/registry.go rename to sdk/metric/registry/registry.go index c929bf45c85..c2870e483d1 100644 --- a/internal/metric/registry/registry.go +++ b/sdk/metric/registry/registry.go @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package registry // import "go.opentelemetry.io/otel/internal/metric/registry" +package registry // import "go.opentelemetry.io/otel/sdk/metric/registry" import ( "context" "fmt" "sync" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) // UniqueInstrumentMeterImpl implements the metric.MeterImpl interface, adding @@ -53,11 +53,6 @@ func (u *UniqueInstrumentMeterImpl) MeterImpl() sdkapi.MeterImpl { return u.impl } -// RecordBatch implements sdkapi.MeterImpl. -func (u *UniqueInstrumentMeterImpl) RecordBatch(ctx context.Context, labels []attribute.KeyValue, ms ...sdkapi.Measurement) { - u.impl.RecordBatch(ctx, labels, ms...) -} - // NewMetricKindMismatchError formats an error that describes a // mismatched metric instrument definition. func NewMetricKindMismatchError(desc sdkapi.Descriptor) error { @@ -115,10 +110,7 @@ func (u *UniqueInstrumentMeterImpl) NewSyncInstrument(descriptor sdkapi.Descript } // NewAsyncInstrument implements sdkapi.MeterImpl. -func (u *UniqueInstrumentMeterImpl) NewAsyncInstrument( - descriptor sdkapi.Descriptor, - runner sdkapi.AsyncRunner, -) (sdkapi.AsyncImpl, error) { +func (u *UniqueInstrumentMeterImpl) NewAsyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.AsyncImpl, error) { u.lock.Lock() defer u.lock.Unlock() @@ -130,10 +122,17 @@ func (u *UniqueInstrumentMeterImpl) NewAsyncInstrument( return impl.(sdkapi.AsyncImpl), nil } - asyncInst, err := u.impl.NewAsyncInstrument(descriptor, runner) + asyncInst, err := u.impl.NewAsyncInstrument(descriptor) if err != nil { return nil, err } u.state[descriptor.Name()] = asyncInst return asyncInst, nil } + +func (u *UniqueInstrumentMeterImpl) RegisterCallback(insts []instrument.Asynchronous, callback func(context.Context)) error { + u.lock.Lock() + defer u.lock.Unlock() + + return u.impl.RegisterCallback(insts, callback) +} diff --git a/internal/metric/registry/registry_test.go b/sdk/metric/registry/registry_test.go similarity index 71% rename from internal/metric/registry/registry_test.go rename to sdk/metric/registry/registry_test.go index 04884898822..5d2bc9b108f 100644 --- a/internal/metric/registry/registry_test.go +++ b/sdk/metric/registry/registry_test.go @@ -15,16 +15,15 @@ package registry_test import ( - "context" "errors" "testing" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/internal/metric/registry" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/sdkapi" + metricsdk "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/registry" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( @@ -34,22 +33,22 @@ type ( var ( allNew = map[string]newFunc{ "counter.int64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewInt64Counter(name)) + return unwrap(m.SyncInt64().Counter(name)) }, "counter.float64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewFloat64Counter(name)) + return unwrap(m.SyncFloat64().Counter(name)) }, "histogram.int64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewInt64Histogram(name)) + return unwrap(m.SyncInt64().Histogram(name)) }, "histogram.float64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewFloat64Histogram(name)) + return unwrap(m.SyncFloat64().Histogram(name)) }, "gaugeobserver.int64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewInt64GaugeObserver(name, func(context.Context, metric.Int64ObserverResult) {})) + return unwrap(m.AsyncInt64().Gauge(name)) }, "gaugeobserver.float64": func(m metric.Meter, name string) (sdkapi.InstrumentImpl, error) { - return unwrap(m.NewFloat64GaugeObserver(name, func(context.Context, metric.Float64ObserverResult) {})) + return unwrap(m.AsyncFloat64().Gauge(name)) }, } ) @@ -71,10 +70,11 @@ func unwrap(impl interface{}, err error) (sdkapi.InstrumentImpl, error) { return nil, err } +// TODO Replace with controller func testMeterWithRegistry(name string) metric.Meter { - return metric.WrapMeterImpl( + return sdkapi.WrapMeterImpl( registry.NewUniqueInstrumentMeterImpl( - metrictest.NewMeterProvider().Meter(name).MeterImpl(), + metricsdk.NewAccumulator(nil), ), ) } @@ -91,21 +91,6 @@ func TestRegistrySameInstruments(t *testing.T) { } } -func TestRegistryDifferentNamespace(t *testing.T) { - for _, nf := range allNew { - provider := metrictest.NewMeterProvider() - - meter1 := provider.Meter("meter1") - meter2 := provider.Meter("meter2") - inst1, err1 := nf(meter1, "this") - inst2, err2 := nf(meter2, "this") - - require.NoError(t, err1) - require.NoError(t, err2) - require.NotEqual(t, inst1, inst2) - } -} - func TestRegistryDiffInstruments(t *testing.T) { for origName, origf := range allNew { meter := testMeterWithRegistry("meter") @@ -120,7 +105,7 @@ func TestRegistryDiffInstruments(t *testing.T) { other, err := nf(meter, "this") require.Error(t, err) - require.NotNil(t, other) + require.Nil(t, other) require.True(t, errors.Is(err, registry.ErrMetricKindMismatch)) } } diff --git a/sdk/metric/sdk.go b/sdk/metric/sdk.go index a043342627b..db3ad330213 100644 --- a/sdk/metric/sdk.go +++ b/sdk/metric/sdk.go @@ -23,11 +23,11 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - internal "go.opentelemetry.io/otel/internal/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( @@ -44,10 +44,8 @@ type ( // current maps `mapkey` to *record. current sync.Map - // asyncInstruments is a set of - // `*asyncInstrument` instances - asyncLock sync.Mutex - asyncInstruments *internal.AsyncInstrumentState + callbackLock sync.Mutex + callbacks map[*callback]struct{} // currentEpoch is the current epoch number. It is // incremented in `Collect()`. @@ -58,15 +56,23 @@ type ( // collectLock prevents simultaneous calls to Collect(). collectLock sync.Mutex + } - // asyncSortSlice has a single purpose - as a temporary - // place for sorting during labels creation to avoid - // allocation. It is cleared after use. - asyncSortSlice attribute.Sortable + callback struct { + insts map[*asyncInstrument]struct{} + f func(context.Context) + } + + asyncContextKey struct{} + + asyncInstrument struct { + baseInstrument + instrument.Asynchronous } syncInstrument struct { - instrument + baseInstrument + instrument.Synchronous } // mapkey uniquely describes a metric instrument in terms of @@ -92,16 +98,10 @@ type ( // supports checking for no updates during a round. collectedCount int64 - // storage is the stored label set for this record, + // labels is the stored label set for this record, // except in cases where a label set is shared due to // batch recording. - storage attribute.Set - - // labels is the processed label set for this record. - // this may refer to the `storage` field in another - // record if this label set is shared resulting from - // `RecordBatch`. - labels *attribute.Set + labels attribute.Set // sortSlice has a single purpose - as a temporary // place for sorting during labels creation to avoid @@ -109,7 +109,7 @@ type ( sortSlice attribute.Sortable // inst is a pointer to the corresponding instrument. - inst *syncInstrument + inst *baseInstrument // current implements the actual RecordOne() API, // depending on the type of aggregation. If nil, the @@ -118,36 +118,23 @@ type ( checkpoint aggregator.Aggregator } - instrument struct { + baseInstrument struct { meter *Accumulator descriptor sdkapi.Descriptor } - - asyncInstrument struct { - instrument - // recorders maps ordered labels to the pair of - // labelset and recorder - recorders map[attribute.Distinct]*labeledRecorder - } - - labeledRecorder struct { - observedEpoch int64 - labels *attribute.Set - observed aggregator.Aggregator - } ) var ( _ sdkapi.MeterImpl = &Accumulator{} - _ sdkapi.AsyncImpl = &asyncInstrument{} - _ sdkapi.SyncImpl = &syncInstrument{} // ErrUninitializedInstrument is returned when an instrument is used when uninitialized. ErrUninitializedInstrument = fmt.Errorf("use of an uninitialized instrument") + + ErrBadInstrument = fmt.Errorf("use of a instrument from another SDK") ) -func (inst *instrument) Descriptor() sdkapi.Descriptor { - return inst.descriptor +func (b *baseInstrument) Descriptor() sdkapi.Descriptor { + return b.descriptor } func (a *asyncInstrument) Implementation() interface{} { @@ -158,77 +145,24 @@ func (s *syncInstrument) Implementation() interface{} { return s } -func (a *asyncInstrument) observe(num number.Number, labels *attribute.Set) { - if err := aggregator.RangeTest(num, &a.descriptor); err != nil { - otel.Handle(err) - return - } - recorder := a.getRecorder(labels) - if recorder == nil { - // The instrument is disabled according to the - // AggregatorSelector. - return - } - if err := recorder.Update(context.Background(), num, &a.descriptor); err != nil { - otel.Handle(err) - return - } -} - -func (a *asyncInstrument) getRecorder(labels *attribute.Set) aggregator.Aggregator { - lrec, ok := a.recorders[labels.Equivalent()] - if ok { - // Note: SynchronizedMove(nil) can't return an error - _ = lrec.observed.SynchronizedMove(nil, &a.descriptor) - lrec.observedEpoch = a.meter.currentEpoch - a.recorders[labels.Equivalent()] = lrec - return lrec.observed - } - var rec aggregator.Aggregator - a.meter.processor.AggregatorFor(&a.descriptor, &rec) - if a.recorders == nil { - a.recorders = make(map[attribute.Distinct]*labeledRecorder) - } - // This may store nil recorder in the map, thus disabling the - // asyncInstrument for the labelset for good. This is intentional, - // but will be revisited later. - a.recorders[labels.Equivalent()] = &labeledRecorder{ - observed: rec, - labels: labels, - observedEpoch: a.meter.currentEpoch, - } - return rec -} - // acquireHandle gets or creates a `*record` corresponding to `kvs`, -// the input labels. The second argument `labels` is passed in to -// support re-use of the orderedLabels computed by a previous -// measurement in the same batch. This performs two allocations -// in the common case. -func (s *syncInstrument) acquireHandle(kvs []attribute.KeyValue, labelPtr *attribute.Set) *record { - var rec *record - var equiv attribute.Distinct - - if labelPtr == nil { - // This memory allocation may not be used, but it's - // needed for the `sortSlice` field, to avoid an - // allocation while sorting. - rec = &record{} - rec.storage = attribute.NewSetWithSortable(kvs, &rec.sortSlice) - rec.labels = &rec.storage - equiv = rec.storage.Equivalent() - } else { - equiv = labelPtr.Equivalent() - } +// the input labels. +func (b *baseInstrument) acquireHandle(kvs []attribute.KeyValue) *record { + + // This memory allocation may not be used, but it's + // needed for the `sortSlice` field, to avoid an + // allocation while sorting. + rec := &record{} + rec.labels = attribute.NewSetWithSortable(kvs, &rec.sortSlice) // Create lookup key for sync.Map (one allocation, as this // passes through an interface{}) mk := mapkey{ - descriptor: &s.descriptor, - ordered: equiv, + descriptor: &b.descriptor, + ordered: rec.labels.Equivalent(), } - if actual, ok := s.meter.current.Load(mk); ok { + if actual, ok := b.meter.current.Load(mk); ok { // Existing record case. existingRec := actual.(*record) if existingRec.refMapped.ref() { @@ -239,19 +173,15 @@ func (s *syncInstrument) acquireHandle(kvs []attribute.KeyValue, labelPtr *attri // This entry is no longer mapped, try to add a new entry. } - if rec == nil { - rec = &record{} - rec.labels = labelPtr - } rec.refMapped = refcountMapped{value: 2} - rec.inst = s + rec.inst = b - s.meter.processor.AggregatorFor(&s.descriptor, &rec.current, &rec.checkpoint) + b.meter.processor.AggregatorFor(&b.descriptor, &rec.current, &rec.checkpoint) for { // Load/Store: there's a memory allocation to place `mk` into // an interface here. - if actual, loaded := s.meter.current.LoadOrStore(mk, rec); loaded { + if actual, loaded := b.meter.current.LoadOrStore(mk, rec); loaded { // Existing record case. Cannot change rec here because if fail // will try to add rec again to avoid new allocations. oldRec := actual.(*record) @@ -278,11 +208,22 @@ func (s *syncInstrument) acquireHandle(kvs []attribute.KeyValue, labelPtr *attri } } +// RecordOne captures a single synchronous metric event. +// // The order of the input array `kvs` may be sorted after the function is called. func (s *syncInstrument) RecordOne(ctx context.Context, num number.Number, kvs []attribute.KeyValue) { - h := s.acquireHandle(kvs, nil) + h := s.acquireHandle(kvs) defer h.unbind() - h.RecordOne(ctx, num) + h.captureOne(ctx, num) +} + +// ObserveOne captures a single asynchronous metric event. + +// The order of the input array `kvs` may be sorted after the function is called. +func (a *asyncInstrument) ObserveOne(ctx context.Context, num number.Number, attrs []attribute.KeyValue) { + h := a.acquireHandle(attrs) + defer h.unbind() + h.captureOne(ctx, num) } // NewAccumulator constructs a new Accumulator for the given @@ -296,15 +237,17 @@ func (s *syncInstrument) RecordOne(ctx context.Context, num number.Number, kvs [ // own periodic collection. func NewAccumulator(processor export.Processor) *Accumulator { return &Accumulator{ - processor: processor, - asyncInstruments: internal.NewAsyncInstrumentState(), + processor: processor, + callbacks: map[*callback]struct{}{}, } } +var _ sdkapi.MeterImpl = &Accumulator{} + // NewSyncInstrument implements sdkapi.MetricImpl. func (m *Accumulator) NewSyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.SyncImpl, error) { return &syncInstrument{ - instrument: instrument{ + baseInstrument: baseInstrument{ descriptor: descriptor, meter: m, }, @@ -312,19 +255,40 @@ func (m *Accumulator) NewSyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.Sy } // NewAsyncInstrument implements sdkapi.MetricImpl. -func (m *Accumulator) NewAsyncInstrument(descriptor sdkapi.Descriptor, runner sdkapi.AsyncRunner) (sdkapi.AsyncImpl, error) { +func (m *Accumulator) NewAsyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.AsyncImpl, error) { a := &asyncInstrument{ - instrument: instrument{ + baseInstrument: baseInstrument{ descriptor: descriptor, meter: m, }, } - m.asyncLock.Lock() - defer m.asyncLock.Unlock() - m.asyncInstruments.Register(a, runner) return a, nil } +func (m *Accumulator) RegisterCallback(insts []instrument.Asynchronous, f func(context.Context)) error { + cb := &callback{ + insts: map[*asyncInstrument]struct{}{}, + f: f, + } + for _, inst := range insts { + impl, ok := inst.(sdkapi.AsyncImpl) + if !ok { + return ErrBadInstrument + } + + ai, err := m.fromAsync(impl) + if err != nil { + return err + } + cb.insts[ai] = struct{}{} + } + + m.callbackLock.Lock() + defer m.callbackLock.Unlock() + m.callbacks[cb] = struct{}{} + return nil +} + // Collect traverses the list of active records and observers and // exports data for each active instrument. Collect() may not be // called concurrently. @@ -337,14 +301,14 @@ func (m *Accumulator) Collect(ctx context.Context) int { m.collectLock.Lock() defer m.collectLock.Unlock() - checkpointed := m.observeAsyncInstruments(ctx) - checkpointed += m.collectSyncInstruments() + m.runAsyncCallbacks(ctx) + checkpointed := m.collectInstruments() m.currentEpoch++ return checkpointed } -func (m *Accumulator) collectSyncInstruments() int { +func (m *Accumulator) collectInstruments() int { checkpointed := 0 m.current.Range(func(key interface{}, value interface{}) bool { @@ -387,33 +351,15 @@ func (m *Accumulator) collectSyncInstruments() int { return checkpointed } -// CollectAsync implements internal.AsyncCollector. -// The order of the input array `kvs` may be sorted after the function is called. -func (m *Accumulator) CollectAsync(kv []attribute.KeyValue, obs ...sdkapi.Observation) { - labels := attribute.NewSetWithSortable(kv, &m.asyncSortSlice) - - for _, ob := range obs { - if a := m.fromAsync(ob.AsyncImpl()); a != nil { - a.observe(ob.Number(), &labels) - } - } -} - -func (m *Accumulator) observeAsyncInstruments(ctx context.Context) int { - m.asyncLock.Lock() - defer m.asyncLock.Unlock() - - asyncCollected := 0 +func (m *Accumulator) runAsyncCallbacks(ctx context.Context) { + m.callbackLock.Lock() + defer m.callbackLock.Unlock() - m.asyncInstruments.Run(ctx, m) + ctx = context.WithValue(ctx, asyncContextKey{}, m) - for _, inst := range m.asyncInstruments.Instruments() { - if a := m.fromAsync(inst); a != nil { - asyncCollected += m.checkpointAsync(a) - } + for cb := range m.callbacks { + cb.f(ctx) } - - return asyncCollected } func (m *Accumulator) checkpointRecord(r *record) int { @@ -426,7 +372,7 @@ func (m *Accumulator) checkpointRecord(r *record) int { return 0 } - a := export.NewAccumulation(&r.inst.descriptor, r.labels, r.checkpoint) + a := export.NewAccumulation(&r.inst.descriptor, &r.labels, r.checkpoint) err = m.processor.Process(a) if err != nil { otel.Handle(err) @@ -434,63 +380,7 @@ func (m *Accumulator) checkpointRecord(r *record) int { return 1 } -func (m *Accumulator) checkpointAsync(a *asyncInstrument) int { - if len(a.recorders) == 0 { - return 0 - } - checkpointed := 0 - for encodedLabels, lrec := range a.recorders { - lrec := lrec - epochDiff := m.currentEpoch - lrec.observedEpoch - if epochDiff == 0 { - if lrec.observed != nil { - a := export.NewAccumulation(&a.descriptor, lrec.labels, lrec.observed) - err := m.processor.Process(a) - if err != nil { - otel.Handle(err) - } - checkpointed++ - } - } else if epochDiff > 1 { - // This is second collection cycle with no - // observations for this labelset. Remove the - // recorder. - delete(a.recorders, encodedLabels) - } - } - if len(a.recorders) == 0 { - a.recorders = nil - } - return checkpointed -} - -// RecordBatch enters a batch of metric events. -// The order of the input array `kvs` may be sorted after the function is called. -func (m *Accumulator) RecordBatch(ctx context.Context, kvs []attribute.KeyValue, measurements ...sdkapi.Measurement) { - // Labels will be computed the first time acquireHandle is - // called. Subsequent calls to acquireHandle will re-use the - // previously computed value instead of recomputing the - // ordered labels. - var labelsPtr *attribute.Set - for i, meas := range measurements { - s := m.fromSync(meas.SyncImpl()) - if s == nil { - continue - } - h := s.acquireHandle(kvs, labelsPtr) - - // Re-use labels for the next measurement. - if i == 0 { - labelsPtr = h.labels - } - - defer h.unbind() - h.RecordOne(ctx, meas.Number()) - } -} - -// RecordOne implements sdkapi.SyncImpl. -func (r *record) RecordOne(ctx context.Context, num number.Number) { +func (r *record) captureOne(ctx context.Context, num number.Number) { if r.current == nil { // The instrument is disabled according to the AggregatorSelector. return @@ -519,26 +409,16 @@ func (r *record) mapkey() mapkey { } } -// fromSync gets a sync implementation object, checking for -// uninitialized instruments and instruments created by another SDK. -func (m *Accumulator) fromSync(sync sdkapi.SyncImpl) *syncInstrument { - if sync != nil { - if inst, ok := sync.Implementation().(*syncInstrument); ok { - return inst - } - } - otel.Handle(ErrUninitializedInstrument) - return nil -} - // fromSync gets an async implementation object, checking for // uninitialized instruments and instruments created by another SDK. -func (m *Accumulator) fromAsync(async sdkapi.AsyncImpl) *asyncInstrument { - if async != nil { - if inst, ok := async.Implementation().(*asyncInstrument); ok { - return inst - } +func (m *Accumulator) fromAsync(async sdkapi.AsyncImpl) (*asyncInstrument, error) { + if async == nil { + return nil, ErrUninitializedInstrument } - otel.Handle(ErrUninitializedInstrument) - return nil + inst, ok := async.Implementation().(*asyncInstrument) + if !ok { + return nil, ErrBadInstrument + } + return inst, nil + } diff --git a/metric/sdkapi/descriptor.go b/sdk/metric/sdkapi/descriptor.go similarity index 94% rename from metric/sdkapi/descriptor.go rename to sdk/metric/sdkapi/descriptor.go index 14eb0532e45..f86e4473459 100644 --- a/metric/sdkapi/descriptor.go +++ b/sdk/metric/sdkapi/descriptor.go @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi" +package sdkapi // import "go.opentelemetry.io/otel/sdk/metric/sdkapi" import ( - "go.opentelemetry.io/otel/metric/number" "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/sdk/metric/number" ) // Descriptor contains all the settings that describe an instrument, diff --git a/metric/sdkapi/descriptor_test.go b/sdk/metric/sdkapi/descriptor_test.go similarity index 96% rename from metric/sdkapi/descriptor_test.go rename to sdk/metric/sdkapi/descriptor_test.go index 6b6927075f9..1f084472535 100644 --- a/metric/sdkapi/descriptor_test.go +++ b/sdk/metric/sdkapi/descriptor_test.go @@ -19,8 +19,8 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/number" "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/sdk/metric/number" ) func TestDescriptorGetters(t *testing.T) { diff --git a/metric/sdkapi/instrumentkind.go b/sdk/metric/sdkapi/instrumentkind.go similarity index 97% rename from metric/sdkapi/instrumentkind.go rename to sdk/metric/sdkapi/instrumentkind.go index 64aa5ead123..c7406a3e49a 100644 --- a/metric/sdkapi/instrumentkind.go +++ b/sdk/metric/sdkapi/instrumentkind.go @@ -14,7 +14,7 @@ //go:generate stringer -type=InstrumentKind -package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi" +package sdkapi // import "go.opentelemetry.io/otel/sdk/metric/sdkapi" // InstrumentKind describes the kind of instrument. type InstrumentKind int8 diff --git a/metric/sdkapi/instrumentkind_string.go b/sdk/metric/sdkapi/instrumentkind_string.go similarity index 100% rename from metric/sdkapi/instrumentkind_string.go rename to sdk/metric/sdkapi/instrumentkind_string.go diff --git a/metric/sdkapi/instrumentkind_test.go b/sdk/metric/sdkapi/instrumentkind_test.go similarity index 96% rename from metric/sdkapi/instrumentkind_test.go rename to sdk/metric/sdkapi/instrumentkind_test.go index 0b2901383b2..cd1db02a898 100644 --- a/metric/sdkapi/instrumentkind_test.go +++ b/sdk/metric/sdkapi/instrumentkind_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/sdkapi" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) func TestInstrumentKinds(t *testing.T) { diff --git a/metric/sdkapi/noop.go b/sdk/metric/sdkapi/noop.go similarity index 71% rename from metric/sdkapi/noop.go rename to sdk/metric/sdkapi/noop.go index f22895dae6f..6b2374b68a3 100644 --- a/metric/sdkapi/noop.go +++ b/sdk/metric/sdkapi/noop.go @@ -12,20 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi" +package sdkapi // import "go.opentelemetry.io/otel/sdk/metric/sdkapi" import ( "context" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/number" -) + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/sdk/metric/number" +) // import ( +// "context" + +// "go.opentelemetry.io/otel/attribute" +// "go.opentelemetry.io/otel/sdk/metric/number" +// ) type noopInstrument struct { descriptor Descriptor } -type noopSyncInstrument struct{ noopInstrument } -type noopAsyncInstrument struct{ noopInstrument } +type noopSyncInstrument struct { + noopInstrument + + instrument.Synchronous +} +type noopAsyncInstrument struct { + noopInstrument + + instrument.Asynchronous +} var _ SyncImpl = noopSyncInstrument{} var _ AsyncImpl = noopAsyncInstrument{} @@ -34,7 +48,7 @@ var _ AsyncImpl = noopAsyncInstrument{} // synchronous instrument interface. func NewNoopSyncInstrument() SyncImpl { return noopSyncInstrument{ - noopInstrument{ + noopInstrument: noopInstrument{ descriptor: Descriptor{ instrumentKind: CounterInstrumentKind, }, @@ -46,7 +60,7 @@ func NewNoopSyncInstrument() SyncImpl { // asynchronous instrument interface. func NewNoopAsyncInstrument() AsyncImpl { return noopAsyncInstrument{ - noopInstrument{ + noopInstrument: noopInstrument{ descriptor: Descriptor{ instrumentKind: CounterObserverInstrumentKind, }, @@ -64,3 +78,6 @@ func (n noopInstrument) Descriptor() Descriptor { func (noopSyncInstrument) RecordOne(context.Context, number.Number, []attribute.KeyValue) { } + +func (noopAsyncInstrument) ObserveOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) { +} diff --git a/metric/sdkapi/sdkapi.go b/sdk/metric/sdkapi/sdkapi.go similarity index 90% rename from metric/sdkapi/sdkapi.go rename to sdk/metric/sdkapi/sdkapi.go index 36836364bdc..4345358ff3e 100644 --- a/metric/sdkapi/sdkapi.go +++ b/sdk/metric/sdkapi/sdkapi.go @@ -12,21 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi" +package sdkapi // import "go.opentelemetry.io/otel/sdk/metric/sdkapi" import ( "context" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/number" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/sdk/metric/number" ) // MeterImpl is the interface an SDK must implement to supply a Meter // implementation. type MeterImpl interface { - // RecordBatch atomically records a batch of measurements. - RecordBatch(ctx context.Context, labels []attribute.KeyValue, measurement ...Measurement) - // NewSyncInstrument returns a newly constructed // synchronous instrument implementation or an error, should // one occur. @@ -35,10 +33,10 @@ type MeterImpl interface { // NewAsyncInstrument returns a newly constructed // asynchronous instrument implementation or an error, should // one occur. - NewAsyncInstrument( - descriptor Descriptor, - runner AsyncRunner, - ) (AsyncImpl, error) + NewAsyncInstrument(descriptor Descriptor) (AsyncImpl, error) + + // Etc. + RegisterCallback(insts []instrument.Asynchronous, callback func(context.Context)) error } // InstrumentImpl is a common interface for synchronous and @@ -57,6 +55,7 @@ type InstrumentImpl interface { // synchronous instrument (e.g., Histogram and Counter instruments). type SyncImpl interface { InstrumentImpl + instrument.Synchronous // RecordOne captures a single synchronous metric event. RecordOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) @@ -66,6 +65,10 @@ type SyncImpl interface { // asynchronous instrument (e.g., Observer instruments). type AsyncImpl interface { InstrumentImpl + instrument.Asynchronous + + // ObserveOne captures a single synchronous metric event. + ObserveOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) } // AsyncRunner is expected to convert into an AsyncSingleRunner or an diff --git a/metric/sdkapi/sdkapi_test.go b/sdk/metric/sdkapi/sdkapi_test.go similarity index 96% rename from metric/sdkapi/sdkapi_test.go rename to sdk/metric/sdkapi/sdkapi_test.go index 9c80f89bddb..69fec0fe692 100644 --- a/metric/sdkapi/sdkapi_test.go +++ b/sdk/metric/sdkapi/sdkapi_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/number" + "go.opentelemetry.io/otel/sdk/metric/number" ) func TestMeasurementGetters(t *testing.T) { diff --git a/sdk/metric/sdkapi/wrap.go b/sdk/metric/sdkapi/wrap.go new file mode 100644 index 00000000000..9a4d5b0de14 --- /dev/null +++ b/sdk/metric/sdkapi/wrap.go @@ -0,0 +1,181 @@ +// 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 sdkapi // import "go.opentelemetry.io/otel/sdk/metric/sdkapi" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/sdk/metric/number" +) + +type ( + meter struct{ MeterImpl } + sfMeter struct{ meter } + siMeter struct{ meter } + afMeter struct{ meter } + aiMeter struct{ meter } + + iAdder struct{ SyncImpl } + fAdder struct{ SyncImpl } + iRecorder struct{ SyncImpl } + fRecorder struct{ SyncImpl } + iObserver struct{ AsyncImpl } + fObserver struct{ AsyncImpl } +) + +func WrapMeterImpl(impl MeterImpl) metric.Meter { + return meter{impl} +} + +func UnwrapMeterImpl(m metric.Meter) MeterImpl { + mm, ok := m.(meter) + if !ok { + return nil + } + return mm.MeterImpl +} + +func (m meter) AsyncFloat64() asyncfloat64.InstrumentProvider { + return afMeter{m} +} + +func (m meter) AsyncInt64() asyncint64.InstrumentProvider { + return aiMeter{m} +} + +func (m meter) SyncFloat64() syncfloat64.InstrumentProvider { + return sfMeter{m} +} + +func (m meter) SyncInt64() syncint64.InstrumentProvider { + return siMeter{m} +} + +func (m meter) RegisterCallback(insts []instrument.Asynchronous, cb func(ctx context.Context)) error { + return m.MeterImpl.RegisterCallback(insts, cb) +} + +func (m meter) newSync(name string, ikind InstrumentKind, nkind number.Kind, opts []instrument.Option) (SyncImpl, error) { + cfg := instrument.NewConfig(opts...) + return m.NewSyncInstrument(NewDescriptor(name, ikind, nkind, cfg.Description(), cfg.Unit())) +} + +func (m meter) newAsync(name string, ikind InstrumentKind, nkind number.Kind, opts []instrument.Option) (AsyncImpl, error) { + cfg := instrument.NewConfig(opts...) + return m.NewAsyncInstrument(NewDescriptor(name, ikind, nkind, cfg.Description(), cfg.Unit())) +} + +func (m afMeter) Counter(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) { + inst, err := m.newAsync(name, CounterObserverInstrumentKind, number.Float64Kind, opts) + return fObserver{inst}, err +} + +func (m afMeter) UpDownCounter(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) { + inst, err := m.newAsync(name, UpDownCounterObserverInstrumentKind, number.Float64Kind, opts) + return fObserver{inst}, err +} + +func (m afMeter) Gauge(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { + inst, err := m.newAsync(name, GaugeObserverInstrumentKind, number.Float64Kind, opts) + return fObserver{inst}, err +} + +func (m aiMeter) Counter(name string, opts ...instrument.Option) (asyncint64.Counter, error) { + inst, err := m.newAsync(name, CounterObserverInstrumentKind, number.Int64Kind, opts) + return iObserver{inst}, err +} + +func (m aiMeter) UpDownCounter(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) { + inst, err := m.newAsync(name, UpDownCounterObserverInstrumentKind, number.Int64Kind, opts) + return iObserver{inst}, err +} + +func (m aiMeter) Gauge(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { + inst, err := m.newAsync(name, GaugeObserverInstrumentKind, number.Int64Kind, opts) + return iObserver{inst}, err +} + +func (m sfMeter) Counter(name string, opts ...instrument.Option) (syncfloat64.Counter, error) { + inst, err := m.newSync(name, CounterInstrumentKind, number.Float64Kind, opts) + return fAdder{inst}, err +} + +func (m sfMeter) UpDownCounter(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) { + inst, err := m.newSync(name, UpDownCounterInstrumentKind, number.Float64Kind, opts) + return fAdder{inst}, err +} + +func (m sfMeter) Histogram(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) { + inst, err := m.newSync(name, HistogramInstrumentKind, number.Float64Kind, opts) + return fRecorder{inst}, err +} + +func (m siMeter) Counter(name string, opts ...instrument.Option) (syncint64.Counter, error) { + inst, err := m.newSync(name, CounterInstrumentKind, number.Int64Kind, opts) + return iAdder{inst}, err +} + +func (m siMeter) UpDownCounter(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) { + inst, err := m.newSync(name, UpDownCounterInstrumentKind, number.Int64Kind, opts) + return iAdder{inst}, err +} + +func (m siMeter) Histogram(name string, opts ...instrument.Option) (syncint64.Histogram, error) { + inst, err := m.newSync(name, HistogramInstrumentKind, number.Int64Kind, opts) + return iRecorder{inst}, err +} + +func (a fAdder) Add(ctx context.Context, value float64, attrs ...attribute.KeyValue) { + if a.SyncImpl != nil { + a.SyncImpl.RecordOne(ctx, number.NewFloat64Number(value), attrs) + } +} + +func (a iAdder) Add(ctx context.Context, value int64, attrs ...attribute.KeyValue) { + if a.SyncImpl != nil { + a.SyncImpl.RecordOne(ctx, number.NewInt64Number(value), attrs) + } +} + +func (a fRecorder) Record(ctx context.Context, value float64, attrs ...attribute.KeyValue) { + if a.SyncImpl != nil { + a.SyncImpl.RecordOne(ctx, number.NewFloat64Number(value), attrs) + } +} + +func (a iRecorder) Record(ctx context.Context, value int64, attrs ...attribute.KeyValue) { + if a.SyncImpl != nil { + a.SyncImpl.RecordOne(ctx, number.NewInt64Number(value), attrs) + } +} + +func (a fObserver) Observe(ctx context.Context, value float64, attrs ...attribute.KeyValue) { + if a.AsyncImpl != nil { + a.AsyncImpl.ObserveOne(ctx, number.NewFloat64Number(value), attrs) + } +} + +func (a iObserver) Observe(ctx context.Context, value int64, attrs ...attribute.KeyValue) { + if a.AsyncImpl != nil { + a.AsyncImpl.ObserveOne(ctx, number.NewInt64Number(value), attrs) + } +} diff --git a/sdk/metric/selector/simple/simple.go b/sdk/metric/selector/simple/simple.go index 5b3e1c5fd5e..5451072f607 100644 --- a/sdk/metric/selector/simple/simple.go +++ b/sdk/metric/selector/simple/simple.go @@ -15,12 +15,12 @@ package simple // import "go.opentelemetry.io/otel/sdk/metric/selector/simple" import ( - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) type ( diff --git a/sdk/metric/selector/simple/simple_test.go b/sdk/metric/selector/simple/simple_test.go index a25291b5112..9e946864e53 100644 --- a/sdk/metric/selector/simple/simple_test.go +++ b/sdk/metric/selector/simple/simple_test.go @@ -19,14 +19,14 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/aggregator" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/export" + "go.opentelemetry.io/otel/sdk/metric/metrictest" + "go.opentelemetry.io/otel/sdk/metric/number" + "go.opentelemetry.io/otel/sdk/metric/sdkapi" "go.opentelemetry.io/otel/sdk/metric/selector/simple" ) diff --git a/sdk/metric/stress_test.go b/sdk/metric/stress_test.go deleted file mode 100644 index f6b9eb4ba02..00000000000 --- a/sdk/metric/stress_test.go +++ /dev/null @@ -1,502 +0,0 @@ -// 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. - -// This test is too large for the race detector. This SDK uses no locks -// that the race detector would help with, anyway. -//go:build !race -// +build !race - -package metric - -import ( - "context" - "fmt" - "math" - "math/rand" - "runtime" - "sort" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" - "go.opentelemetry.io/otel/sdk/metric/export" - "go.opentelemetry.io/otel/sdk/metric/export/aggregation" - "go.opentelemetry.io/otel/sdk/metric/processor/processortest" -) - -const ( - concurrencyPerCPU = 100 - reclaimPeriod = time.Millisecond * 100 - testRun = 5 * time.Second - epsilon = 1e-10 -) - -var Must = metric.Must - -type ( - testFixture struct { - // stop has to be aligned for 64-bit atomic operations. - stop int64 - expected sync.Map - received sync.Map // Note: doesn't require synchronization - wg sync.WaitGroup - impl testImpl - T *testing.T - - export.AggregatorSelector - - lock sync.Mutex - lused map[string]bool - - dupCheck map[testKey]int - totalDups int64 - } - - testKey struct { - labels string - descriptor *sdkapi.Descriptor - } - - testImpl struct { - newInstrument func(meter metric.Meter, name string) SyncImpler - getUpdateValue func() number.Number - operate func(interface{}, context.Context, number.Number, []attribute.KeyValue) - newStore func() interface{} - - // storeCollect and storeExpect are the same for - // counters, different for lastValues, to ensure we are - // testing the timestamps correctly. - storeCollect func(store interface{}, value number.Number, ts time.Time) - storeExpect func(store interface{}, value number.Number) - readStore func(store interface{}) number.Number - equalValues func(a, b number.Number) bool - } - - SyncImpler interface { - SyncImpl() sdkapi.SyncImpl - } - - // lastValueState supports merging lastValue values, for the case - // where a race condition causes duplicate records. We always - // take the later timestamp. - lastValueState struct { - // raw has to be aligned for 64-bit atomic operations. - raw number.Number - ts time.Time - } -) - -func concurrency() int { - return concurrencyPerCPU * runtime.NumCPU() -} - -func canonicalizeLabels(ls []attribute.KeyValue) string { - copy := append(ls[0:0:0], ls...) - sort.SliceStable(copy, func(i, j int) bool { - return copy[i].Key < copy[j].Key - }) - var b strings.Builder - for _, kv := range copy { - b.WriteString(string(kv.Key)) - b.WriteString("=") - b.WriteString(kv.Value.Emit()) - b.WriteString("$") - } - return b.String() -} - -func getPeriod() time.Duration { - dur := math.Max( - float64(reclaimPeriod)/10, - float64(reclaimPeriod)*(1+0.1*rand.NormFloat64()), - ) - return time.Duration(dur) -} - -func (f *testFixture) someLabels() []attribute.KeyValue { - n := 1 + rand.Intn(3) - l := make([]attribute.KeyValue, n) - - for { - oused := map[string]bool{} - for i := 0; i < n; i++ { - var k string - for { - k = fmt.Sprint("k", rand.Intn(1000000000)) - if !oused[k] { - oused[k] = true - break - } - } - l[i] = attribute.String(k, fmt.Sprint("v", rand.Intn(1000000000))) - } - lc := canonicalizeLabels(l) - f.lock.Lock() - avail := !f.lused[lc] - if avail { - f.lused[lc] = true - f.lock.Unlock() - return l - } - f.lock.Unlock() - } -} - -func (f *testFixture) startWorker(impl *Accumulator, meter metric.Meter, wg *sync.WaitGroup, i int) { - ctx := context.Background() - name := fmt.Sprint("test_", i) - instrument := f.impl.newInstrument(meter, name) - var descriptor *sdkapi.Descriptor - if ii, ok := instrument.SyncImpl().(*syncInstrument); ok { - descriptor = &ii.descriptor - } - kvs := f.someLabels() - clabs := canonicalizeLabels(kvs) - dur := getPeriod() - key := testKey{ - labels: clabs, - descriptor: descriptor, - } - for { - sleep := time.Duration(rand.ExpFloat64() * float64(dur)) - time.Sleep(sleep) - value := f.impl.getUpdateValue() - f.impl.operate(instrument, ctx, value, kvs) - - actual, _ := f.expected.LoadOrStore(key, f.impl.newStore()) - - f.impl.storeExpect(actual, value) - - if atomic.LoadInt64(&f.stop) != 0 { - wg.Done() - return - } - } -} - -func (f *testFixture) assertTest(numCollect int) { - var allErrs []func() - csize := 0 - f.received.Range(func(key, gstore interface{}) bool { - csize++ - gvalue := f.impl.readStore(gstore) - - estore, loaded := f.expected.Load(key) - if !loaded { - allErrs = append(allErrs, func() { - f.T.Error("Could not locate expected key: ", key) - }) - return true - } - evalue := f.impl.readStore(estore) - - if !f.impl.equalValues(evalue, gvalue) { - allErrs = append(allErrs, func() { - f.T.Error("Expected value mismatch: ", - evalue, "!=", gvalue, " for ", key) - }) - } - return true - }) - rsize := 0 - f.expected.Range(func(key, value interface{}) bool { - rsize++ - if _, loaded := f.received.Load(key); !loaded { - allErrs = append(allErrs, func() { - f.T.Error("Did not receive expected key: ", key) - }) - } - return true - }) - if rsize != csize { - f.T.Error("Did not receive the correct set of metrics: Received != Expected", rsize, csize) - } - - for _, anErr := range allErrs { - anErr() - } - - // Note: It's useful to know the test triggers this condition, - // but we can't assert it. Infrequently no duplicates are - // found, and we can't really force a race to happen. - // - // fmt.Printf("Test duplicate records seen: %.1f%%\n", - // float64(100*f.totalDups/int64(numCollect*concurrency()))) -} - -func (f *testFixture) preCollect() { - // Collect calls Process in a single-threaded context. No need - // to lock this struct. - f.dupCheck = map[testKey]int{} -} - -func (*testFixture) Reader() export.Reader { - return nil -} - -func (f *testFixture) Process(accumulation export.Accumulation) error { - labels := accumulation.Labels().ToSlice() - key := testKey{ - labels: canonicalizeLabels(labels), - descriptor: accumulation.Descriptor(), - } - if f.dupCheck[key] == 0 { - f.dupCheck[key]++ - } else { - f.totalDups++ - } - - actual, _ := f.received.LoadOrStore(key, f.impl.newStore()) - - agg := accumulation.Aggregator() - switch accumulation.Descriptor().InstrumentKind() { - case sdkapi.CounterInstrumentKind: - sum, err := agg.(aggregation.Sum).Sum() - if err != nil { - f.T.Fatal("Sum error: ", err) - } - f.impl.storeCollect(actual, sum, time.Time{}) - case sdkapi.HistogramInstrumentKind: - lv, ts, err := agg.(aggregation.LastValue).LastValue() - if err != nil && err != aggregation.ErrNoData { - f.T.Fatal("Last value error: ", err) - } - f.impl.storeCollect(actual, lv, ts) - default: - panic("Not used in this test") - } - return nil -} - -func stressTest(t *testing.T, impl testImpl) { - ctx := context.Background() - t.Parallel() - fixture := &testFixture{ - T: t, - impl: impl, - lused: map[string]bool{}, - AggregatorSelector: processortest.AggregatorSelector(), - } - cc := concurrency() - - sdk := NewAccumulator(fixture) - meter := metric.WrapMeterImpl(sdk) - fixture.wg.Add(cc + 1) - - for i := 0; i < cc; i++ { - go fixture.startWorker(sdk, meter, &fixture.wg, i) - } - - numCollect := 0 - - go func() { - for { - time.Sleep(reclaimPeriod) - fixture.preCollect() - sdk.Collect(ctx) - numCollect++ - if atomic.LoadInt64(&fixture.stop) != 0 { - fixture.wg.Done() - return - } - } - }() - - time.Sleep(testRun) - atomic.StoreInt64(&fixture.stop, 1) - fixture.wg.Wait() - fixture.preCollect() - sdk.Collect(ctx) - numCollect++ - - fixture.assertTest(numCollect) -} - -func int64sEqual(a, b number.Number) bool { - return a.AsInt64() == b.AsInt64() -} - -func float64sEqual(a, b number.Number) bool { - diff := math.Abs(a.AsFloat64() - b.AsFloat64()) - return diff < math.Abs(a.AsFloat64())*epsilon -} - -// Counters - -func intCounterTestImpl() testImpl { - return testImpl{ - newInstrument: func(meter metric.Meter, name string) SyncImpler { - return Must(meter).NewInt64Counter(name + ".sum") - }, - getUpdateValue: func() number.Number { - for { - x := int64(rand.Intn(100)) - if x != 0 { - return number.NewInt64Number(x) - } - } - }, - operate: func(inst interface{}, ctx context.Context, value number.Number, labels []attribute.KeyValue) { - counter := inst.(metric.Int64Counter) - counter.Add(ctx, value.AsInt64(), labels...) - }, - newStore: func() interface{} { - n := number.NewInt64Number(0) - return &n - }, - storeCollect: func(store interface{}, value number.Number, _ time.Time) { - store.(*number.Number).AddInt64Atomic(value.AsInt64()) - }, - storeExpect: func(store interface{}, value number.Number) { - store.(*number.Number).AddInt64Atomic(value.AsInt64()) - }, - readStore: func(store interface{}) number.Number { - return store.(*number.Number).AsNumberAtomic() - }, - equalValues: int64sEqual, - } -} - -func TestStressInt64Counter(t *testing.T) { - stressTest(t, intCounterTestImpl()) -} - -func floatCounterTestImpl() testImpl { - return testImpl{ - newInstrument: func(meter metric.Meter, name string) SyncImpler { - return Must(meter).NewFloat64Counter(name + ".sum") - }, - getUpdateValue: func() number.Number { - for { - x := rand.Float64() - if x != 0 { - return number.NewFloat64Number(x) - } - } - }, - operate: func(inst interface{}, ctx context.Context, value number.Number, labels []attribute.KeyValue) { - counter := inst.(metric.Float64Counter) - counter.Add(ctx, value.AsFloat64(), labels...) - }, - newStore: func() interface{} { - n := number.NewFloat64Number(0.0) - return &n - }, - storeCollect: func(store interface{}, value number.Number, _ time.Time) { - store.(*number.Number).AddFloat64Atomic(value.AsFloat64()) - }, - storeExpect: func(store interface{}, value number.Number) { - store.(*number.Number).AddFloat64Atomic(value.AsFloat64()) - }, - readStore: func(store interface{}) number.Number { - return store.(*number.Number).AsNumberAtomic() - }, - equalValues: float64sEqual, - } -} - -func TestStressFloat64Counter(t *testing.T) { - stressTest(t, floatCounterTestImpl()) -} - -// LastValue - -func intLastValueTestImpl() testImpl { - return testImpl{ - newInstrument: func(meter metric.Meter, name string) SyncImpler { - return Must(meter).NewInt64Histogram(name + ".lastvalue") - }, - getUpdateValue: func() number.Number { - r1 := rand.Int63() - return number.NewInt64Number(rand.Int63() - r1) - }, - operate: func(inst interface{}, ctx context.Context, value number.Number, labels []attribute.KeyValue) { - histogram := inst.(metric.Int64Histogram) - histogram.Record(ctx, value.AsInt64(), labels...) - }, - newStore: func() interface{} { - return &lastValueState{ - raw: number.NewInt64Number(0), - } - }, - storeCollect: func(store interface{}, value number.Number, ts time.Time) { - gs := store.(*lastValueState) - - if !ts.Before(gs.ts) { - gs.ts = ts - gs.raw.SetInt64Atomic(value.AsInt64()) - } - }, - storeExpect: func(store interface{}, value number.Number) { - gs := store.(*lastValueState) - gs.raw.SetInt64Atomic(value.AsInt64()) - }, - readStore: func(store interface{}) number.Number { - gs := store.(*lastValueState) - return gs.raw.AsNumberAtomic() - }, - equalValues: int64sEqual, - } -} - -func TestStressInt64LastValue(t *testing.T) { - stressTest(t, intLastValueTestImpl()) -} - -func floatLastValueTestImpl() testImpl { - return testImpl{ - newInstrument: func(meter metric.Meter, name string) SyncImpler { - return Must(meter).NewFloat64Histogram(name + ".lastvalue") - }, - getUpdateValue: func() number.Number { - return number.NewFloat64Number((-0.5 + rand.Float64()) * 100000) - }, - operate: func(inst interface{}, ctx context.Context, value number.Number, labels []attribute.KeyValue) { - histogram := inst.(metric.Float64Histogram) - histogram.Record(ctx, value.AsFloat64(), labels...) - }, - newStore: func() interface{} { - return &lastValueState{ - raw: number.NewFloat64Number(0), - } - }, - storeCollect: func(store interface{}, value number.Number, ts time.Time) { - gs := store.(*lastValueState) - - if !ts.Before(gs.ts) { - gs.ts = ts - gs.raw.SetFloat64Atomic(value.AsFloat64()) - } - }, - storeExpect: func(store interface{}, value number.Number) { - gs := store.(*lastValueState) - gs.raw.SetFloat64Atomic(value.AsFloat64()) - }, - readStore: func(store interface{}) number.Number { - gs := store.(*lastValueState) - return gs.raw.AsNumberAtomic() - }, - equalValues: float64sEqual, - } -} - -func TestStressFloat64LastValue(t *testing.T) { - stressTest(t, floatLastValueTestImpl()) -}