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 internal_test 16 17import ( 18 "context" 19 "errors" 20 "testing" 21 22 "github.com/stretchr/testify/require" 23 24 "go.opentelemetry.io/otel/api/global" 25 "go.opentelemetry.io/otel/api/global/internal" 26 "go.opentelemetry.io/otel/api/metric" 27 metrictest "go.opentelemetry.io/otel/internal/metric" 28 "go.opentelemetry.io/otel/label" 29) 30 31var Must = metric.Must 32 33// Note: Maybe this should be factored into ../../../internal/metric? 34type measured struct { 35 Name string 36 InstrumentationName string 37 InstrumentationVersion string 38 Labels map[label.Key]label.Value 39 Number metric.Number 40} 41 42func asStructs(batches []metrictest.Batch) []measured { 43 var r []measured 44 for _, batch := range batches { 45 for _, m := range batch.Measurements { 46 r = append(r, measured{ 47 Name: m.Instrument.Descriptor().Name(), 48 InstrumentationName: m.Instrument.Descriptor().InstrumentationName(), 49 InstrumentationVersion: m.Instrument.Descriptor().InstrumentationVersion(), 50 Labels: asMap(batch.Labels...), 51 Number: m.Number, 52 }) 53 } 54 } 55 return r 56} 57 58func asMap(kvs ...label.KeyValue) map[label.Key]label.Value { 59 m := map[label.Key]label.Value{} 60 for _, label := range kvs { 61 m[label.Key] = label.Value 62 } 63 return m 64} 65 66var asInt = metric.NewInt64Number 67var asFloat = metric.NewFloat64Number 68 69func TestDirect(t *testing.T) { 70 internal.ResetForTest() 71 72 ctx := context.Background() 73 meter1 := global.Meter("test1", metric.WithInstrumentationVersion("semver:v1.0.0")) 74 meter2 := global.Meter("test2") 75 labels1 := []label.KeyValue{label.String("A", "B")} 76 labels2 := []label.KeyValue{label.String("C", "D")} 77 labels3 := []label.KeyValue{label.String("E", "F")} 78 79 counter := Must(meter1).NewInt64Counter("test.counter") 80 counter.Add(ctx, 1, labels1...) 81 counter.Add(ctx, 1, labels1...) 82 83 valuerecorder := Must(meter1).NewFloat64ValueRecorder("test.valuerecorder") 84 valuerecorder.Record(ctx, 1, labels1...) 85 valuerecorder.Record(ctx, 2, labels1...) 86 87 _ = Must(meter1).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) { 88 result.Observe(1., labels1...) 89 result.Observe(2., labels2...) 90 }) 91 92 _ = Must(meter1).NewInt64ValueObserver("test.valueobserver.int", func(_ context.Context, result metric.Int64ObserverResult) { 93 result.Observe(1, labels1...) 94 result.Observe(2, labels2...) 95 }) 96 97 second := Must(meter2).NewFloat64ValueRecorder("test.second") 98 second.Record(ctx, 1, labels3...) 99 second.Record(ctx, 2, labels3...) 100 101 mock, provider := metrictest.NewProvider() 102 global.SetMeterProvider(provider) 103 104 counter.Add(ctx, 1, labels1...) 105 valuerecorder.Record(ctx, 3, labels1...) 106 second.Record(ctx, 3, labels3...) 107 108 mock.RunAsyncInstruments() 109 110 measurements := asStructs(mock.MeasurementBatches) 111 112 require.EqualValues(t, 113 []measured{ 114 { 115 Name: "test.counter", 116 InstrumentationName: "test1", 117 InstrumentationVersion: "semver:v1.0.0", 118 Labels: asMap(labels1...), 119 Number: asInt(1), 120 }, 121 { 122 Name: "test.valuerecorder", 123 InstrumentationName: "test1", 124 InstrumentationVersion: "semver:v1.0.0", 125 Labels: asMap(labels1...), 126 Number: asFloat(3), 127 }, 128 { 129 Name: "test.second", 130 InstrumentationName: "test2", 131 Labels: asMap(labels3...), 132 Number: asFloat(3), 133 }, 134 { 135 Name: "test.valueobserver.float", 136 InstrumentationName: "test1", 137 InstrumentationVersion: "semver:v1.0.0", 138 Labels: asMap(labels1...), 139 Number: asFloat(1), 140 }, 141 { 142 Name: "test.valueobserver.float", 143 InstrumentationName: "test1", 144 InstrumentationVersion: "semver:v1.0.0", 145 Labels: asMap(labels2...), 146 Number: asFloat(2), 147 }, 148 { 149 Name: "test.valueobserver.int", 150 InstrumentationName: "test1", 151 InstrumentationVersion: "semver:v1.0.0", 152 Labels: asMap(labels1...), 153 Number: asInt(1), 154 }, 155 { 156 Name: "test.valueobserver.int", 157 InstrumentationName: "test1", 158 InstrumentationVersion: "semver:v1.0.0", 159 Labels: asMap(labels2...), 160 Number: asInt(2), 161 }, 162 }, 163 measurements, 164 ) 165} 166 167func TestBound(t *testing.T) { 168 internal.ResetForTest() 169 170 // Note: this test uses opposite Float64/Int64 number kinds 171 // vs. the above, to cover all the instruments. 172 ctx := context.Background() 173 glob := global.Meter("test") 174 labels1 := []label.KeyValue{label.String("A", "B")} 175 176 counter := Must(glob).NewFloat64Counter("test.counter") 177 boundC := counter.Bind(labels1...) 178 boundC.Add(ctx, 1) 179 boundC.Add(ctx, 1) 180 181 valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") 182 boundM := valuerecorder.Bind(labels1...) 183 boundM.Record(ctx, 1) 184 boundM.Record(ctx, 2) 185 186 mock, provider := metrictest.NewProvider() 187 global.SetMeterProvider(provider) 188 189 boundC.Add(ctx, 1) 190 boundM.Record(ctx, 3) 191 192 require.EqualValues(t, 193 []measured{ 194 { 195 Name: "test.counter", 196 InstrumentationName: "test", 197 Labels: asMap(labels1...), 198 Number: asFloat(1), 199 }, 200 { 201 Name: "test.valuerecorder", 202 InstrumentationName: "test", 203 Labels: asMap(labels1...), 204 Number: asInt(3), 205 }, 206 }, 207 asStructs(mock.MeasurementBatches)) 208 209 boundC.Unbind() 210 boundM.Unbind() 211} 212 213func TestUnbind(t *testing.T) { 214 // Tests Unbind with SDK never installed. 215 internal.ResetForTest() 216 217 glob := global.Meter("test") 218 labels1 := []label.KeyValue{label.String("A", "B")} 219 220 counter := Must(glob).NewFloat64Counter("test.counter") 221 boundC := counter.Bind(labels1...) 222 223 valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder") 224 boundM := valuerecorder.Bind(labels1...) 225 226 boundC.Unbind() 227 boundM.Unbind() 228} 229 230func TestUnbindThenRecordOne(t *testing.T) { 231 internal.ResetForTest() 232 233 ctx := context.Background() 234 mock, provider := metrictest.NewProvider() 235 236 meter := global.Meter("test") 237 counter := Must(meter).NewInt64Counter("test.counter") 238 boundC := counter.Bind() 239 global.SetMeterProvider(provider) 240 boundC.Unbind() 241 242 require.NotPanics(t, func() { 243 boundC.Add(ctx, 1) 244 }) 245 require.Equal(t, 0, len(mock.MeasurementBatches)) 246} 247 248type meterProviderWithConstructorError struct { 249 metric.Provider 250} 251 252type meterWithConstructorError struct { 253 metric.MeterImpl 254} 255 256func (m *meterProviderWithConstructorError) Meter(iName string, opts ...metric.MeterOption) metric.Meter { 257 return metric.WrapMeterImpl(&meterWithConstructorError{m.Provider.Meter(iName, opts...).MeterImpl()}, iName, opts...) 258} 259 260func (m *meterWithConstructorError) NewSyncInstrument(_ metric.Descriptor) (metric.SyncImpl, error) { 261 return metric.NoopSync{}, errors.New("constructor error") 262} 263 264func TestErrorInDeferredConstructor(t *testing.T) { 265 internal.ResetForTest() 266 267 ctx := context.Background() 268 meter := global.MeterProvider().Meter("builtin") 269 270 c1 := Must(meter).NewInt64Counter("test") 271 c2 := Must(meter).NewInt64Counter("test") 272 273 _, provider := metrictest.NewProvider() 274 sdk := &meterProviderWithConstructorError{provider} 275 276 require.Panics(t, func() { 277 global.SetMeterProvider(sdk) 278 }) 279 280 c1.Add(ctx, 1) 281 c2.Add(ctx, 2) 282} 283 284func TestImplementationIndirection(t *testing.T) { 285 internal.ResetForTest() 286 287 // Test that Implementation() does the proper indirection, i.e., 288 // returns the implementation interface not the global, after 289 // registered. 290 291 meter1 := global.Meter("test1") 292 293 // Sync: no SDK yet 294 counter := Must(meter1).NewInt64Counter("interface.counter") 295 296 ival := counter.Measurement(1).SyncImpl().Implementation() 297 require.NotNil(t, ival) 298 299 _, ok := ival.(*metrictest.Sync) 300 require.False(t, ok) 301 302 // Async: no SDK yet 303 valueobserver := Must(meter1).NewFloat64ValueObserver( 304 "interface.valueobserver", 305 func(_ context.Context, result metric.Float64ObserverResult) {}, 306 ) 307 308 ival = valueobserver.AsyncImpl().Implementation() 309 require.NotNil(t, ival) 310 311 _, ok = ival.(*metrictest.Async) 312 require.False(t, ok) 313 314 // Register the SDK 315 _, provider := metrictest.NewProvider() 316 global.SetMeterProvider(provider) 317 318 // Repeat the above tests 319 320 // Sync 321 ival = counter.Measurement(1).SyncImpl().Implementation() 322 require.NotNil(t, ival) 323 324 _, ok = ival.(*metrictest.Sync) 325 require.True(t, ok) 326 327 // Async 328 ival = valueobserver.AsyncImpl().Implementation() 329 require.NotNil(t, ival) 330 331 _, ok = ival.(*metrictest.Async) 332 require.True(t, ok) 333} 334 335func TestRecordBatchMock(t *testing.T) { 336 internal.ResetForTest() 337 338 meter := global.MeterProvider().Meter("builtin") 339 340 counter := Must(meter).NewInt64Counter("test.counter") 341 342 meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) 343 344 mock, provider := metrictest.NewProvider() 345 global.SetMeterProvider(provider) 346 347 meter.RecordBatch(context.Background(), nil, counter.Measurement(1)) 348 349 require.EqualValues(t, 350 []measured{ 351 { 352 Name: "test.counter", 353 InstrumentationName: "builtin", 354 Labels: asMap(), 355 Number: asInt(1), 356 }, 357 }, 358 asStructs(mock.MeasurementBatches)) 359} 360