// 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 opencensus // import "go.opentelemetry.io/otel/bridge/opencensus" import ( "context" "fmt" "testing" "time" "github.com/stretchr/testify/require" ocmetricdata "go.opencensus.io/metric/metricdata" "go.opencensus.io/metric/metricproducer" ocresource "go.opencensus.io/resource" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/resource" ) func TestMetricProducer(t *testing.T) { now := time.Now() for _, tc := range []struct { desc string input []*ocmetricdata.Metric expected []metricdata.ScopeMetrics expectErr bool }{ { desc: "empty", expected: nil, }, { desc: "success", input: []*ocmetricdata.Metric{ { Resource: &ocresource.Resource{ Labels: map[string]string{ "R1": "V1", "R2": "V2", }, }, TimeSeries: []*ocmetricdata.TimeSeries{ { StartTime: now, Points: []ocmetricdata.Point{ {Value: int64(123), Time: now}, }, }, }, }, }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ { Attributes: attribute.NewSet(), StartTime: now, Time: now, Value: 123, }, }, }, }, }, }}, }, { desc: "partial success", input: []*ocmetricdata.Metric{ { Descriptor: ocmetricdata.Descriptor{ Name: "foo.com/bad-point", Description: "a bad type", Unit: ocmetricdata.UnitDimensionless, Type: ocmetricdata.TypeGaugeDistribution, }, }, { Resource: &ocresource.Resource{ Labels: map[string]string{ "R1": "V1", "R2": "V2", }, }, TimeSeries: []*ocmetricdata.TimeSeries{ { StartTime: now, Points: []ocmetricdata.Point{ {Value: int64(123), Time: now}, }, }, }, }, }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ { Attributes: attribute.NewSet(), StartTime: now, Time: now, Value: 123, }, }, }, }, }, }}, expectErr: true, }, } { t.Run(tc.desc, func(t *testing.T) { fakeProducer := &fakeOCProducer{metrics: tc.input} metricproducer.GlobalManager().AddProducer(fakeProducer) defer metricproducer.GlobalManager().DeleteProducer(fakeProducer) output, err := NewMetricProducer().Produce(context.Background()) if tc.expectErr { require.Error(t, err) } else { require.Nil(t, err) } require.Equal(t, len(output), len(tc.expected)) for i := range output { metricdatatest.AssertEqual(t, tc.expected[i], output[i]) } }) } } type fakeOCProducer struct { metrics []*ocmetricdata.Metric } func (f *fakeOCProducer) Read() []*ocmetricdata.Metric { return f.metrics } func TestPushMetricsExporter(t *testing.T) { now := time.Now() for _, tc := range []struct { desc string input []*ocmetricdata.Metric inputResource *resource.Resource exportErr error expected *metricdata.ResourceMetrics expectErr bool }{ { desc: "empty batch isn't sent", }, { desc: "export error", exportErr: fmt.Errorf("failed to export"), input: []*ocmetricdata.Metric{ { Resource: &ocresource.Resource{ Labels: map[string]string{ "R1": "V1", "R2": "V2", }, }, TimeSeries: []*ocmetricdata.TimeSeries{ { StartTime: now, Points: []ocmetricdata.Point{ {Value: int64(123), Time: now}, }, }, }, }, }, expectErr: true, }, { desc: "success", input: []*ocmetricdata.Metric{ { Resource: &ocresource.Resource{ Labels: map[string]string{ "R1": "V1", "R2": "V2", }, }, TimeSeries: []*ocmetricdata.TimeSeries{ { StartTime: now, Points: []ocmetricdata.Point{ {Value: int64(123), Time: now}, }, }, }, }, }, inputResource: resource.NewSchemaless( attribute.String("R1", "V1"), attribute.String("R2", "V2"), ), expected: &metricdata.ResourceMetrics{ Resource: resource.NewSchemaless( attribute.String("R1", "V1"), attribute.String("R2", "V2"), ), ScopeMetrics: []metricdata.ScopeMetrics{ { Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "", Description: "", Unit: "", Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ { Attributes: attribute.NewSet(), StartTime: now, Time: now, Value: 123, }, }, }, }, }, }, }, }, }, } { t.Run(tc.desc, func(t *testing.T) { fake := &fakeExporter{err: tc.exportErr} exporter := NewMetricExporter(fake, tc.inputResource) err := exporter.ExportMetrics(context.Background(), tc.input) if tc.expectErr { require.Error(t, err) } else { require.NoError(t, err) } if tc.expected != nil { require.NotNil(t, fake.data) metricdatatest.AssertEqual(t, *tc.expected, *fake.data) } else { require.Nil(t, fake.data) } }) } } type fakeExporter struct { metric.Exporter data *metricdata.ResourceMetrics err error } func (f *fakeExporter) Export(ctx context.Context, data metricdata.ResourceMetrics) error { if f.err == nil { f.data = &data } return f.err }