From 292d175897864cb4a0d4f4fcffc713f6df67d5f3 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:26:18 +0000 Subject: [PATCH 01/11] in memory exporter --- sdk/metric/aggregator/aggregatortest/test.go | 3 +- .../export/aggregation/temporality_test.go | 3 +- sdk/metric/metrictest/doc.go | 18 ++ sdk/metric/metrictest/exporter.go | 170 ++++++++++++++ sdk/metric/metrictest/exporter_test.go | 211 ++++++++++++++++++ 5 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 sdk/metric/metrictest/doc.go create mode 100644 sdk/metric/metrictest/exporter.go create mode 100644 sdk/metric/metrictest/exporter_test.go 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/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..2600dc03121 --- /dev/null +++ b/sdk/metric/metrictest/exporter.go @@ -0,0 +1,170 @@ +// 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" +) + +type Exporter struct { + exports []ExportRecord + // resource *resource.Resource + + controller *controller.Controller +} + +type ExportRecord struct { + InstrumentName string + InstrumentationLibrary Library + Labels []attribute.KeyValue + AggregationKind aggregation.Kind + Sum number.Number + Count uint64 + Histogram aggregation.Buckets + LastValue number.Number +} + +// Export is called immediately after completing a collection +// pass in the SDK. +// +// The Context comes from the controller that initiated +// collection. +// +// The InstrumentationLibraryReader interface refers to the +// Processor that just completed collection. +func (e *Exporter) Collect(ctx context.Context) error { + e.exports = []ExportRecord{} + + e.controller.Collect(ctx) + + e.controller.ForEach(func(l instrumentation.Library, r export.Reader) error { + lib := Library{ + InstrumentationName: l.Name, + InstrumentationVersion: l.Version, + SchemaURL: l.SchemaURL, + } + + r.ForEach(aggregation.CumulativeTemporalitySelector(), func(rec export.Record) error { + record := ExportRecord{ + InstrumentName: rec.Descriptor().Name(), + InstrumentationLibrary: lib, + Labels: rec.Labels().ToSlice(), + AggregationKind: rec.Aggregation().Kind(), + } + + 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.exports = append(e.exports, record) + return nil + }) + return nil + }) + return nil +} + +func (e *Exporter) GetRecords() []ExportRecord { + return e.exports +} + +var ErrNotFound = fmt.Errorf("record not found") + +func (e *Exporter) GetByName(name string) (ExportRecord, error) { + for _, rec := range e.exports { + if rec.InstrumentName == name { + return rec, nil + } + } + return ExportRecord{}, ErrNotFound +} + +func (e *Exporter) GetByNameAndLabels(name string, labels []attribute.KeyValue) (ExportRecord, error) { + for _, rec := range e.exports { + if rec.InstrumentName == name && labelsMatch(labels, rec.Labels) { + return rec, nil + } + } + return ExportRecord{}, ErrNotFound +} + +func labelsMatch(labelsA, labelsB []attribute.KeyValue) bool { + if len(labelsA) != len(labelsB) { + return false + } + for i := range labelsA { + if labelsA[i] != labelsB[i] { + return false + } + } + + return true +} + +func NewMeterProvider() (metric.MeterProvider, *Exporter) { + c := controller.New( + processor.NewFactory( + selector.NewWithHistogramDistribution(), + aggregation.CumulativeTemporalitySelector(), + ), + controller.WithCollectPeriod(0), + ) + exp := &Exporter{ + + controller: c, + } + + return c, exp +} diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go new file mode 100644 index 00000000000..018cd5e1e7e --- /dev/null +++ b/sdk/metric/metrictest/exporter_test.go @@ -0,0 +1,211 @@ +// 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 TestSyncCounter(t *testing.T) { + ctx := context.Background() + mp, exp := metrictest.NewMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + + fcnt, err := meter.SyncFloat64().Counter("fCount") + require.NoError(t, err) + fudcnt, err := meter.SyncFloat64().UpDownCounter("fUDCount") + require.NoError(t, err) + fhis, err := meter.SyncFloat64().Histogram("fHist") + require.NoError(t, err) + + icnt, err := meter.SyncInt64().Counter("iCount") + require.NoError(t, err) + iudcnt, err := meter.SyncInt64().UpDownCounter("iUDCount") + require.NoError(t, err) + ihis, err := meter.SyncInt64().Histogram("iHist") + require.NoError(t, err) + + fcnt.Add(ctx, 2) + fudcnt.Add(ctx, 3) + fhis.Record(ctx, 4) + fhis.Record(ctx, 5) + + icnt.Add(ctx, 22) + iudcnt.Add(ctx, 23) + ihis.Record(ctx, 24) + ihis.Record(ctx, 25) + + err = exp.Collect(context.Background()) + assert.NoError(t, err) + + out, err := exp.GetByName("fCount") + assert.NoError(t, err) + assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("fUDCount") + assert.NoError(t, err) + assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("fHist") + assert.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) + + out, err = exp.GetByName("iCount") + assert.NoError(t, err) + assert.EqualValues(t, 22, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("iUDCount") + assert.NoError(t, err) + assert.EqualValues(t, 23, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("iHist") + assert.NoError(t, err) + assert.EqualValues(t, 49, out.Sum.AsInt64()) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) +} + +func TestAsyncCounter(t *testing.T) { + ctx := context.Background() + mp, exp := metrictest.NewMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + + fcnt, err := meter.AsyncFloat64().Counter("fCount") + require.NoError(t, err) + fudcnt, err := meter.AsyncFloat64().UpDownCounter("fUDCount") + require.NoError(t, err) + fgauge, err := meter.AsyncFloat64().Gauge("fGauge") + require.NoError(t, err) + + icnt, err := meter.AsyncInt64().Counter("iCount") + require.NoError(t, err) + iudcnt, err := meter.AsyncInt64().UpDownCounter("iUDCount") + require.NoError(t, err) + igauge, err := meter.AsyncInt64().Gauge("iGauge") + require.NoError(t, err) + + meter.RegisterCallback( + []instrument.Asynchronous{ + fcnt, + fudcnt, + fgauge, + icnt, + iudcnt, + igauge, + }, func(context.Context) { + fcnt.Observe(ctx, 2) + fudcnt.Observe(ctx, 3) + fgauge.Observe(ctx, 4) + icnt.Observe(ctx, 22) + iudcnt.Observe(ctx, 23) + igauge.Observe(ctx, 25) + }) + + err = exp.Collect(context.Background()) + assert.NoError(t, err) + + out, err := exp.GetByName("fCount") + assert.NoError(t, err) + assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("fUDCount") + assert.NoError(t, err) + assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("fGauge") + assert.NoError(t, err) + assert.InDelta(t, 4.0, out.LastValue.AsFloat64(), 0.0001) + assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) + + out, err = exp.GetByName("iCount") + assert.NoError(t, err) + assert.EqualValues(t, 22, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("iUDCount") + assert.NoError(t, err) + assert.EqualValues(t, 23, out.Sum.AsInt64()) + assert.Equal(t, aggregation.SumKind, out.AggregationKind) + + out, err = exp.GetByName("iGauge") + assert.NoError(t, err) + assert.EqualValues(t, 25, out.LastValue.AsInt64()) + assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) +} + +func ExampleExporter_GetByName() { + mp, exp := metrictest.NewMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + + 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_GetByNameAndLabels() { + mp, exp := metrictest.NewMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + + cnt, err := meter.SyncFloat64().Counter("fCount") + if err != nil { + panic("could not acquire counter") + } + + cnt.Add(context.Background(), 4, attribute.String("foo", "bar")) + + err = exp.Collect(context.Background()) + if err != nil { + panic("collection failed") + } + + out, err := exp.GetByNameAndLabels("fCount", []attribute.KeyValue{attribute.String("foo", "bar")}) + if err != nil { + println(err.Error()) + } + + fmt.Println(out.Sum.AsFloat64()) + // Output: 4 + +} From 9d92eb2ac180df6cb00efbe875cebd2d50eba2f8 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:53:50 +0000 Subject: [PATCH 02/11] Add comments --- sdk/metric/metrictest/exporter.go | 54 ++++++++++++++------------ sdk/metric/metrictest/exporter_test.go | 10 ++--- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go index 2600dc03121..95981cc474f 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -29,6 +29,12 @@ import ( 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 { exports []ExportRecord // resource *resource.Resource @@ -36,6 +42,23 @@ type Exporter struct { controller *controller.Controller } +func NewTestMeterProvider() (metric.MeterProvider, *Exporter) { + c := controller.New( + processor.NewFactory( + selector.NewWithHistogramDistribution(), + aggregation.CumulativeTemporalitySelector(), + ), + controller.WithCollectPeriod(0), + ) + exp := &Exporter{ + + controller: c, + } + + return c, exp +} + +// ExportRecord represents one collected datapoint from the Exporter. type ExportRecord struct { InstrumentName string InstrumentationLibrary Library @@ -47,14 +70,8 @@ type ExportRecord struct { LastValue number.Number } -// Export is called immediately after completing a collection -// pass in the SDK. -// -// The Context comes from the controller that initiated -// collection. -// -// The InstrumentationLibraryReader interface refers to the -// Processor that just completed collection. +// Collect triggers the SDK's collect methods and then aggregates the data into +// `ExportRecord`s. func (e *Exporter) Collect(ctx context.Context) error { e.exports = []ExportRecord{} @@ -116,12 +133,14 @@ func (e *Exporter) Collect(ctx context.Context) error { return nil } +// GetRecords returns all Records found by the SDK func (e *Exporter) GetRecords() []ExportRecord { return e.exports } var ErrNotFound = fmt.Errorf("record not found") +// GetByName returns the first Record with a matching name. func (e *Exporter) GetByName(name string) (ExportRecord, error) { for _, rec := range e.exports { if rec.InstrumentName == name { @@ -131,6 +150,7 @@ func (e *Exporter) GetByName(name string) (ExportRecord, error) { return ExportRecord{}, ErrNotFound } +// GetByNameAndLabels returns the first Record with a matching name and set of labels func (e *Exporter) GetByNameAndLabels(name string, labels []attribute.KeyValue) (ExportRecord, error) { for _, rec := range e.exports { if rec.InstrumentName == name && labelsMatch(labels, rec.Labels) { @@ -141,7 +161,7 @@ func (e *Exporter) GetByNameAndLabels(name string, labels []attribute.KeyValue) } func labelsMatch(labelsA, labelsB []attribute.KeyValue) bool { - if len(labelsA) != len(labelsB) { + if len(labelsA) == len(labelsB) { return false } for i := range labelsA { @@ -152,19 +172,3 @@ func labelsMatch(labelsA, labelsB []attribute.KeyValue) bool { return true } - -func NewMeterProvider() (metric.MeterProvider, *Exporter) { - c := controller.New( - processor.NewFactory( - selector.NewWithHistogramDistribution(), - aggregation.CumulativeTemporalitySelector(), - ), - controller.WithCollectPeriod(0), - ) - exp := &Exporter{ - - controller: c, - } - - return c, exp -} diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go index 018cd5e1e7e..6615e29cb8e 100644 --- a/sdk/metric/metrictest/exporter_test.go +++ b/sdk/metric/metrictest/exporter_test.go @@ -29,7 +29,7 @@ import ( func TestSyncCounter(t *testing.T) { ctx := context.Background() - mp, exp := metrictest.NewMeterProvider() + mp, exp := metrictest.NewTestMeterProvider() meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") fcnt, err := meter.SyncFloat64().Counter("fCount") @@ -94,8 +94,8 @@ func TestSyncCounter(t *testing.T) { func TestAsyncCounter(t *testing.T) { ctx := context.Background() - mp, exp := metrictest.NewMeterProvider() - meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + mp, exp := metrictest.NewTestMeterProvider() + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter") fcnt, err := meter.AsyncFloat64().Counter("fCount") require.NoError(t, err) @@ -163,7 +163,7 @@ func TestAsyncCounter(t *testing.T) { } func ExampleExporter_GetByName() { - mp, exp := metrictest.NewMeterProvider() + mp, exp := metrictest.NewTestMeterProvider() meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") cnt, err := meter.SyncFloat64().Counter("fCount") @@ -185,7 +185,7 @@ func ExampleExporter_GetByName() { } func ExampleExporter_GetByNameAndLabels() { - mp, exp := metrictest.NewMeterProvider() + mp, exp := metrictest.NewTestMeterProvider() meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") cnt, err := meter.SyncFloat64().Counter("fCount") From 3e6e637e6517164459b3c63b4983031bdd6f206a Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:53:52 +0000 Subject: [PATCH 03/11] Split tests and address PR comments --- sdk/metric/metrictest/exporter.go | 14 +- sdk/metric/metrictest/exporter_test.go | 366 ++++++++++++++++--------- 2 files changed, 246 insertions(+), 134 deletions(-) diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go index 95981cc474f..09974fa416e 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -51,7 +51,6 @@ func NewTestMeterProvider() (metric.MeterProvider, *Exporter) { controller.WithCollectPeriod(0), ) exp := &Exporter{ - controller: c, } @@ -75,16 +74,19 @@ type ExportRecord struct { func (e *Exporter) Collect(ctx context.Context) error { e.exports = []ExportRecord{} - e.controller.Collect(ctx) + err := e.controller.Collect(ctx) + if err != nil { + return err + } - e.controller.ForEach(func(l instrumentation.Library, r export.Reader) error { + return e.controller.ForEach(func(l instrumentation.Library, r export.Reader) error { lib := Library{ InstrumentationName: l.Name, InstrumentationVersion: l.Version, SchemaURL: l.SchemaURL, } - r.ForEach(aggregation.CumulativeTemporalitySelector(), func(rec export.Record) error { + return r.ForEach(aggregation.CumulativeTemporalitySelector(), func(rec export.Record) error { record := ExportRecord{ InstrumentName: rec.Descriptor().Name(), InstrumentationLibrary: lib, @@ -128,9 +130,7 @@ func (e *Exporter) Collect(ctx context.Context) error { e.exports = append(e.exports, record) return nil }) - return nil }) - return nil } // GetRecords returns all Records found by the SDK @@ -161,7 +161,7 @@ func (e *Exporter) GetByNameAndLabels(name string, labels []attribute.KeyValue) } func labelsMatch(labelsA, labelsB []attribute.KeyValue) bool { - if len(labelsA) == len(labelsB) { + if len(labelsA) != len(labelsB) { return false } for i := range labelsA { diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go index 6615e29cb8e..347fbcc0e72 100644 --- a/sdk/metric/metrictest/exporter_test.go +++ b/sdk/metric/metrictest/exporter_test.go @@ -21,150 +21,262 @@ import ( "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 TestSyncCounter(t *testing.T) { +func TestSyncInstruments(t *testing.T) { ctx := context.Background() mp, exp := metrictest.NewTestMeterProvider() - meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") - - fcnt, err := meter.SyncFloat64().Counter("fCount") - require.NoError(t, err) - fudcnt, err := meter.SyncFloat64().UpDownCounter("fUDCount") - require.NoError(t, err) - fhis, err := meter.SyncFloat64().Histogram("fHist") - require.NoError(t, err) - - icnt, err := meter.SyncInt64().Counter("iCount") - require.NoError(t, err) - iudcnt, err := meter.SyncInt64().UpDownCounter("iUDCount") - require.NoError(t, err) - ihis, err := meter.SyncInt64().Histogram("iHist") - require.NoError(t, err) - - fcnt.Add(ctx, 2) - fudcnt.Add(ctx, 3) - fhis.Record(ctx, 4) - fhis.Record(ctx, 5) - - icnt.Add(ctx, 22) - iudcnt.Add(ctx, 23) - ihis.Record(ctx, 24) - ihis.Record(ctx, 25) + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncInstruments") - err = exp.Collect(context.Background()) - assert.NoError(t, err) - - out, err := exp.GetByName("fCount") - assert.NoError(t, err) - assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("fUDCount") - assert.NoError(t, err) - assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("fHist") - assert.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) - - out, err = exp.GetByName("iCount") - assert.NoError(t, err) - assert.EqualValues(t, 22, out.Sum.AsInt64()) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("iUDCount") - assert.NoError(t, err) - assert.EqualValues(t, 23, out.Sum.AsInt64()) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("iHist") - assert.NoError(t, err) - assert.EqualValues(t, 49, out.Sum.AsInt64()) - assert.EqualValues(t, 2, out.Count) - assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + 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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fHist") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iHist") + assert.NoError(t, err) + assert.EqualValues(t, 49, out.Sum.AsInt64()) + assert.EqualValues(t, 2, out.Count) + assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) + }) } -func TestAsyncCounter(t *testing.T) { +func TestAsyncInstruments(t *testing.T) { ctx := context.Background() mp, exp := metrictest.NewTestMeterProvider() - meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestAsyncCounter") - - fcnt, err := meter.AsyncFloat64().Counter("fCount") - require.NoError(t, err) - fudcnt, err := meter.AsyncFloat64().UpDownCounter("fUDCount") - require.NoError(t, err) - fgauge, err := meter.AsyncFloat64().Gauge("fGauge") - require.NoError(t, err) - - icnt, err := meter.AsyncInt64().Counter("iCount") - require.NoError(t, err) - iudcnt, err := meter.AsyncInt64().UpDownCounter("iUDCount") - require.NoError(t, err) - igauge, err := meter.AsyncInt64().Gauge("iGauge") - require.NoError(t, err) - - meter.RegisterCallback( - []instrument.Asynchronous{ - fcnt, - fudcnt, - fgauge, - icnt, - iudcnt, - igauge, - }, func(context.Context) { - fcnt.Observe(ctx, 2) - fudcnt.Observe(ctx, 3) - fgauge.Observe(ctx, 4) - icnt.Observe(ctx, 22) - iudcnt.Observe(ctx, 23) - igauge.Observe(ctx, 25) - }) - err = exp.Collect(context.Background()) - assert.NoError(t, err) - - out, err := exp.GetByName("fCount") - assert.NoError(t, err) - assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("fUDCount") - assert.NoError(t, err) - assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("fGauge") - assert.NoError(t, err) - assert.InDelta(t, 4.0, out.LastValue.AsFloat64(), 0.0001) - assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) - - out, err = exp.GetByName("iCount") - assert.NoError(t, err) - assert.EqualValues(t, 22, out.Sum.AsInt64()) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("iUDCount") - assert.NoError(t, err) - assert.EqualValues(t, 23, out.Sum.AsInt64()) - assert.Equal(t, aggregation.SumKind, out.AggregationKind) - - out, err = exp.GetByName("iGauge") - assert.NoError(t, err) - assert.EqualValues(t, 25, out.LastValue.AsInt64()) - assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) + 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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fGauge") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iGauge") + assert.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_TestSyncCounter") + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_ExampleExporter_GetByName") cnt, err := meter.SyncFloat64().Counter("fCount") if err != nil { @@ -186,7 +298,7 @@ func ExampleExporter_GetByName() { func ExampleExporter_GetByNameAndLabels() { mp, exp := metrictest.NewTestMeterProvider() - meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_TestSyncCounter") + meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_ExampleExporter_GetByNameAndLabels") cnt, err := meter.SyncFloat64().Counter("fCount") if err != nil { From 3ce004505f20ab24c9c9b568824d6e3f23994c19 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:18:19 +0000 Subject: [PATCH 04/11] address PR comments. --- sdk/metric/metrictest/config.go | 57 +++++++++++++ sdk/metric/metrictest/exporter.go | 64 +++++++------- sdk/metric/metrictest/exporter_test.go | 110 ++++++++++++++++++++++++- 3 files changed, 197 insertions(+), 34 deletions(-) create mode 100644 sdk/metric/metrictest/config.go diff --git a/sdk/metric/metrictest/config.go b/sdk/metric/metrictest/config.go new file mode 100644 index 00000000000..8d88399959b --- /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/exporter.go b/sdk/metric/metrictest/exporter.go index 09974fa416e..ccd69d5bd86 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -36,22 +36,27 @@ import ( // // Exporters are not thread safe, and should only be used for testing. type Exporter struct { - exports []ExportRecord - // resource *resource.Resource + // Records contains the last metrics collected. + Records []ExportRecord - controller *controller.Controller + controller *controller.Controller + temporalitySelector aggregation.TemporalitySelector } -func NewTestMeterProvider() (metric.MeterProvider, *Exporter) { +// 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(), - aggregation.CumulativeTemporalitySelector(), + cfg.temporalitySelector, ), controller.WithCollectPeriod(0), ) exp := &Exporter{ - controller: c, + controller: c, + temporalitySelector: cfg.temporalitySelector, } return c, exp @@ -61,7 +66,7 @@ func NewTestMeterProvider() (metric.MeterProvider, *Exporter) { type ExportRecord struct { InstrumentName string InstrumentationLibrary Library - Labels []attribute.KeyValue + Attributes []attribute.KeyValue AggregationKind aggregation.Kind Sum number.Number Count uint64 @@ -70,9 +75,9 @@ type ExportRecord struct { } // Collect triggers the SDK's collect methods and then aggregates the data into -// `ExportRecord`s. +// ExportRecords. This will overwrite any previous collected metrics. func (e *Exporter) Collect(ctx context.Context) error { - e.exports = []ExportRecord{} + e.Records = []ExportRecord{} err := e.controller.Collect(ctx) if err != nil { @@ -86,11 +91,11 @@ func (e *Exporter) Collect(ctx context.Context) error { SchemaURL: l.SchemaURL, } - return r.ForEach(aggregation.CumulativeTemporalitySelector(), func(rec export.Record) error { + return r.ForEach(e.temporalitySelector, func(rec export.Record) error { record := ExportRecord{ InstrumentName: rec.Descriptor().Name(), InstrumentationLibrary: lib, - Labels: rec.Labels().ToSlice(), + Attributes: rec.Labels().ToSlice(), AggregationKind: rec.Aggregation().Kind(), } @@ -127,48 +132,47 @@ func (e *Exporter) Collect(ctx context.Context) error { } } - e.exports = append(e.exports, record) + e.Records = append(e.Records, record) return nil }) }) } -// GetRecords returns all Records found by the SDK +// GetRecords returns all Records found by the SDK. func (e *Exporter) GetRecords() []ExportRecord { - return e.exports + return e.Records[:] } -var ErrNotFound = fmt.Errorf("record not found") +var errNotFound = fmt.Errorf("record not found") -// GetByName returns the first Record with a matching name. +// GetByName returns the first Record with a matching instrument name. func (e *Exporter) GetByName(name string) (ExportRecord, error) { - for _, rec := range e.exports { + for _, rec := range e.Records { if rec.InstrumentName == name { return rec, nil } } - return ExportRecord{}, ErrNotFound + return ExportRecord{}, errNotFound } -// GetByNameAndLabels returns the first Record with a matching name and set of labels -func (e *Exporter) GetByNameAndLabels(name string, labels []attribute.KeyValue) (ExportRecord, error) { - for _, rec := range e.exports { - if rec.InstrumentName == name && labelsMatch(labels, rec.Labels) { +// GetByNameAndAttributes returns the first Record with a matching name and 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 + return ExportRecord{}, errNotFound } -func labelsMatch(labelsA, labelsB []attribute.KeyValue) bool { - if len(labelsA) != len(labelsB) { - return false - } - for i := range labelsA { - if labelsA[i] != labelsB[i] { +// subSet returns true if A is a subset of B +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 index 347fbcc0e72..6d0e882d443 100644 --- a/sdk/metric/metrictest/exporter_test.go +++ b/sdk/metric/metrictest/exporter_test.go @@ -130,6 +130,108 @@ func TestSyncInstruments(t *testing.T) { }) } +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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("fHist") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iUDCount") + assert.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()) + assert.NoError(t, err) + + out, err := exp.GetByName("iHist") + assert.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() @@ -296,23 +398,23 @@ func ExampleExporter_GetByName() { // Output: 2.5 } -func ExampleExporter_GetByNameAndLabels() { +func ExampleExporter_GetByNameAndAttributes() { mp, exp := metrictest.NewTestMeterProvider() - meter := mp.Meter("go.opentelemetry.io/otel/sdk/metric/metrictest/exporter_ExampleExporter_GetByNameAndLabels") + 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")) + 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.GetByNameAndLabels("fCount", []attribute.KeyValue{attribute.String("foo", "bar")}) + out, err := exp.GetByNameAndAttributes("fCount", []attribute.KeyValue{attribute.String("foo", "bar")}) if err != nil { println(err.Error()) } From 0ef294535851ab38748a8ca0b43d0947ee525cb6 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:22:28 +0000 Subject: [PATCH 05/11] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd5a72511d..d3da853fc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Resolve supply-chain failure for the markdown-link-checker GitHub action by calling the CLI directly. (#2834) - Remove import of `testing` package in non-tests builds. (#2786) +### Added + +- Added an in-memory exporter to metrictest to aid testing with a full SDK (#2776) + ## [0.29.0] - 2022-04-11 ### Added From 241d1b271920b7e5606780f8d8896b975bd9df76 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:25:01 +0000 Subject: [PATCH 06/11] fix Changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3da853fc16..d00be1af0d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Resolve supply-chain failure for the markdown-link-checker GitHub action by calling the CLI directly. (#2834) - Remove import of `testing` package in non-tests builds. (#2786) -### Added +### Added - Added an in-memory exporter to metrictest to aid testing with a full SDK (#2776) From 60278185b092cada0530caaf472665d444710999 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Fri, 22 Apr 2022 07:44:32 -0500 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Tyler Yahn Co-authored-by: Joshua MacDonald --- CHANGELOG.md | 2 +- sdk/metric/metrictest/config.go | 4 ++-- sdk/metric/metrictest/exporter.go | 6 +++--- sdk/metric/metrictest/exporter_test.go | 5 ++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d00be1af0d9..b721417c49b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- Added an in-memory exporter to metrictest to aid testing with a full SDK (#2776) +- Added an in-memory exporter to metrictest to aid testing with a full SDK. (#2776) ## [0.29.0] - 2022-04-11 diff --git a/sdk/metric/metrictest/config.go b/sdk/metric/metrictest/config.go index 8d88399959b..4531b178fdc 100644 --- a/sdk/metric/metrictest/config.go +++ b/sdk/metric/metrictest/config.go @@ -43,8 +43,8 @@ func (f functionOption) apply(cfg config) config { // WithTemporalitySelector allows for the use of either cumulative (default) or // delta metrics. - -// Warning the current SDK does not convert async instruments into delta +// +// Warning: the current SDK does not convert async instruments into delta // temporality. func WithTemporalitySelector(ts aggregation.TemporalitySelector) Option { return functionOption(func(cfg config) config { diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go index ccd69d5bd86..1559ff0efac 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -140,7 +140,7 @@ func (e *Exporter) Collect(ctx context.Context) error { // GetRecords returns all Records found by the SDK. func (e *Exporter) GetRecords() []ExportRecord { - return e.Records[:] + return e.Records } var errNotFound = fmt.Errorf("record not found") @@ -155,7 +155,7 @@ func (e *Exporter) GetByName(name string) (ExportRecord, error) { return ExportRecord{}, errNotFound } -// GetByNameAndAttributes returns the first Record with a matching name and set of Attributes. +// 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) { @@ -165,7 +165,7 @@ func (e *Exporter) GetByNameAndAttributes(name string, attributes []attribute.Ke return ExportRecord{}, errNotFound } -// subSet returns true if A is a subset of B +// subSet returns true if attributesA is a subset of attributesB. func subSet(attributesA, attributesB []attribute.KeyValue) bool { b := attribute.NewSet(attributesB...) diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go index 6d0e882d443..c3baedeaf26 100644 --- a/sdk/metric/metrictest/exporter_test.go +++ b/sdk/metric/metrictest/exporter_test.go @@ -40,10 +40,10 @@ func TestSyncInstruments(t *testing.T) { fcnt.Add(ctx, 2) err = exp.Collect(context.Background()) - assert.NoError(t, err) + requrie.NoError(t, err) out, err := exp.GetByName("fCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) }) @@ -421,5 +421,4 @@ func ExampleExporter_GetByNameAndAttributes() { fmt.Println(out.Sum.AsFloat64()) // Output: 4 - } From af15c12290ea581c238ae2b0fb19543147960f04 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Fri, 22 Apr 2022 12:47:16 +0000 Subject: [PATCH 08/11] Add numberkind --- sdk/metric/metrictest/exporter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go index 1559ff0efac..486dba28b9e 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -68,6 +68,7 @@ type ExportRecord struct { InstrumentationLibrary Library Attributes []attribute.KeyValue AggregationKind aggregation.Kind + NumberKind number.Kind Sum number.Number Count uint64 Histogram aggregation.Buckets @@ -97,6 +98,7 @@ func (e *Exporter) Collect(ctx context.Context) error { InstrumentationLibrary: lib, Attributes: rec.Labels().ToSlice(), AggregationKind: rec.Aggregation().Kind(), + NumberKind: rec.Descriptor().NumberKind(), } var err error From 60cef94978a04a62350bd4b80825e9a4d77e715a Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:46:58 +0000 Subject: [PATCH 09/11] Made the tests require.NoError --- sdk/metric/metrictest/exporter_test.go | 70 +++++++++++++------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/sdk/metric/metrictest/exporter_test.go b/sdk/metric/metrictest/exporter_test.go index c3baedeaf26..8fa8e805bf1 100644 --- a/sdk/metric/metrictest/exporter_test.go +++ b/sdk/metric/metrictest/exporter_test.go @@ -40,7 +40,7 @@ func TestSyncInstruments(t *testing.T) { fcnt.Add(ctx, 2) err = exp.Collect(context.Background()) - requrie.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fCount") require.NoError(t, err) @@ -55,10 +55,10 @@ func TestSyncInstruments(t *testing.T) { fudcnt.Add(ctx, 3) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -72,10 +72,10 @@ func TestSyncInstruments(t *testing.T) { fhis.Record(ctx, 5) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fHist") - assert.NoError(t, err) + 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) @@ -88,10 +88,10 @@ func TestSyncInstruments(t *testing.T) { icnt.Add(ctx, 22) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 22, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -103,10 +103,10 @@ func TestSyncInstruments(t *testing.T) { iudcnt.Add(ctx, 23) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 23, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -120,10 +120,10 @@ func TestSyncInstruments(t *testing.T) { ihis.Record(ctx, 25) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iHist") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 49, out.Sum.AsInt64()) assert.EqualValues(t, 2, out.Count) assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) @@ -142,10 +142,10 @@ func TestSyncDeltaInstruments(t *testing.T) { fcnt.Add(ctx, 2) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) }) @@ -157,10 +157,10 @@ func TestSyncDeltaInstruments(t *testing.T) { fudcnt.Add(ctx, 3) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -174,10 +174,10 @@ func TestSyncDeltaInstruments(t *testing.T) { fhis.Record(ctx, 5) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fHist") - assert.NoError(t, err) + 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) @@ -190,10 +190,10 @@ func TestSyncDeltaInstruments(t *testing.T) { icnt.Add(ctx, 22) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 22, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -205,10 +205,10 @@ func TestSyncDeltaInstruments(t *testing.T) { iudcnt.Add(ctx, 23) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 23, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -222,10 +222,10 @@ func TestSyncDeltaInstruments(t *testing.T) { ihis.Record(ctx, 25) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iHist") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 49, out.Sum.AsInt64()) assert.EqualValues(t, 2, out.Count) assert.Equal(t, aggregation.HistogramKind, out.AggregationKind) @@ -251,10 +251,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 2.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) }) @@ -274,10 +274,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 3.0, out.Sum.AsFloat64(), 0.0001) assert.Equal(t, aggregation.SumKind, out.AggregationKind) }) @@ -297,10 +297,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("fGauge") - assert.NoError(t, err) + require.NoError(t, err) assert.InDelta(t, 4.0, out.LastValue.AsFloat64(), 0.0001) assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) }) @@ -320,10 +320,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 22, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) }) @@ -343,10 +343,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iUDCount") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 23, out.Sum.AsInt64()) assert.Equal(t, aggregation.SumKind, out.AggregationKind) @@ -366,10 +366,10 @@ func TestAsyncInstruments(t *testing.T) { require.NoError(t, err) err = exp.Collect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) out, err := exp.GetByName("iGauge") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 25, out.LastValue.AsInt64()) assert.Equal(t, aggregation.LastValueKind, out.AggregationKind) }) From ff1cac2f1a67a21efcb8ba42c2bd46c0b597f788 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Fri, 22 Apr 2022 22:43:32 +0000 Subject: [PATCH 10/11] fix the Label changing --- sdk/metric/metrictest/exporter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/metric/metrictest/exporter.go b/sdk/metric/metrictest/exporter.go index 486dba28b9e..e57c8664c7d 100644 --- a/sdk/metric/metrictest/exporter.go +++ b/sdk/metric/metrictest/exporter.go @@ -96,7 +96,7 @@ func (e *Exporter) Collect(ctx context.Context) error { record := ExportRecord{ InstrumentName: rec.Descriptor().Name(), InstrumentationLibrary: lib, - Attributes: rec.Labels().ToSlice(), + Attributes: rec.Attributes().ToSlice(), AggregationKind: rec.Aggregation().Kind(), NumberKind: rec.Descriptor().NumberKind(), } From fea1be2f10b909b5144447d6ad6b800152243ed0 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sun, 24 Apr 2022 07:44:26 -0700 Subject: [PATCH 11/11] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4766fea5d9..16f3f0d0f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +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) +- Added an in-memory exporter to metrictest to aid testing with a full SDK. (#2776) ### Fixed