// 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 internal_test import ( "context" "errors" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global/internal" "go.opentelemetry.io/otel/api/kv" "go.opentelemetry.io/otel/api/metric" metrictest "go.opentelemetry.io/otel/internal/metric" ) var Must = metric.Must // Note: Maybe this should be factored into ../../../internal/metric? type measured struct { Name string InstrumentationName string InstrumentationVersion string Labels map[kv.Key]kv.Value Number metric.Number } func asStructs(batches []metrictest.Batch) []measured { var r []measured for _, batch := range batches { for _, m := range batch.Measurements { r = append(r, measured{ Name: m.Instrument.Descriptor().Name(), InstrumentationName: m.Instrument.Descriptor().InstrumentationName(), InstrumentationVersion: m.Instrument.Descriptor().InstrumentationVersion(), Labels: asMap(batch.Labels...), Number: m.Number, }) } } return r } func asMap(kvs ...kv.KeyValue) map[kv.Key]kv.Value { m := map[kv.Key]kv.Value{} for _, kv := range kvs { m[kv.Key] = kv.Value } return m } var asInt = metric.NewInt64Number var asFloat = metric.NewFloat64Number func TestDirect(t *testing.T) { internal.ResetForTest() ctx := context.Background() meter1 := global.Meter("test1", metric.WithInstrumentationVersion("semver:v1.0.0")) meter2 := global.Meter("test2") labels1 := []kv.KeyValue{kv.String("A", "B")} labels2 := []kv.KeyValue{kv.String("C", "D")} labels3 := []kv.KeyValue{kv.String("E", "F")} counter := Must(meter1).NewInt64Counter("test.counter") counter.Add(ctx, 1, labels1...) counter.Add(ctx, 1, labels1...) valuerecorder := Must(meter1).NewFloat64ValueRecorder("test.valuerecorder") valuerecorder.Record(ctx, 1, labels1...) valuerecorder.Record(ctx, 2, labels1...) _ = Must(meter1).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) { result.Observe(1., labels1...) result.Observe(2., labels2...) }) _ = Must(meter1).NewInt64ValueObserver("test.valueobserver.int", func(_ context.Context, result metric.Int64ObserverResult) { result.Observe(1, labels1...) result.Observe(2, labels2...) }) second := Must(meter2).NewFloat64ValueRecorder("test.second") second.Record(ctx, 1, labels3...) second.Record(ctx, 2, labels3...) mock, provider := metrictest.NewProvider() global.SetMeterProvider(provider) counter.Add(ctx, 1, labels1...) valuerecorder.Record(ctx, 3, labels1...) second.Record(ctx, 3, labels3...) mock.RunAsyncInstruments() measurements := asStructs(mock.MeasurementBatches) require.EqualValues(t, []measured{ { Name: "test.counter", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels1...), Number: asInt(1), }, { Name: "test.valuerecorder", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels1...), Number: asFloat(3), }, { Name: "test.second", InstrumentationName: "test2", Labels: asMap(labels3...), Number: asFloat(3), }, { Name: "test.valueobserver.float", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels1...), Number: asFloat(1), }, { Name: "test.valueobserver.float", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels2...), Number: asFloat(2), }, { Name: "test.valueobserver.int", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels1...), Number: asInt(1), }, { Name: "test.valueobserver.int", InstrumentationName: "test1", InstrumentationVersion: "semver:v1.0.0", Labels: asMap(labels2...), Number: asInt(2), }, }, measurements, ) } func TestBound(t *testing.T) { internal.ResetForTest() // Note: this test uses opposite Float64/Int64 number kinds // vs. the above, to cover all the instruments. ctx := context.Background() glob := global.Meter("test") labels1 := []kv.KeyValue{kv.String("A", "B")} counter := Must(glob).NewFloat64Counter("test.counter") boundC := counter.Bind(labels1...) boundC.Add(ctx, 1) boundC.Add(ctx, 1) valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") boundM := valuerecorder.Bind(labels1...) boundM.Record(ctx, 1) boundM.Record(ctx, 2) mock, provider := metrictest.NewProvider() global.SetMeterProvider(provider) boundC.Add(ctx, 1) boundM.Record(ctx, 3) require.EqualValues(t, []measured{ { Name: "test.counter", InstrumentationName: "test", Labels: asMap(labels1...), Number: asFloat(1), }, { Name: "test.valuerecorder", InstrumentationName: "test", Labels: asMap(labels1...), Number: asInt(3), }, }, asStructs(mock.MeasurementBatches)) boundC.Unbind() boundM.Unbind() } func TestUnbind(t *testing.T) { // Tests Unbind with SDK never installed. internal.ResetForTest() glob := global.Meter("test") labels1 := []kv.KeyValue{kv.String("A", "B")} counter := Must(glob).NewFloat64Counter("test.counter") boundC := counter.Bind(labels1...) valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") boundM := valuerecorder.Bind(labels1...) boundC.Unbind() boundM.Unbind() } func TestUnbindThenRecordOne(t *testing.T) { internal.ResetForTest() ctx := context.Background() mock, provider := metrictest.NewProvider() meter := global.Meter("test") counter := Must(meter).NewInt64Counter("test.counter") boundC := counter.Bind() global.SetMeterProvider(provider) boundC.Unbind() require.NotPanics(t, func() { boundC.Add(ctx, 1) }) require.Equal(t, 0, len(mock.MeasurementBatches)) } type meterProviderWithConstructorError struct { metric.Provider } type meterWithConstructorError struct { metric.MeterImpl } func (m *meterProviderWithConstructorError) Meter(iName string, opts ...metric.MeterOption) metric.Meter { return metric.WrapMeterImpl(&meterWithConstructorError{m.Provider.Meter(iName, opts...).MeterImpl()}, iName, opts...) } func (m *meterWithConstructorError) NewSyncInstrument(_ metric.Descriptor) (metric.SyncImpl, error) { return metric.NoopSync{}, errors.New("constructor error") } func TestErrorInDeferredConstructor(t *testing.T) { internal.ResetForTest() ctx := context.Background() meter := global.MeterProvider().Meter("builtin") c1 := Must(meter).NewInt64Counter("test") c2 := Must(meter).NewInt64Counter("test") _, provider := metrictest.NewProvider() sdk := &meterProviderWithConstructorError{provider} require.Panics(t, func() { global.SetMeterProvider(sdk) }) c1.Add(ctx, 1) c2.Add(ctx, 2) } func TestImplementationIndirection(t *testing.T) { internal.ResetForTest() // Test that Implementation() does the proper indirection, i.e., // returns the implementation interface not the global, after // registered. meter1 := global.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 valueobserver := Must(meter1).NewFloat64ValueObserver( "interface.valueobserver", func(_ context.Context, result metric.Float64ObserverResult) {}, ) ival = valueobserver.AsyncImpl().Implementation() require.NotNil(t, ival) _, ok = ival.(*metrictest.Async) require.False(t, ok) // Register the SDK _, provider := metrictest.NewProvider() global.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 = valueobserver.AsyncImpl().Implementation() require.NotNil(t, ival) _, ok = ival.(*metrictest.Async) require.True(t, ok) } func TestRecordBatchMock(t *testing.T) { internal.ResetForTest() meter := global.MeterProvider().Meter("builtin") counter := Must(meter).NewInt64Counter("test.counter") meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) mock, provider := metrictest.NewProvider() global.SetMeterProvider(provider) meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) require.EqualValues(t, []measured{ { Name: "test.counter", InstrumentationName: "builtin", Labels: asMap(), Number: asInt(1), }, }, asStructs(mock.MeasurementBatches)) }