1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package global_test 16 17import ( 18 "context" 19 "errors" 20 "testing" 21 22 "github.com/stretchr/testify/require" 23 24 "go.opentelemetry.io/otel/attribute" 25 "go.opentelemetry.io/otel/internal/global" 26 "go.opentelemetry.io/otel/metric" 27 metricglobal "go.opentelemetry.io/otel/metric/global" 28 "go.opentelemetry.io/otel/metric/number" 29 "go.opentelemetry.io/otel/oteltest" 30) 31 32var Must = metric.Must 33 34var asInt = number.NewInt64Number 35var asFloat = number.NewFloat64Number 36 37func TestDirect(t *testing.T) { 38 global.ResetForTest() 39 40 ctx := context.Background() 41 meter1 := metricglobal.Meter("test1", metric.WithInstrumentationVersion("semver:v1.0.0")) 42 meter2 := metricglobal.Meter("test2") 43 labels1 := []attribute.KeyValue{attribute.String("A", "B")} 44 labels2 := []attribute.KeyValue{attribute.String("C", "D")} 45 labels3 := []attribute.KeyValue{attribute.String("E", "F")} 46 47 counter := Must(meter1).NewInt64Counter("test.counter") 48 counter.Add(ctx, 1, labels1...) 49 counter.Add(ctx, 1, labels1...) 50 51 valuerecorder := Must(meter1).NewFloat64ValueRecorder("test.valuerecorder") 52 valuerecorder.Record(ctx, 1, labels1...) 53 valuerecorder.Record(ctx, 2, labels1...) 54 55 _ = Must(meter1).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) { 56 result.Observe(1., labels1...) 57 result.Observe(2., labels2...) 58 }) 59 60 _ = Must(meter1).NewInt64ValueObserver("test.valueobserver.int", func(_ context.Context, result metric.Int64ObserverResult) { 61 result.Observe(1, labels1...) 62 result.Observe(2, labels2...) 63 }) 64 65 second := Must(meter2).NewFloat64ValueRecorder("test.second") 66 second.Record(ctx, 1, labels3...) 67 second.Record(ctx, 2, labels3...) 68 69 mock, provider := oteltest.NewMeterProvider() 70 metricglobal.SetMeterProvider(provider) 71 72 counter.Add(ctx, 1, labels1...) 73 valuerecorder.Record(ctx, 3, labels1...) 74 second.Record(ctx, 3, labels3...) 75 76 mock.RunAsyncInstruments() 77 78 measurements := oteltest.AsStructs(mock.MeasurementBatches) 79 80 require.EqualValues(t, 81 []oteltest.Measured{ 82 { 83 Name: "test.counter", 84 InstrumentationName: "test1", 85 InstrumentationVersion: "semver:v1.0.0", 86 Labels: oteltest.LabelsToMap(labels1...), 87 Number: asInt(1), 88 }, 89 { 90 Name: "test.valuerecorder", 91 InstrumentationName: "test1", 92 InstrumentationVersion: "semver:v1.0.0", 93 Labels: oteltest.LabelsToMap(labels1...), 94 Number: asFloat(3), 95 }, 96 { 97 Name: "test.second", 98 InstrumentationName: "test2", 99 Labels: oteltest.LabelsToMap(labels3...), 100 Number: asFloat(3), 101 }, 102 { 103 Name: "test.valueobserver.float", 104 InstrumentationName: "test1", 105 InstrumentationVersion: "semver:v1.0.0", 106 Labels: oteltest.LabelsToMap(labels1...), 107 Number: asFloat(1), 108 }, 109 { 110 Name: "test.valueobserver.float", 111 InstrumentationName: "test1", 112 InstrumentationVersion: "semver:v1.0.0", 113 Labels: oteltest.LabelsToMap(labels2...), 114 Number: asFloat(2), 115 }, 116 { 117 Name: "test.valueobserver.int", 118 InstrumentationName: "test1", 119 InstrumentationVersion: "semver:v1.0.0", 120 Labels: oteltest.LabelsToMap(labels1...), 121 Number: asInt(1), 122 }, 123 { 124 Name: "test.valueobserver.int", 125 InstrumentationName: "test1", 126 InstrumentationVersion: "semver:v1.0.0", 127 Labels: oteltest.LabelsToMap(labels2...), 128 Number: asInt(2), 129 }, 130 }, 131 measurements, 132 ) 133} 134 135func TestBound(t *testing.T) { 136 global.ResetForTest() 137 138 // Note: this test uses opposite Float64/Int64 number kinds 139 // vs. the above, to cover all the instruments. 140 ctx := context.Background() 141 glob := metricglobal.Meter("test") 142 labels1 := []attribute.KeyValue{attribute.String("A", "B")} 143 144 counter := Must(glob).NewFloat64Counter("test.counter") 145 boundC := counter.Bind(labels1...) 146 boundC.Add(ctx, 1) 147 boundC.Add(ctx, 1) 148 149 valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") 150 boundM := valuerecorder.Bind(labels1...) 151 boundM.Record(ctx, 1) 152 boundM.Record(ctx, 2) 153 154 mock, provider := oteltest.NewMeterProvider() 155 metricglobal.SetMeterProvider(provider) 156 157 boundC.Add(ctx, 1) 158 boundM.Record(ctx, 3) 159 160 require.EqualValues(t, 161 []oteltest.Measured{ 162 { 163 Name: "test.counter", 164 InstrumentationName: "test", 165 Labels: oteltest.LabelsToMap(labels1...), 166 Number: asFloat(1), 167 }, 168 { 169 Name: "test.valuerecorder", 170 InstrumentationName: "test", 171 Labels: oteltest.LabelsToMap(labels1...), 172 Number: asInt(3), 173 }, 174 }, 175 oteltest.AsStructs(mock.MeasurementBatches)) 176 177 boundC.Unbind() 178 boundM.Unbind() 179} 180 181func TestUnbind(t *testing.T) { 182 // Tests Unbind with SDK never installed. 183 global.ResetForTest() 184 185 glob := metricglobal.Meter("test") 186 labels1 := []attribute.KeyValue{attribute.String("A", "B")} 187 188 counter := Must(glob).NewFloat64Counter("test.counter") 189 boundC := counter.Bind(labels1...) 190 191 valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") 192 boundM := valuerecorder.Bind(labels1...) 193 194 boundC.Unbind() 195 boundM.Unbind() 196} 197 198func TestUnbindThenRecordOne(t *testing.T) { 199 global.ResetForTest() 200 201 ctx := context.Background() 202 mock, provider := oteltest.NewMeterProvider() 203 204 meter := metricglobal.Meter("test") 205 counter := Must(meter).NewInt64Counter("test.counter") 206 boundC := counter.Bind() 207 metricglobal.SetMeterProvider(provider) 208 boundC.Unbind() 209 210 require.NotPanics(t, func() { 211 boundC.Add(ctx, 1) 212 }) 213 require.Equal(t, 0, len(mock.MeasurementBatches)) 214} 215 216type meterProviderWithConstructorError struct { 217 metric.MeterProvider 218} 219 220type meterWithConstructorError struct { 221 metric.MeterImpl 222} 223 224func (m *meterProviderWithConstructorError) Meter(iName string, opts ...metric.MeterOption) metric.Meter { 225 return metric.WrapMeterImpl(&meterWithConstructorError{m.MeterProvider.Meter(iName, opts...).MeterImpl()}, iName, opts...) 226} 227 228func (m *meterWithConstructorError) NewSyncInstrument(_ metric.Descriptor) (metric.SyncImpl, error) { 229 return metric.NoopSync{}, errors.New("constructor error") 230} 231 232func TestErrorInDeferredConstructor(t *testing.T) { 233 global.ResetForTest() 234 235 ctx := context.Background() 236 meter := metricglobal.GetMeterProvider().Meter("builtin") 237 238 c1 := Must(meter).NewInt64Counter("test") 239 c2 := Must(meter).NewInt64Counter("test") 240 241 _, provider := oteltest.NewMeterProvider() 242 sdk := &meterProviderWithConstructorError{provider} 243 244 require.Panics(t, func() { 245 metricglobal.SetMeterProvider(sdk) 246 }) 247 248 c1.Add(ctx, 1) 249 c2.Add(ctx, 2) 250} 251 252func TestImplementationIndirection(t *testing.T) { 253 global.ResetForTest() 254 255 // Test that Implementation() does the proper indirection, i.e., 256 // returns the implementation interface not the global, after 257 // registered. 258 259 meter1 := metricglobal.Meter("test1") 260 261 // Sync: no SDK yet 262 counter := Must(meter1).NewInt64Counter("interface.counter") 263 264 ival := counter.Measurement(1).SyncImpl().Implementation() 265 require.NotNil(t, ival) 266 267 _, ok := ival.(*oteltest.Sync) 268 require.False(t, ok) 269 270 // Async: no SDK yet 271 valueobserver := Must(meter1).NewFloat64ValueObserver( 272 "interface.valueobserver", 273 func(_ context.Context, result metric.Float64ObserverResult) {}, 274 ) 275 276 ival = valueobserver.AsyncImpl().Implementation() 277 require.NotNil(t, ival) 278 279 _, ok = ival.(*oteltest.Async) 280 require.False(t, ok) 281 282 // Register the SDK 283 _, provider := oteltest.NewMeterProvider() 284 metricglobal.SetMeterProvider(provider) 285 286 // Repeat the above tests 287 288 // Sync 289 ival = counter.Measurement(1).SyncImpl().Implementation() 290 require.NotNil(t, ival) 291 292 _, ok = ival.(*oteltest.Sync) 293 require.True(t, ok) 294 295 // Async 296 ival = valueobserver.AsyncImpl().Implementation() 297 require.NotNil(t, ival) 298 299 _, ok = ival.(*oteltest.Async) 300 require.True(t, ok) 301} 302 303func TestRecordBatchMock(t *testing.T) { 304 global.ResetForTest() 305 306 meter := metricglobal.GetMeterProvider().Meter("builtin") 307 308 counter := Must(meter).NewInt64Counter("test.counter") 309 310 meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) 311 312 mock, provider := oteltest.NewMeterProvider() 313 metricglobal.SetMeterProvider(provider) 314 315 meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) 316 317 require.EqualValues(t, 318 []oteltest.Measured{ 319 { 320 Name: "test.counter", 321 InstrumentationName: "builtin", 322 Labels: oteltest.LabelsToMap(), 323 Number: asInt(1), 324 }, 325 }, 326 oteltest.AsStructs(mock.MeasurementBatches)) 327} 328