From 93a34bb2c3579c903d6ddecec084c300f89a5060 Mon Sep 17 00:00:00 2001 From: Amol Patil <9298683+adp2201@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:13:27 -0700 Subject: [PATCH] Add bursty load JMH benchmark for BatchSpanProcessor --- ...BatchSpanProcessorBurstyLoadBenchmark.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBurstyLoadBenchmark.java diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBurstyLoadBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBurstyLoadBenchmark.java new file mode 100644 index 00000000000..23ea04f2a0e --- /dev/null +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBurstyLoadBenchmark.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Simulates bursty span production with cooldown periods to measure dropped spans and export rates + * under high-throughput conditions. + */ +public class BatchSpanProcessorBurstyLoadBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + private InMemoryMetricReader metricReader; + private BatchSpanProcessor processor; + private Tracer tracer; + private long exportedSpans; + private long droppedSpans; + private double dropRatio; + + @Param({"64", "2048"}) + int maxQueueSize; + + @Param({"64"}) + int maxExportBatchSize; + + @Param({"200"}) + int scheduleDelayMs; + + @Param({"20"}) + int exporterDelayMs; + + @Param({"20000"}) + int burstSize; + + @Param({"5", "25"}) + int cooldownMs; + + @Setup(Level.Iteration) + public void setup() { + metricReader = InMemoryMetricReader.create(); + MeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + SpanExporter exporter = new DelayingSpanExporter(exporterDelayMs); + processor = + BatchSpanProcessor.builder(exporter) + .setMeterProvider(meterProvider) + .setMaxQueueSize(maxQueueSize) + .setMaxExportBatchSize(maxExportBatchSize) + .setScheduleDelay(Duration.ofMillis(scheduleDelayMs)) + .build(); + tracer = SdkTracerProvider.builder().build().get("burstyBenchmarkTracer"); + } + + @TearDown(Level.Iteration) + public void recordMetrics() { + processor.forceFlush().join(30, TimeUnit.SECONDS); + BatchSpanProcessorMetrics metrics = + new BatchSpanProcessorMetrics(metricReader.collectAllMetrics(), 1); + dropRatio = metrics.dropRatio(); + exportedSpans = metrics.exportedSpans(); + droppedSpans = metrics.droppedSpans(); + } + + @TearDown(Level.Iteration) + public void tearDown() { + processor.shutdown().join(30, TimeUnit.SECONDS); + } + } + + @State(Scope.Thread) + @AuxCounters(AuxCounters.Type.EVENTS) + public static class MetricsState { + BenchmarkState benchmarkState; + + @TearDown(Level.Iteration) + public void capture(BenchmarkState benchmarkState) { + this.benchmarkState = benchmarkState; + } + + public long exportedSpans() { + return benchmarkState.exportedSpans; + } + + public long droppedSpans() { + return benchmarkState.droppedSpans; + } + + public double dropRatio() { + return benchmarkState.dropRatio; + } + } + + @Benchmark + @Fork(1) + @Warmup(iterations = 3, time = 1) + @Measurement(iterations = 5, time = 5) + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void exportBursty( + BenchmarkState benchmarkState, @SuppressWarnings("unused") MetricsState metricsState) + throws InterruptedException { + for (int i = 0; i < benchmarkState.burstSize; i++) { + Span span = benchmarkState.tracer.spanBuilder("burst-span").startSpan(); + benchmarkState.processor.onEnd((ReadableSpan) span); + } + if (benchmarkState.cooldownMs > 0) { + TimeUnit.MILLISECONDS.sleep(benchmarkState.cooldownMs); + } + } +}