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 oteltest // import "go.opentelemetry.io/otel/oteltest" 16 17import ( 18 "context" 19 "sync" 20 "testing" 21 22 internalmetric "go.opentelemetry.io/otel/internal/metric" 23 "go.opentelemetry.io/otel/label" 24 "go.opentelemetry.io/otel/metric" 25 "go.opentelemetry.io/otel/metric/number" 26 "go.opentelemetry.io/otel/metric/registry" 27) 28 29type ( 30 Handle struct { 31 Instrument *Sync 32 Labels []label.KeyValue 33 } 34 35 Batch struct { 36 // Measurement needs to be aligned for 64-bit atomic operations. 37 Measurements []Measurement 38 Ctx context.Context 39 Labels []label.KeyValue 40 LibraryName string 41 } 42 43 // MeterImpl is an OpenTelemetry Meter implementation used for testing. 44 MeterImpl struct { 45 lock sync.Mutex 46 47 MeasurementBatches []Batch 48 49 asyncInstruments *internalmetric.AsyncInstrumentState 50 } 51 52 Measurement struct { 53 // Number needs to be aligned for 64-bit atomic operations. 54 Number number.Number 55 Instrument metric.InstrumentImpl 56 } 57 58 Instrument struct { 59 meter *MeterImpl 60 descriptor metric.Descriptor 61 } 62 63 Async struct { 64 Instrument 65 66 runner metric.AsyncRunner 67 } 68 69 Sync struct { 70 Instrument 71 } 72) 73 74var ( 75 _ metric.SyncImpl = &Sync{} 76 _ metric.BoundSyncImpl = &Handle{} 77 _ metric.MeterImpl = &MeterImpl{} 78 _ metric.AsyncImpl = &Async{} 79) 80 81func (i Instrument) Descriptor() metric.Descriptor { 82 return i.descriptor 83} 84 85func (a *Async) Implementation() interface{} { 86 return a 87} 88 89func (s *Sync) Implementation() interface{} { 90 return s 91} 92 93func (s *Sync) Bind(labels []label.KeyValue) metric.BoundSyncImpl { 94 return &Handle{ 95 Instrument: s, 96 Labels: labels, 97 } 98} 99 100func (s *Sync) RecordOne(ctx context.Context, number number.Number, labels []label.KeyValue) { 101 s.meter.doRecordSingle(ctx, labels, s, number) 102} 103 104func (h *Handle) RecordOne(ctx context.Context, number number.Number) { 105 h.Instrument.meter.doRecordSingle(ctx, h.Labels, h.Instrument, number) 106} 107 108func (h *Handle) Unbind() { 109} 110 111func (m *MeterImpl) doRecordSingle(ctx context.Context, labels []label.KeyValue, instrument metric.InstrumentImpl, number number.Number) { 112 m.collect(ctx, labels, []Measurement{{ 113 Instrument: instrument, 114 Number: number, 115 }}) 116} 117 118func NewMeterProvider() (*MeterImpl, metric.MeterProvider) { 119 impl := &MeterImpl{ 120 asyncInstruments: internalmetric.NewAsyncInstrumentState(), 121 } 122 return impl, registry.NewMeterProvider(impl) 123} 124 125func NewMeter() (*MeterImpl, metric.Meter) { 126 impl, p := NewMeterProvider() 127 return impl, p.Meter("mock") 128} 129 130func (m *MeterImpl) NewSyncInstrument(descriptor metric.Descriptor) (metric.SyncImpl, error) { 131 m.lock.Lock() 132 defer m.lock.Unlock() 133 134 return &Sync{ 135 Instrument{ 136 descriptor: descriptor, 137 meter: m, 138 }, 139 }, nil 140} 141 142func (m *MeterImpl) NewAsyncInstrument(descriptor metric.Descriptor, runner metric.AsyncRunner) (metric.AsyncImpl, error) { 143 m.lock.Lock() 144 defer m.lock.Unlock() 145 146 a := &Async{ 147 Instrument: Instrument{ 148 descriptor: descriptor, 149 meter: m, 150 }, 151 runner: runner, 152 } 153 m.asyncInstruments.Register(a, runner) 154 return a, nil 155} 156 157func (m *MeterImpl) RecordBatch(ctx context.Context, labels []label.KeyValue, measurements ...metric.Measurement) { 158 mm := make([]Measurement, len(measurements)) 159 for i := 0; i < len(measurements); i++ { 160 m := measurements[i] 161 mm[i] = Measurement{ 162 Instrument: m.SyncImpl().Implementation().(*Sync), 163 Number: m.Number(), 164 } 165 } 166 m.collect(ctx, labels, mm) 167} 168 169func (m *MeterImpl) CollectAsync(labels []label.KeyValue, obs ...metric.Observation) { 170 mm := make([]Measurement, len(obs)) 171 for i := 0; i < len(obs); i++ { 172 o := obs[i] 173 mm[i] = Measurement{ 174 Instrument: o.AsyncImpl(), 175 Number: o.Number(), 176 } 177 } 178 m.collect(context.Background(), labels, mm) 179} 180 181func (m *MeterImpl) collect(ctx context.Context, labels []label.KeyValue, measurements []Measurement) { 182 m.lock.Lock() 183 defer m.lock.Unlock() 184 185 m.MeasurementBatches = append(m.MeasurementBatches, Batch{ 186 Ctx: ctx, 187 Labels: labels, 188 Measurements: measurements, 189 }) 190} 191 192func (m *MeterImpl) RunAsyncInstruments() { 193 m.asyncInstruments.Run(context.Background(), m) 194} 195 196// Measured is the helper struct which provides flat representation of recorded measurements 197// to simplify testing 198type Measured struct { 199 Name string 200 InstrumentationName string 201 InstrumentationVersion string 202 Labels map[label.Key]label.Value 203 Number number.Number 204} 205 206// LabelsToMap converts label set to keyValue map, to be easily used in tests 207func LabelsToMap(kvs ...label.KeyValue) map[label.Key]label.Value { 208 m := map[label.Key]label.Value{} 209 for _, label := range kvs { 210 m[label.Key] = label.Value 211 } 212 return m 213} 214 215// AsStructs converts recorded batches to array of flat, readable Measured helper structures 216func AsStructs(batches []Batch) []Measured { 217 var r []Measured 218 for _, batch := range batches { 219 for _, m := range batch.Measurements { 220 r = append(r, Measured{ 221 Name: m.Instrument.Descriptor().Name(), 222 InstrumentationName: m.Instrument.Descriptor().InstrumentationName(), 223 InstrumentationVersion: m.Instrument.Descriptor().InstrumentationVersion(), 224 Labels: LabelsToMap(batch.Labels...), 225 Number: m.Number, 226 }) 227 } 228 } 229 return r 230} 231 232// ResolveNumberByKind takes defined metric descriptor creates a concrete typed metric number 233func ResolveNumberByKind(t *testing.T, kind number.Kind, value float64) number.Number { 234 t.Helper() 235 switch kind { 236 case number.Int64Kind: 237 return number.NewInt64Number(int64(value)) 238 case number.Float64Kind: 239 return number.NewFloat64Number(value) 240 } 241 panic("invalid number kind") 242} 243