diff --git a/CHANGELOG.md b/CHANGELOG.md index b82b2e983fa..16f3f0d0f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The package contains semantic conventions from the `v1.9.0` version of the OpenTelemetry specification. (#2792) - Add the `go.opentelemetry.io/otel/semconv/v1.10.0` package. The package contains semantic conventions from the `v1.10.0` version of the OpenTelemetry specification. (#2842) +- Added an in-memory exporter to metrictest to aid testing with a full SDK. (#2776) ### Fixed @@ -43,6 +44,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [0.29.0] - 2022-04-11 +### Added + - The metrics global package was added back into several test files. (#2764) - The `Meter` function is added back to the `go.opentelemetry.io/otel/metric/global` package. This function is a convenience function equivalent to calling `global.MeterProvider().Meter(...)`. (#2750) diff --git a/sdk/metric/aggregator/aggregatortest/test.go b/sdk/metric/aggregator/aggregatortest/test.go index b9ea62da9bd..75fd7d44ff5 100644 --- a/sdk/metric/aggregator/aggregatortest/test.go +++ b/sdk/metric/aggregator/aggregatortest/test.go @@ -28,7 +28,6 @@ import ( ottest "go.opentelemetry.io/otel/internal/internaltest" "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" ) @@ -65,7 +64,7 @@ func newProfiles() []Profile { } func NewAggregatorTest(mkind sdkapi.InstrumentKind, nkind number.Kind) *sdkapi.Descriptor { - desc := metrictest.NewDescriptor("test.name", mkind, nkind) + desc := sdkapi.NewDescriptor("test.name", mkind, nkind, "", "") return &desc } diff --git a/sdk/metric/export/aggregation/temporality_test.go b/sdk/metric/export/aggregation/temporality_test.go index 69b976da773..ab1682b729d 100644 --- a/sdk/metric/export/aggregation/temporality_test.go +++ b/sdk/metric/export/aggregation/temporality_test.go @@ -19,7 +19,6 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/sdk/metric/metrictest" "go.opentelemetry.io/otel/sdk/metric/number" "go.opentelemetry.io/otel/sdk/metric/sdkapi" ) @@ -59,7 +58,7 @@ func TestTemporalitySelectors(t *testing.T) { sAggTemp := StatelessTemporalitySelector() for _, ikind := range append(deltaMemoryTemporalties, cumulativeMemoryTemporalties...) { - desc := metrictest.NewDescriptor("instrument", ikind, number.Int64Kind) + desc := sdkapi.NewDescriptor("instrument", ikind, number.Int64Kind, "", "") var akind Kind if ikind.Adding() { diff --git a/sdk/metric/metrictest/config.go b/sdk/metric/metrictest/config.go new file mode 100644 index 00000000000..4531b178fdc --- /dev/null +++ b/sdk/metric/metrictest/config.go @@ -0,0 +1,57 @@ +// 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 "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + +type config struct { + temporalitySelector aggregation.TemporalitySelector +} + +func newConfig(opts ...Option) config { + cfg := config{ + temporalitySelector: aggregation.CumulativeTemporalitySelector(), + } + for _, opt := range opts { + cfg = opt.apply(cfg) + } + return cfg +} + +// Option allow for control of details of the TestMeterProvider created. +type Option interface { + apply(config) config +} + +type functionOption func(config) config + +func (f functionOption) apply(cfg config) config { + return f(cfg) +} + +// WithTemporalitySelector allows for the use of either cumulative (default) or +// delta metrics. +// +// Warning: the current SDK does not convert async instruments into delta +// temporality. +func WithTemporalitySelector(ts aggregation.TemporalitySelector) Option { + return functionOption(func(cfg config) config { + if ts == nil { + return cfg + } + cfg.temporalitySelector = ts + return cfg + }) +} diff --git a/sdk/metric/metrictest/doc.go b/sdk/metric/metrictest/doc.go new file mode 100644 index 00000000000..504384dd3ab --- /dev/null +++ b/sdk/metric/metrictest/doc.go @@ -0,0 +1,18 @@ +// 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. + +// The metrictest package is a collection of tools used to make testing parts of +// the SDK easier. + +package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest" diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go new file mode 100644 index 00000000000..e57c8664c7d --- /dev/null +++ b/sdk/metric/metrictest/exporter.go @@ -0,0 +1,180 @@ +// 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" + "fmt" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "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" + processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" + selector "go.opentelemetry.io/otel/sdk/metric/selector/simple" +) + +// Exporter is a manually collected exporter for testing the SDK. It does not +// satisfy the `export.Exporter` interface because it is not intended to be +// used with the periodic collection of the SDK, instead the test should +// manually call `Collect()` +// +// Exporters are not thread safe, and should only be used for testing. +type Exporter struct { + // Records contains the last metrics collected. + Records []ExportRecord + + controller *controller.Controller + temporalitySelector aggregation.TemporalitySelector +} + +// NewTestMeterProvider creates a MeterProvider and Exporter to be used in tests. +func NewTestMeterProvider(opts ...Option) (metric.MeterProvider, *Exporter) { + cfg := newConfig(opts...) + + c := controller.New( + processor.NewFactory( + selector.NewWithHistogramDistribution(), + cfg.temporalitySelector, + ), + controller.WithCollectPeriod(0), + ) + exp := &Exporter{ + controller: c, + temporalitySelector: cfg.temporalitySelector, + } + + return c, exp +} + +// ExportRecord represents one collected datapoint from the Exporter. +type ExportRecord struct { + InstrumentName string + InstrumentationLibrary Library + Attributes []attribute.KeyValue + AggregationKind aggregation.Kind + NumberKind number.Kind + Sum number.Number + Count uint64 + Histogram aggregation.Buckets + LastValue number.Number +} + +// Collect triggers the SDK's collect methods and then aggregates the data into +// ExportRecords. This will overwrite any previous collected metrics. +func (e *Exporter) Collect(ctx context.Context) error { + e.Records = []ExportRecord{} + + err := e.controller.Collect(ctx) + if err != nil { + return err + } + + return e.controller.ForEach(func(l instrumentation.Library, r export.Reader) error { + lib := Library{ + InstrumentationName: l.Name, + InstrumentationVersion: l.Version, + SchemaURL: l.SchemaURL, + } + + return r.ForEach(e.temporalitySelector, func(rec export.Record) error { + record := ExportRecord{ + InstrumentName: rec.Descriptor().Name(), + InstrumentationLibrary: lib, + Attributes: rec.Attributes().ToSlice(), + AggregationKind: rec.Aggregation().Kind(), + NumberKind: rec.Descriptor().NumberKind(), + } + + var err error + switch agg := rec.Aggregation().(type) { + case aggregation.Histogram: + record.AggregationKind = aggregation.HistogramKind + record.Histogram, err = agg.Histogram() + if err != nil { + return err + } + record.Sum, err = agg.Sum() + if err != nil { + return err + } + record.Count, err = agg.Count() + if err != nil { + return err + } + case aggregation.Count: + record.Count, err = agg.Count() + if err != nil { + return err + } + case aggregation.LastValue: + record.LastValue, _, err = agg.LastValue() + if err != nil { + return err + } + case aggregation.Sum: + record.Sum, err = agg.Sum() + if err != nil { + return err + } + } + + e.Records = append(e.Records, record) + return nil + }) + }) +} + +// GetRecords returns all Records found by the SDK. +func (e *Exporter) GetRecords() []ExportRecord { + return e.Records +} + +var errNotFound = fmt.Errorf("record not found") + +// GetByName returns the first Record with a matching instrument name. +func (e *Exporter) GetByName(name string) (ExportRecord, error) { + for _, rec := range e.Records { + if rec.InstrumentName == name { + return rec, nil + } + } + return ExportRecord{}, errNotFound +} + +// GetByNameAndAttributes returns the first Record with a matching name and the sub-set of attributes. +func (e *Exporter) GetByNameAndAttributes(name string, attributes []attribute.KeyValue) (ExportRecord, error) { + for _, rec := range e.Records { + if rec.InstrumentName == name && subSet(attributes, rec.Attributes) { + return rec, nil + } + } + return ExportRecord{}, errNotFound +} + +// subSet returns true if attributesA is a subset of attributesB. +func subSet(attributesA, attributesB []attribute.KeyValue) bool { + b := attribute.NewSet(attributesB...) + + for _, kv := range attributesA { + if v, found := b.Value(kv.Key); !found || v != kv.Value { + return false + } + } + return true +} diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go new file mode 100644 index 00000000000..8fa8e805bf1 --- /dev/null +++ b/sdk/metric/metrictest/exporter_test.go @@ -0,0 +1,424 @@ +// 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_test // import "go.opentelemetry.io/otel/sdk/metric/metrictest" + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/sdk/metric/export/aggregation" + "go.opentelemetry.io/otel/sdk/metric/metrictest" +) + +func TestSyncInstruments(t *testing.T) { + ctx := context.Background() + mp, exp := metrictest.NewTestMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncInstruments") + + t.Run("Float Counter", func(t *testing.T) { + fcnt, err := meter.SyncFloat64().Counter("fCount") + require.NoError(t, err) + + fcnt.Add(ctx, 2) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fCount") + require.NoError(t, err) + assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + }) + + t.Run("Float UpDownCounter", func(t *testing.T) { + fudcnt, err := meter.SyncFloat64().UpDownCounter("fUDCount") + require.NoError(t, err) + + fudcnt.Add(ctx, 3) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + require.NoError(t, err) + assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + + t.Run("Float Histogram", func(t *testing.T) { + fhis, err := meter.SyncFloat64().Histogram("fHist") + require.NoError(t, err) + + fhis.Record(ctx, 4) + fhis.Record(ctx, 5) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fHist") + require.NoError(t, err) + assert.InDelta(t, 9.0, out.Sum.AsFloat64(), 0.0001) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + }) + + t.Run("Int Counter", func(t *testing.T) { + icnt, err := meter.SyncInt64().Counter("iCount") + require.NoError(t, err) + + icnt.Add(ctx, 22) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iCount") + require.NoError(t, err) + assert.EqualValues(t, 22, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + t.Run("Int UpDownCounter", func(t *testing.T) { + iudcnt, err := meter.SyncInt64().UpDownCounter("iUDCount") + require.NoError(t, err) + + iudcnt.Add(ctx, 23) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + require.NoError(t, err) + assert.EqualValues(t, 23, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + t.Run("Int Histogram", func(t *testing.T) { + + ihis, err := meter.SyncInt64().Histogram("iHist") + require.NoError(t, err) + + ihis.Record(ctx, 24) + ihis.Record(ctx, 25) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iHist") + require.NoError(t, err) + assert.EqualValues(t, 49, out.Sum.AsInt64()) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + }) +} + +func TestSyncDeltaInstruments(t *testing.T) { + ctx := context.Background() + mp, exp := metrictest.NewTestMeterProvider(metrictest.WithTemporalitySelector(aggregation.DeltaTemporalitySelector())) + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncDeltaInstruments") + + t.Run("Float Counter", func(t *testing.T) { + fcnt, err := meter.SyncFloat64().Counter("fCount") + require.NoError(t, err) + + fcnt.Add(ctx, 2) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fCount") + require.NoError(t, err) + assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + }) + + t.Run("Float UpDownCounter", func(t *testing.T) { + fudcnt, err := meter.SyncFloat64().UpDownCounter("fUDCount") + require.NoError(t, err) + + fudcnt.Add(ctx, 3) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + require.NoError(t, err) + assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + + t.Run("Float Histogram", func(t *testing.T) { + fhis, err := meter.SyncFloat64().Histogram("fHist") + require.NoError(t, err) + + fhis.Record(ctx, 4) + fhis.Record(ctx, 5) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fHist") + require.NoError(t, err) + assert.InDelta(t, 9.0, out.Sum.AsFloat64(), 0.0001) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + }) + + t.Run("Int Counter", func(t *testing.T) { + icnt, err := meter.SyncInt64().Counter("iCount") + require.NoError(t, err) + + icnt.Add(ctx, 22) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iCount") + require.NoError(t, err) + assert.EqualValues(t, 22, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + t.Run("Int UpDownCounter", func(t *testing.T) { + iudcnt, err := meter.SyncInt64().UpDownCounter("iUDCount") + require.NoError(t, err) + + iudcnt.Add(ctx, 23) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + require.NoError(t, err) + assert.EqualValues(t, 23, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + t.Run("Int Histogram", func(t *testing.T) { + + ihis, err := meter.SyncInt64().Histogram("iHist") + require.NoError(t, err) + + ihis.Record(ctx, 24) + ihis.Record(ctx, 25) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iHist") + require.NoError(t, err) + assert.EqualValues(t, 49, out.Sum.AsInt64()) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + }) +} + +func TestAsyncInstruments(t *testing.T) { + ctx := context.Background() + mp, exp := metrictest.NewTestMeterProvider() + + t.Run("Float Counter", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_FloatCounter") + + fcnt, err := meter.AsyncFloat64().Counter("fCount") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + fcnt, + }, func(context.Context) { + fcnt.Observe(ctx, 2) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fCount") + require.NoError(t, err) + assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + }) + + t.Run("Float UpDownCounter", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_FloatUpDownCounter") + + fudcnt, err := meter.AsyncFloat64().UpDownCounter("fUDCount") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + fudcnt, + }, func(context.Context) { + fudcnt.Observe(ctx, 3) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + require.NoError(t, err) + assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + }) + + t.Run("Float Gauge", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_FloatGauge") + + fgauge, err := meter.AsyncFloat64().Gauge("fGauge") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + fgauge, + }, func(context.Context) { + fgauge.Observe(ctx, 4) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("fGauge") + require.NoError(t, err) + assert.InDelta(t, 4.0, out.LastValue.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) + }) + + t.Run("Int Counter", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_IntCounter") + + icnt, err := meter.AsyncInt64().Counter("iCount") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + icnt, + }, func(context.Context) { + icnt.Observe(ctx, 22) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iCount") + require.NoError(t, err) + assert.EqualValues(t, 22, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + }) + + t.Run("Int UpDownCounter", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_IntUpDownCounter") + + iudcnt, err := meter.AsyncInt64().UpDownCounter("iUDCount") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + iudcnt, + }, func(context.Context) { + iudcnt.Observe(ctx, 23) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + require.NoError(t, err) + assert.EqualValues(t, 23, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + }) + t.Run("Int Gauge", func(t *testing.T) { + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter_IntGauge") + + igauge, err := meter.AsyncInt64().Gauge("iGauge") + require.NoError(t, err) + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + igauge, + }, func(context.Context) { + igauge.Observe(ctx, 25) + }) + require.NoError(t, err) + + err = exp.Collect(context.Background()) + require.NoError(t, err) + + out, err := exp.GetByName("iGauge") + require.NoError(t, err) + assert.EqualValues(t, 25, out.LastValue.AsInt64()) + assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) + }) + +} + +func ExampleExporter_GetByName() { + mp, exp := metrictest.NewTestMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_ExampleExporter_GetByName") + + cnt, err := meter.SyncFloat64().Counter("fCount") + if err != nil { + panic("could not acquire counter") + } + + cnt.Add(context.Background(), 2.5) + + err = exp.Collect(context.Background()) + if err != nil { + panic("collection failed") + } + + out, _ := exp.GetByName("fCount") + + fmt.Println(out.Sum.AsFloat64()) + // Output: 2.5 +} + +func ExampleExporter_GetByNameAndAttributes() { + mp, exp := metrictest.NewTestMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_ExampleExporter_GetByNameAndAttributes") + + cnt, err := meter.SyncFloat64().Counter("fCount") + if err != nil { + panic("could not acquire counter") + } + + cnt.Add(context.Background(), 4, attribute.String("foo", "bar"), attribute.Bool("found", false)) + + err = exp.Collect(context.Background()) + if err != nil { + panic("collection failed") + } + + out, err := exp.GetByNameAndAttributes("fCount", []attribute.KeyValue{attribute.String("foo", "bar")}) + if err != nil { + println(err.Error()) + } + + fmt.Println(out.Sum.AsFloat64()) + // Output: 4 +}