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