From f12abb508f3101660dd882a50e653c976c48c0a5 Mon Sep 17 00:00:00 2001 From: EvgeniiR Date: Thu, 25 Jun 2026 20:59:02 +0200 Subject: [PATCH 1/4] fix: BSP benchmark aux counters always reported zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BatchSpanProcessorMetrics.getMetric() used stringKey("dropped") to filter metric points, but BatchSpanProcessor records the attribute with booleanKey("dropped"). The filter never matched, so exportedSpans and droppedSpans always returned 0 in all BSP benchmarks since #3017. - Fix stringKey → booleanKey in BatchSpanProcessorMetrics - Remove dropRatio() — ratio does not aggregate correctly across JMH thread states - Switch BatchSpanProcessorDroppedSpansBenchmark to Type.EVENTS to avoid integer truncation to zero in tight-loop (millions of ops/iter collapse per-op long counters even after the key-type fix) - Derive numThreads from BenchmarkParams.getThreads() in @Setup instead of hardcoding in each benchmark method body --- ...atchSpanProcessorDroppedSpansBenchmark.java | 8 +------- .../export/BatchSpanProcessorMetrics.java | 18 ++++++------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java index b5a438db88e..6a4e426edf0 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java @@ -32,7 +32,6 @@ public static class BenchmarkState { private InMemoryMetricReader metricReader; private BatchSpanProcessor processor; private Tracer tracer; - private double dropRatio; private long exportedSpans; private long droppedSpans; private int numThreads; @@ -52,7 +51,6 @@ public final void setup() { public final void recordMetrics() { BatchSpanProcessorMetrics metrics = new BatchSpanProcessorMetrics(metricReader.collectAllMetrics(), numThreads); - dropRatio = metrics.dropRatio(); exportedSpans = metrics.exportedSpans(); droppedSpans = metrics.droppedSpans(); } @@ -64,7 +62,7 @@ public final void tearDown() { } @State(Scope.Thread) - @AuxCounters(AuxCounters.Type.OPERATIONS) + @AuxCounters(AuxCounters.Type.EVENTS) public static class ThreadState { BenchmarkState benchmarkState; @@ -73,10 +71,6 @@ public final void recordMetrics(BenchmarkState benchmarkState) { this.benchmarkState = benchmarkState; } - public double dropRatio() { - return benchmarkState.dropRatio; - } - public long exportedSpans() { return benchmarkState.exportedSpans; } diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java index 9a74d60070e..5c9eac2d16a 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.trace.export; -import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; import io.opentelemetry.sdk.metrics.data.LongPointData; import io.opentelemetry.sdk.metrics.data.MetricData; @@ -21,15 +21,6 @@ public BatchSpanProcessorMetrics(Collection allMetrics, int numThrea this.numThreads = numThreads; } - public double dropRatio() { - long exported = getMetric(false); - long dropped = getMetric(true); - long total = exported + dropped; - // Due to peculiarities of JMH reporting we have to divide this by the number of the - // concurrent threads running the actual benchmark. - return total == 0 ? 0 : (double) dropped / total / numThreads; - } - public long exportedSpans() { return getMetric(false) / numThreads; } @@ -39,14 +30,17 @@ public long droppedSpans() { } private long getMetric(boolean dropped) { - String labelValue = String.valueOf(dropped); OptionalLong value = allMetrics.stream() .filter(metricData -> metricData.getName().equals("processedSpans")) .filter(metricData -> !metricData.isEmpty()) .map(metricData -> metricData.getLongSumData().getPoints()) .flatMap(Collection::stream) - .filter(point -> labelValue.equals(point.getAttributes().get(stringKey("dropped")))) + .filter( + point -> { + Boolean attrDropped = point.getAttributes().get(booleanKey("dropped")); + return attrDropped != null && attrDropped == dropped; + }) .mapToLong(LongPointData::getValue) .findFirst(); return value.isPresent() ? value.getAsLong() : 0; From 5d3fcfca712271dfbf8bb922de0d48dd0ef0b4a5 Mon Sep 17 00:00:00 2001 From: EvgeniiR Date: Thu, 25 Jun 2026 21:31:35 +0200 Subject: [PATCH 2/4] fix: derive numThreads from BenchmarkParams instead of hardcoding per method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BenchmarkParams.getThreads() injected into @Setup(Level.Iteration) correctly returns the per-method @Threads(N) value. Verified empirically: ratio (exportedSpans + droppedSpans) / (primary_ops_per_sec × iteration_time) ≈ 1.0 with 5 threads. Removes the fragile manual coupling between @Threads(N) and numThreads = N scattered across each benchmark method body. --- .../sdk/trace/export/BatchSpanProcessorCpuBenchmark.java | 9 +++------ .../export/BatchSpanProcessorDroppedSpansBenchmark.java | 5 +++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorCpuBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorCpuBenchmark.java index 7756a752b82..b5f86793f72 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorCpuBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorCpuBenchmark.java @@ -28,6 +28,7 @@ import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; /* * Run this along with a profiler to measure the CPU usage of BatchSpanProcessor's exporter thread. @@ -47,7 +48,8 @@ public static class BenchmarkState { private long droppedSpans; @Setup(Level.Iteration) - public final void setup() { + public final void setup(BenchmarkParams params) { + numThreads = params.getThreads(); metricReader = InMemoryMetricReader.create(); MeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); @@ -110,7 +112,6 @@ private static void doWork(BenchmarkState benchmarkState) { @OutputTimeUnit(TimeUnit.SECONDS) public void export_01Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 1; doWork(benchmarkState); } @@ -123,7 +124,6 @@ public void export_01Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_02Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 2; doWork(benchmarkState); } @@ -136,7 +136,6 @@ public void export_02Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_05Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 5; doWork(benchmarkState); } @@ -149,7 +148,6 @@ public void export_05Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_10Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 10; doWork(benchmarkState); } @@ -162,7 +160,6 @@ public void export_10Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_20Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 20; doWork(benchmarkState); } } diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java index 6a4e426edf0..ed8c8aef6bf 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java @@ -24,6 +24,7 @@ import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; public class BatchSpanProcessorDroppedSpansBenchmark { @@ -37,7 +38,8 @@ public static class BenchmarkState { private int numThreads; @Setup(Level.Iteration) - public final void setup() { + public final void setup(BenchmarkParams params) { + numThreads = params.getThreads(); metricReader = InMemoryMetricReader.create(); MeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); @@ -89,7 +91,6 @@ public long droppedSpans() { @BenchmarkMode(Mode.Throughput) public void export( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 5; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } From 2b3c36f05ac6747cda8b7eeea5afc4d5b2c5f75b Mon Sep 17 00:00:00 2001 From: EvgeniiR Date: Thu, 25 Jun 2026 22:08:52 +0200 Subject: [PATCH 3/4] fix: apply same BenchmarkParams and Type.EVENTS fix to MultiThreadBenchmark BatchSpanProcessorMultiThreadBenchmark had the same two issues as the other benchmarks fixed in the previous commit: - numThreads hardcoded in each method body (fragile; same BenchmarkParams fix) - Type.OPERATIONS reports spans/s (same unit as primary, drop rate not obvious); Type.EVENTS reports absolute iteration totals with clear drop rates --- .../BatchSpanProcessorMultiThreadBenchmark.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java index d8eabb090c3..f0413eb3cd3 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java @@ -27,6 +27,7 @@ import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; @State(Scope.Benchmark) public class BatchSpanProcessorMultiThreadBenchmark { @@ -45,7 +46,8 @@ public static class BenchmarkState { private long droppedSpans; @Setup(Level.Iteration) - public final void setup() { + public final void setup(BenchmarkParams params) { + numThreads = params.getThreads(); collector = InMemoryMetricReader.create(); MeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(collector).build(); @@ -66,7 +68,7 @@ public final void recordMetrics() { } @State(Scope.Thread) - @AuxCounters(AuxCounters.Type.OPERATIONS) + @AuxCounters(AuxCounters.Type.EVENTS) public static class ThreadState { BenchmarkState benchmarkState; @@ -93,7 +95,6 @@ public long droppedSpans() { @OutputTimeUnit(TimeUnit.SECONDS) public void export_01Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 1; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } @@ -107,7 +108,6 @@ public void export_01Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_02Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 2; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } @@ -121,7 +121,6 @@ public void export_02Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_05Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 5; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } @@ -135,7 +134,6 @@ public void export_05Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_10Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 10; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } @@ -149,7 +147,6 @@ public void export_10Thread( @OutputTimeUnit(TimeUnit.SECONDS) public void export_20Thread( BenchmarkState benchmarkState, @SuppressWarnings("unused") ThreadState threadState) { - benchmarkState.numThreads = 20; benchmarkState.processor.onEnd( (ReadableSpan) benchmarkState.tracer.spanBuilder("span").startSpan()); } From 47db02e4f6102aa05189c6fa7a01b9555fb61172 Mon Sep 17 00:00:00 2001 From: EvgeniiR Date: Fri, 26 Jun 2026 02:55:12 +0200 Subject: [PATCH 4/4] fix: restore dropRatio aux counter for BSP EVENTS benchmarks Add BatchSpanProcessorMetrics.dropRatio(int numIterations), adjusting AggregationPolicy.SUM (numThreads * numIterations) of Type.EVENTS. Use it in DroppedSpansBenchmark and MultiThreadBenchmark, restoring the dropRatio aux counter. CpuBenchmark stays on Type.OPERATIONS, where the drop rate is read directly and this method does not apply. --- ...tchSpanProcessorDroppedSpansBenchmark.java | 8 +++++++ .../export/BatchSpanProcessorMetrics.java | 23 +++++++++++++++++++ ...atchSpanProcessorMultiThreadBenchmark.java | 8 +++++++ 3 files changed, 39 insertions(+) diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java index ed8c8aef6bf..de580ccf4e6 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorDroppedSpansBenchmark.java @@ -33,13 +33,16 @@ public static class BenchmarkState { private InMemoryMetricReader metricReader; private BatchSpanProcessor processor; private Tracer tracer; + private double dropRatio; private long exportedSpans; private long droppedSpans; private int numThreads; + private int numIterations; @Setup(Level.Iteration) public final void setup(BenchmarkParams params) { numThreads = params.getThreads(); + numIterations = params.getMeasurement().getCount(); metricReader = InMemoryMetricReader.create(); MeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); @@ -53,6 +56,7 @@ public final void setup(BenchmarkParams params) { public final void recordMetrics() { BatchSpanProcessorMetrics metrics = new BatchSpanProcessorMetrics(metricReader.collectAllMetrics(), numThreads); + dropRatio = metrics.dropRatio(numIterations); exportedSpans = metrics.exportedSpans(); droppedSpans = metrics.droppedSpans(); } @@ -73,6 +77,10 @@ public final void recordMetrics(BenchmarkState benchmarkState) { this.benchmarkState = benchmarkState; } + public double dropRatio() { + return benchmarkState.dropRatio; + } + public long exportedSpans() { return benchmarkState.exportedSpans; } diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java index 5c9eac2d16a..9d2107669da 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMetrics.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import java.util.Collection; import java.util.OptionalLong; +import org.openjdk.jmh.annotations.AuxCounters; public class BatchSpanProcessorMetrics { private final Collection allMetrics; @@ -21,6 +22,28 @@ public BatchSpanProcessorMetrics(Collection allMetrics, int numThrea this.numThreads = numThreads; } + /** + * Returns the share of dropped spans as a value in {@code [0, 1]}. + * + *

Only meaningful for benchmarks whose {@link AuxCounters} use {@link + * AuxCounters.Type#EVENTS}: JMH emits a {@code ScalarResult} with {@code AggregationPolicy.SUM}, + * which sums the aux counters across the {@code numThreads} thread-local {@code @State} instances + * and the {@code numIterations} measurement iterations. The raw {@code dropped / (exported + + * dropped)} is therefore inflated by {@code numThreads * numIterations}, and both factors are + * divided back out here so the value reported by JMH equals the per-iteration drop ratio. + * + *

For {@link AuxCounters.Type#OPERATIONS} JMH emits a {@code ThroughputResult} (rate per unit + * time) aggregated as a mean across iterations, so this method does not apply — the drop rate is + * read directly from {@link #droppedSpans()} and the ratio, if needed, is computed from the two + * rates. + */ + public double dropRatio(int numIterations) { + long exported = getMetric(false); + long dropped = getMetric(true); + long total = exported + dropped; + return total == 0 ? 0.0 : (double) dropped / total / numThreads / numIterations; + } + public long exportedSpans() { return getMetric(false) / numThreads; } diff --git a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java index f0413eb3cd3..c92c52f55fa 100644 --- a/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java +++ b/sdk/trace/src/jmh/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorMultiThreadBenchmark.java @@ -38,16 +38,19 @@ public static class BenchmarkState { private BatchSpanProcessor processor; private Tracer tracer; private int numThreads = 1; + private int numIterations = 1; @Param({"0"}) private int delayMs; + private double dropRatio; private long exportedSpans; private long droppedSpans; @Setup(Level.Iteration) public final void setup(BenchmarkParams params) { numThreads = params.getThreads(); + numIterations = params.getMeasurement().getCount(); collector = InMemoryMetricReader.create(); MeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(collector).build(); @@ -61,6 +64,7 @@ public final void setup(BenchmarkParams params) { public final void recordMetrics() { BatchSpanProcessorMetrics metrics = new BatchSpanProcessorMetrics(collector.collectAllMetrics(), numThreads); + dropRatio = metrics.dropRatio(numIterations); exportedSpans = metrics.exportedSpans(); droppedSpans = metrics.droppedSpans(); processor.shutdown().join(10, TimeUnit.SECONDS); @@ -77,6 +81,10 @@ public final void recordMetrics(BenchmarkState benchmarkState) { this.benchmarkState = benchmarkState; } + public double dropRatio() { + return benchmarkState.dropRatio; + } + public long exportedSpans() { return benchmarkState.exportedSpans; }