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