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 metric_test
16
17import (
18	"context"
19	"fmt"
20	"math/rand"
21	"testing"
22
23	"go.opentelemetry.io/otel"
24	"go.opentelemetry.io/otel/label"
25	"go.opentelemetry.io/otel/metric"
26	export "go.opentelemetry.io/otel/sdk/export/metric"
27	sdk "go.opentelemetry.io/otel/sdk/metric"
28	"go.opentelemetry.io/otel/sdk/metric/processor/processortest"
29)
30
31type benchFixture struct {
32	meter       metric.Meter
33	accumulator *sdk.Accumulator
34	B           *testing.B
35	export.AggregatorSelector
36}
37
38func newFixture(b *testing.B) *benchFixture {
39	b.ReportAllocs()
40	bf := &benchFixture{
41		B:                  b,
42		AggregatorSelector: processortest.AggregatorSelector(),
43	}
44
45	bf.accumulator = sdk.NewAccumulator(bf, nil)
46	bf.meter = metric.WrapMeterImpl(bf.accumulator, "benchmarks")
47	return bf
48}
49
50func (f *benchFixture) Process(export.Accumulation) error {
51	return nil
52}
53
54func (f *benchFixture) Meter(_ string, _ ...metric.MeterOption) metric.Meter {
55	return f.meter
56}
57
58func (f *benchFixture) meterMust() metric.MeterMust {
59	return metric.Must(f.meter)
60}
61
62func makeManyLabels(n int) [][]label.KeyValue {
63	r := make([][]label.KeyValue, n)
64
65	for i := 0; i < n; i++ {
66		r[i] = makeLabels(1)
67	}
68
69	return r
70}
71
72func makeLabels(n int) []label.KeyValue {
73	used := map[string]bool{}
74	l := make([]label.KeyValue, n)
75	for i := 0; i < n; i++ {
76		var k string
77		for {
78			k = fmt.Sprint("k", rand.Intn(1000000000))
79			if !used[k] {
80				used[k] = true
81				break
82			}
83		}
84		l[i] = label.String(k, fmt.Sprint("v", rand.Intn(1000000000)))
85	}
86	return l
87}
88
89func benchmarkLabels(b *testing.B, n int) {
90	ctx := context.Background()
91	fix := newFixture(b)
92	labs := makeLabels(n)
93	cnt := fix.meterMust().NewInt64Counter("int64.sum")
94
95	b.ResetTimer()
96
97	for i := 0; i < b.N; i++ {
98		cnt.Add(ctx, 1, labs...)
99	}
100}
101
102func BenchmarkInt64CounterAddWithLabels_1(b *testing.B) {
103	benchmarkLabels(b, 1)
104}
105
106func BenchmarkInt64CounterAddWithLabels_2(b *testing.B) {
107	benchmarkLabels(b, 2)
108}
109
110func BenchmarkInt64CounterAddWithLabels_4(b *testing.B) {
111	benchmarkLabels(b, 4)
112}
113
114func BenchmarkInt64CounterAddWithLabels_8(b *testing.B) {
115	benchmarkLabels(b, 8)
116}
117
118func BenchmarkInt64CounterAddWithLabels_16(b *testing.B) {
119	benchmarkLabels(b, 16)
120}
121
122// Note: performance does not depend on label set size for the
123// benchmarks below--all are benchmarked for a single label.
124
125func BenchmarkAcquireNewHandle(b *testing.B) {
126	fix := newFixture(b)
127	labelSets := makeManyLabels(b.N)
128	cnt := fix.meterMust().NewInt64Counter("int64.sum")
129
130	b.ResetTimer()
131
132	for i := 0; i < b.N; i++ {
133		cnt.Bind(labelSets[i]...)
134	}
135}
136
137func BenchmarkAcquireExistingHandle(b *testing.B) {
138	fix := newFixture(b)
139	labelSets := makeManyLabels(b.N)
140	cnt := fix.meterMust().NewInt64Counter("int64.sum")
141
142	for i := 0; i < b.N; i++ {
143		cnt.Bind(labelSets[i]...).Unbind()
144	}
145
146	b.ResetTimer()
147
148	for i := 0; i < b.N; i++ {
149		cnt.Bind(labelSets[i]...)
150	}
151}
152
153func BenchmarkAcquireReleaseExistingHandle(b *testing.B) {
154	fix := newFixture(b)
155	labelSets := makeManyLabels(b.N)
156	cnt := fix.meterMust().NewInt64Counter("int64.sum")
157
158	for i := 0; i < b.N; i++ {
159		cnt.Bind(labelSets[i]...).Unbind()
160	}
161
162	b.ResetTimer()
163
164	for i := 0; i < b.N; i++ {
165		cnt.Bind(labelSets[i]...).Unbind()
166	}
167}
168
169// Iterators
170
171var benchmarkIteratorVar label.KeyValue
172
173func benchmarkIterator(b *testing.B, n int) {
174	labels := label.NewSet(makeLabels(n)...)
175	b.ResetTimer()
176	for i := 0; i < b.N; i++ {
177		iter := labels.Iter()
178		for iter.Next() {
179			benchmarkIteratorVar = iter.Label()
180		}
181	}
182}
183
184func BenchmarkIterator_0(b *testing.B) {
185	benchmarkIterator(b, 0)
186}
187
188func BenchmarkIterator_1(b *testing.B) {
189	benchmarkIterator(b, 1)
190}
191
192func BenchmarkIterator_2(b *testing.B) {
193	benchmarkIterator(b, 2)
194}
195
196func BenchmarkIterator_4(b *testing.B) {
197	benchmarkIterator(b, 4)
198}
199
200func BenchmarkIterator_8(b *testing.B) {
201	benchmarkIterator(b, 8)
202}
203
204func BenchmarkIterator_16(b *testing.B) {
205	benchmarkIterator(b, 16)
206}
207
208// Counters
209
210func BenchmarkGlobalInt64CounterAddWithSDK(b *testing.B) {
211	// Compare with BenchmarkInt64CounterAdd() to see overhead of global
212	// package. This is in the SDK to avoid the API from depending on the
213	// SDK.
214	ctx := context.Background()
215	fix := newFixture(b)
216
217	sdk := otel.Meter("test")
218	otel.SetMeterProvider(fix)
219
220	labs := []label.KeyValue{label.String("A", "B")}
221	cnt := Must(sdk).NewInt64Counter("int64.sum")
222
223	b.ResetTimer()
224
225	for i := 0; i < b.N; i++ {
226		cnt.Add(ctx, 1, labs...)
227	}
228}
229
230func BenchmarkInt64CounterAdd(b *testing.B) {
231	ctx := context.Background()
232	fix := newFixture(b)
233	labs := makeLabels(1)
234	cnt := fix.meterMust().NewInt64Counter("int64.sum")
235
236	b.ResetTimer()
237
238	for i := 0; i < b.N; i++ {
239		cnt.Add(ctx, 1, labs...)
240	}
241}
242
243func BenchmarkInt64CounterHandleAdd(b *testing.B) {
244	ctx := context.Background()
245	fix := newFixture(b)
246	labs := makeLabels(1)
247	cnt := fix.meterMust().NewInt64Counter("int64.sum")
248	handle := cnt.Bind(labs...)
249
250	b.ResetTimer()
251
252	for i := 0; i < b.N; i++ {
253		handle.Add(ctx, 1)
254	}
255}
256
257func BenchmarkFloat64CounterAdd(b *testing.B) {
258	ctx := context.Background()
259	fix := newFixture(b)
260	labs := makeLabels(1)
261	cnt := fix.meterMust().NewFloat64Counter("float64.sum")
262
263	b.ResetTimer()
264
265	for i := 0; i < b.N; i++ {
266		cnt.Add(ctx, 1.1, labs...)
267	}
268}
269
270func BenchmarkFloat64CounterHandleAdd(b *testing.B) {
271	ctx := context.Background()
272	fix := newFixture(b)
273	labs := makeLabels(1)
274	cnt := fix.meterMust().NewFloat64Counter("float64.sum")
275	handle := cnt.Bind(labs...)
276
277	b.ResetTimer()
278
279	for i := 0; i < b.N; i++ {
280		handle.Add(ctx, 1.1)
281	}
282}
283
284// LastValue
285
286func BenchmarkInt64LastValueAdd(b *testing.B) {
287	ctx := context.Background()
288	fix := newFixture(b)
289	labs := makeLabels(1)
290	mea := fix.meterMust().NewInt64ValueRecorder("int64.lastvalue")
291
292	b.ResetTimer()
293
294	for i := 0; i < b.N; i++ {
295		mea.Record(ctx, int64(i), labs...)
296	}
297}
298
299func BenchmarkInt64LastValueHandleAdd(b *testing.B) {
300	ctx := context.Background()
301	fix := newFixture(b)
302	labs := makeLabels(1)
303	mea := fix.meterMust().NewInt64ValueRecorder("int64.lastvalue")
304	handle := mea.Bind(labs...)
305
306	b.ResetTimer()
307
308	for i := 0; i < b.N; i++ {
309		handle.Record(ctx, int64(i))
310	}
311}
312
313func BenchmarkFloat64LastValueAdd(b *testing.B) {
314	ctx := context.Background()
315	fix := newFixture(b)
316	labs := makeLabels(1)
317	mea := fix.meterMust().NewFloat64ValueRecorder("float64.lastvalue")
318
319	b.ResetTimer()
320
321	for i := 0; i < b.N; i++ {
322		mea.Record(ctx, float64(i), labs...)
323	}
324}
325
326func BenchmarkFloat64LastValueHandleAdd(b *testing.B) {
327	ctx := context.Background()
328	fix := newFixture(b)
329	labs := makeLabels(1)
330	mea := fix.meterMust().NewFloat64ValueRecorder("float64.lastvalue")
331	handle := mea.Bind(labs...)
332
333	b.ResetTimer()
334
335	for i := 0; i < b.N; i++ {
336		handle.Record(ctx, float64(i))
337	}
338}
339
340// ValueRecorders
341
342func benchmarkInt64ValueRecorderAdd(b *testing.B, name string) {
343	ctx := context.Background()
344	fix := newFixture(b)
345	labs := makeLabels(1)
346	mea := fix.meterMust().NewInt64ValueRecorder(name)
347
348	b.ResetTimer()
349
350	for i := 0; i < b.N; i++ {
351		mea.Record(ctx, int64(i), labs...)
352	}
353}
354
355func benchmarkInt64ValueRecorderHandleAdd(b *testing.B, name string) {
356	ctx := context.Background()
357	fix := newFixture(b)
358	labs := makeLabels(1)
359	mea := fix.meterMust().NewInt64ValueRecorder(name)
360	handle := mea.Bind(labs...)
361
362	b.ResetTimer()
363
364	for i := 0; i < b.N; i++ {
365		handle.Record(ctx, int64(i))
366	}
367}
368
369func benchmarkFloat64ValueRecorderAdd(b *testing.B, name string) {
370	ctx := context.Background()
371	fix := newFixture(b)
372	labs := makeLabels(1)
373	mea := fix.meterMust().NewFloat64ValueRecorder(name)
374
375	b.ResetTimer()
376
377	for i := 0; i < b.N; i++ {
378		mea.Record(ctx, float64(i), labs...)
379	}
380}
381
382func benchmarkFloat64ValueRecorderHandleAdd(b *testing.B, name string) {
383	ctx := context.Background()
384	fix := newFixture(b)
385	labs := makeLabels(1)
386	mea := fix.meterMust().NewFloat64ValueRecorder(name)
387	handle := mea.Bind(labs...)
388
389	b.ResetTimer()
390
391	for i := 0; i < b.N; i++ {
392		handle.Record(ctx, float64(i))
393	}
394}
395
396// Observers
397
398func BenchmarkObserverRegistration(b *testing.B) {
399	fix := newFixture(b)
400	names := make([]string, 0, b.N)
401	for i := 0; i < b.N; i++ {
402		names = append(names, fmt.Sprintf("test.%d.lastvalue", i))
403	}
404	cb := func(_ context.Context, result metric.Int64ObserverResult) {}
405
406	b.ResetTimer()
407
408	for i := 0; i < b.N; i++ {
409		fix.meterMust().NewInt64ValueObserver(names[i], cb)
410	}
411}
412
413func BenchmarkValueObserverObservationInt64(b *testing.B) {
414	ctx := context.Background()
415	fix := newFixture(b)
416	labs := makeLabels(1)
417	_ = fix.meterMust().NewInt64ValueObserver("test.lastvalue", func(_ context.Context, result metric.Int64ObserverResult) {
418		for i := 0; i < b.N; i++ {
419			result.Observe((int64)(i), labs...)
420		}
421	})
422
423	b.ResetTimer()
424
425	fix.accumulator.Collect(ctx)
426}
427
428func BenchmarkValueObserverObservationFloat64(b *testing.B) {
429	ctx := context.Background()
430	fix := newFixture(b)
431	labs := makeLabels(1)
432	_ = fix.meterMust().NewFloat64ValueObserver("test.lastvalue", func(_ context.Context, result metric.Float64ObserverResult) {
433		for i := 0; i < b.N; i++ {
434			result.Observe((float64)(i), labs...)
435		}
436	})
437
438	b.ResetTimer()
439
440	fix.accumulator.Collect(ctx)
441}
442
443// MaxSumCount
444
445func BenchmarkInt64MaxSumCountAdd(b *testing.B) {
446	benchmarkInt64ValueRecorderAdd(b, "int64.minmaxsumcount")
447}
448
449func BenchmarkInt64MaxSumCountHandleAdd(b *testing.B) {
450	benchmarkInt64ValueRecorderHandleAdd(b, "int64.minmaxsumcount")
451}
452
453func BenchmarkFloat64MaxSumCountAdd(b *testing.B) {
454	benchmarkFloat64ValueRecorderAdd(b, "float64.minmaxsumcount")
455}
456
457func BenchmarkFloat64MaxSumCountHandleAdd(b *testing.B) {
458	benchmarkFloat64ValueRecorderHandleAdd(b, "float64.minmaxsumcount")
459}
460
461// DDSketch
462
463func BenchmarkInt64DDSketchAdd(b *testing.B) {
464	benchmarkInt64ValueRecorderAdd(b, "int64.sketch")
465}
466
467func BenchmarkInt64DDSketchHandleAdd(b *testing.B) {
468	benchmarkInt64ValueRecorderHandleAdd(b, "int64.sketch")
469}
470
471func BenchmarkFloat64DDSketchAdd(b *testing.B) {
472	benchmarkFloat64ValueRecorderAdd(b, "float64.sketch")
473}
474
475func BenchmarkFloat64DDSketchHandleAdd(b *testing.B) {
476	benchmarkFloat64ValueRecorderHandleAdd(b, "float64.sketch")
477}
478
479// Array
480
481func BenchmarkInt64ArrayAdd(b *testing.B) {
482	benchmarkInt64ValueRecorderAdd(b, "int64.exact")
483}
484
485func BenchmarkInt64ArrayHandleAdd(b *testing.B) {
486	benchmarkInt64ValueRecorderHandleAdd(b, "int64.exact")
487}
488
489func BenchmarkFloat64ArrayAdd(b *testing.B) {
490	benchmarkFloat64ValueRecorderAdd(b, "float64.exact")
491}
492
493func BenchmarkFloat64ArrayHandleAdd(b *testing.B) {
494	benchmarkFloat64ValueRecorderHandleAdd(b, "float64.exact")
495}
496
497// BatchRecord
498
499func benchmarkBatchRecord8Labels(b *testing.B, numInst int) {
500	const numLabels = 8
501	ctx := context.Background()
502	fix := newFixture(b)
503	labs := makeLabels(numLabels)
504	var meas []metric.Measurement
505
506	for i := 0; i < numInst; i++ {
507		inst := fix.meterMust().NewInt64Counter(fmt.Sprintf("int64.%d.sum", i))
508		meas = append(meas, inst.Measurement(1))
509	}
510
511	b.ResetTimer()
512
513	for i := 0; i < b.N; i++ {
514		fix.accumulator.RecordBatch(ctx, labs, meas...)
515	}
516}
517
518func BenchmarkBatchRecord8Labels_1Instrument(b *testing.B) {
519	benchmarkBatchRecord8Labels(b, 1)
520}
521
522func BenchmarkBatchRecord_8Labels_2Instruments(b *testing.B) {
523	benchmarkBatchRecord8Labels(b, 2)
524}
525
526func BenchmarkBatchRecord_8Labels_4Instruments(b *testing.B) {
527	benchmarkBatchRecord8Labels(b, 4)
528}
529
530func BenchmarkBatchRecord_8Labels_8Instruments(b *testing.B) {
531	benchmarkBatchRecord8Labels(b, 8)
532}
533
534// Record creation
535
536func BenchmarkRepeatedDirectCalls(b *testing.B) {
537	ctx := context.Background()
538	fix := newFixture(b)
539
540	c := fix.meterMust().NewInt64Counter("int64.sum")
541	k := label.String("bench", "true")
542
543	b.ResetTimer()
544
545	for i := 0; i < b.N; i++ {
546		c.Add(ctx, 1, k)
547		fix.accumulator.Collect(ctx)
548	}
549}
550