From 7c683e353615b56b1ac9d63f092ad572248be36c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 30 Sep 2024 08:30:34 +0200 Subject: [PATCH 1/5] fix buffer sync logic using modern concurrency primitives Signed-off-by: Gregor Zeitlinger --- .../metrics/core/metrics/Buffer.java | 158 +++++++++++------- 1 file changed, 94 insertions(+), 64 deletions(-) diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java index 69f6de86f..74c1b998e 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java @@ -1,83 +1,113 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; + import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * Metrics support concurrent write and scrape operations. - * - *

This is implemented by switching to a Buffer when the scrape starts, and applying the values - * from the buffer after the scrape ends. + *

+ * This is implemented by switching to a Buffer when the scrape starts, + * and applying the values from the buffer after the scrape ends. */ class Buffer { - private static final long signBit = 1L << 63; - private final AtomicLong observationCount = new AtomicLong(0); - private double[] observationBuffer = new double[0]; - private int bufferPos = 0; - private boolean reset = false; - private final Object appendLock = new Object(); - private final Object runLock = new Object(); - - boolean append(double value) { - long count = observationCount.incrementAndGet(); - if ((count & signBit) == 0) { - return false; // sign bit not set -> buffer not active. - } else { - doAppend(value); - return true; + private static final long bufferActiveBit = 1L << 63; + private final AtomicLong observationCount = new AtomicLong(0); + private double[] observationBuffer = new double[0]; + private int bufferPos = 0; + private boolean reset = false; + + ReentrantLock appendLock = new ReentrantLock(); + ReentrantLock runLock = new ReentrantLock(); + Condition bufferFilled = appendLock.newCondition(); + + boolean append(double value) { + long count = observationCount.incrementAndGet(); + if ((count & bufferActiveBit) == 0) { + return false; // sign bit not set -> buffer not active. + } else { + doAppend(value); + return true; + } } - } - - private void doAppend(double amount) { - synchronized (appendLock) { - if (bufferPos >= observationBuffer.length) { - observationBuffer = Arrays.copyOf(observationBuffer, observationBuffer.length + 128); - } - observationBuffer[bufferPos] = amount; - bufferPos++; + + private void doAppend(double amount) { + try { + appendLock.lock(); + if (bufferPos >= observationBuffer.length) { + observationBuffer = Arrays.copyOf(observationBuffer, observationBuffer.length + 128); + } + observationBuffer[bufferPos] = amount; + bufferPos++; + + bufferFilled.signalAll(); + } finally { + appendLock.unlock(); + } } - } - - /** Must be called by the runnable in the run() method. */ - void reset() { - reset = true; - } - - @SuppressWarnings("ThreadPriorityCheck") - T run( - Function complete, Supplier runnable, Consumer observeFunction) { - double[] buffer; - int bufferSize; - T result; - synchronized (runLock) { - long count = observationCount.getAndAdd(signBit); - while (!complete.apply(count)) { - Thread.yield(); - } - result = runnable.get(); - int expectedBufferSize; - if (reset) { - expectedBufferSize = (int) ((observationCount.getAndSet(0) & ~signBit) - count); - reset = false; - } else { - expectedBufferSize = (int) (observationCount.addAndGet(signBit) - count); - } - while (bufferPos != expectedBufferSize) { - Thread.yield(); - } - buffer = observationBuffer; - bufferSize = bufferPos; - observationBuffer = new double[0]; - bufferPos = 0; + + /** + * Must be called by the runnable in the run() method. + */ + void reset() { + reset = true; } - for (int i = 0; i < bufferSize; i++) { - observeFunction.accept(buffer[i]); + + T run(Function complete, Supplier createResult, Consumer observeFunction) { + double[] buffer; + int bufferSize; + T result; + try { + runLock.lock(); + + // Signal that the buffer is active. + Long expectedCount = observationCount.getAndAdd(bufferActiveBit); + try { + appendLock.lock(); + + while (!complete.apply(expectedCount)) { + // Wait until all in-flight threads have added their observations to the buffer. + bufferFilled.await(); + } + result = createResult.get(); + + // Signal that the buffer is inactive. + int expectedBufferSize; + if (reset) { + expectedBufferSize = (int) ((observationCount.getAndSet(0) & ~bufferActiveBit) - expectedCount); + reset = false; + } else { + expectedBufferSize = (int) (observationCount.addAndGet(bufferActiveBit) - expectedCount); + } + + while (bufferPos < expectedBufferSize) { + // Wait until all in-flight threads have added their observations to the buffer. + bufferFilled.await(); + } + } finally { + appendLock.unlock(); + } + + buffer = observationBuffer; + bufferSize = bufferPos; + observationBuffer = new double[0]; + bufferPos = 0; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + runLock.unlock(); + } + + for (int i = 0; i < bufferSize; i++) { + observeFunction.accept(buffer[i]); + } + return result; } - return result; - } } From 72788302c0d064ba4ddc52f7dc9920487d975671 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 1 Oct 2024 14:04:24 +0200 Subject: [PATCH 2/5] format Signed-off-by: Gregor Zeitlinger --- .../metrics/core/metrics/Buffer.java | 179 +++++++++--------- 1 file changed, 90 insertions(+), 89 deletions(-) diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java index 74c1b998e..031939422 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java @@ -1,7 +1,6 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; - import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; @@ -12,102 +11,104 @@ /** * Metrics support concurrent write and scrape operations. - *

- * This is implemented by switching to a Buffer when the scrape starts, - * and applying the values from the buffer after the scrape ends. + * + *

This is implemented by switching to a Buffer when the scrape starts, and applying the values + * from the buffer after the scrape ends. */ class Buffer { - private static final long bufferActiveBit = 1L << 63; - private final AtomicLong observationCount = new AtomicLong(0); - private double[] observationBuffer = new double[0]; - private int bufferPos = 0; - private boolean reset = false; - - ReentrantLock appendLock = new ReentrantLock(); - ReentrantLock runLock = new ReentrantLock(); - Condition bufferFilled = appendLock.newCondition(); - - boolean append(double value) { - long count = observationCount.incrementAndGet(); - if ((count & bufferActiveBit) == 0) { - return false; // sign bit not set -> buffer not active. + private static final long bufferActiveBit = 1L << 63; + private final AtomicLong observationCount = new AtomicLong(0); + private double[] observationBuffer = new double[0]; + private int bufferPos = 0; + private boolean reset = false; + + ReentrantLock appendLock = new ReentrantLock(); + ReentrantLock runLock = new ReentrantLock(); + Condition bufferFilled = appendLock.newCondition(); + + boolean append(double value) { + long count = observationCount.incrementAndGet(); + if ((count & bufferActiveBit) == 0) { + return false; // sign bit not set -> buffer not active. + } else { + doAppend(value); + return true; + } + } + + private void doAppend(double amount) { + try { + appendLock.lock(); + if (bufferPos >= observationBuffer.length) { + observationBuffer = Arrays.copyOf(observationBuffer, observationBuffer.length + 128); + } + observationBuffer[bufferPos] = amount; + bufferPos++; + + bufferFilled.signalAll(); + } finally { + appendLock.unlock(); + } + } + + /** Must be called by the runnable in the run() method. */ + void reset() { + reset = true; + } + + T run( + Function complete, + Supplier createResult, + Consumer observeFunction) { + double[] buffer; + int bufferSize; + T result; + try { + runLock.lock(); + + // Signal that the buffer is active. + Long expectedCount = observationCount.getAndAdd(bufferActiveBit); + try { + appendLock.lock(); + + while (!complete.apply(expectedCount)) { + // Wait until all in-flight threads have added their observations to the buffer. + bufferFilled.await(); + } + result = createResult.get(); + + // Signal that the buffer is inactive. + int expectedBufferSize; + if (reset) { + expectedBufferSize = + (int) ((observationCount.getAndSet(0) & ~bufferActiveBit) - expectedCount); + reset = false; } else { - doAppend(value); - return true; + expectedBufferSize = (int) (observationCount.addAndGet(bufferActiveBit) - expectedCount); } - } - private void doAppend(double amount) { - try { - appendLock.lock(); - if (bufferPos >= observationBuffer.length) { - observationBuffer = Arrays.copyOf(observationBuffer, observationBuffer.length + 128); - } - observationBuffer[bufferPos] = amount; - bufferPos++; - - bufferFilled.signalAll(); - } finally { - appendLock.unlock(); + while (bufferPos < expectedBufferSize) { + // Wait until all in-flight threads have added their observations to the buffer. + bufferFilled.await(); } + } finally { + appendLock.unlock(); + } + + buffer = observationBuffer; + bufferSize = bufferPos; + observationBuffer = new double[0]; + bufferPos = 0; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + runLock.unlock(); } - /** - * Must be called by the runnable in the run() method. - */ - void reset() { - reset = true; - } - - T run(Function complete, Supplier createResult, Consumer observeFunction) { - double[] buffer; - int bufferSize; - T result; - try { - runLock.lock(); - - // Signal that the buffer is active. - Long expectedCount = observationCount.getAndAdd(bufferActiveBit); - try { - appendLock.lock(); - - while (!complete.apply(expectedCount)) { - // Wait until all in-flight threads have added their observations to the buffer. - bufferFilled.await(); - } - result = createResult.get(); - - // Signal that the buffer is inactive. - int expectedBufferSize; - if (reset) { - expectedBufferSize = (int) ((observationCount.getAndSet(0) & ~bufferActiveBit) - expectedCount); - reset = false; - } else { - expectedBufferSize = (int) (observationCount.addAndGet(bufferActiveBit) - expectedCount); - } - - while (bufferPos < expectedBufferSize) { - // Wait until all in-flight threads have added their observations to the buffer. - bufferFilled.await(); - } - } finally { - appendLock.unlock(); - } - - buffer = observationBuffer; - bufferSize = bufferPos; - observationBuffer = new double[0]; - bufferPos = 0; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - runLock.unlock(); - } - - for (int i = 0; i < bufferSize; i++) { - observeFunction.accept(buffer[i]); - } - return result; + for (int i = 0; i < bufferSize; i++) { + observeFunction.accept(buffer[i]); } + return result; + } } From c3074b838757060233c2f49afb09fa06cd7a41c0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 9 Oct 2024 16:52:09 +0200 Subject: [PATCH 3/5] update benchmarks Signed-off-by: Gregor Zeitlinger --- .../metrics/benchmarks/CounterBenchmark.java | 20 +++++++++---------- .../benchmarks/HistogramBenchmark.java | 10 +++++----- pom.xml | 5 +++++ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java index 8ccf6a40d..7e59b42b7 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java @@ -23,16 +23,16 @@ *

  *
  * Benchmark                                     Mode  Cnt      Score     Error  Units
- * CounterBenchmark.codahaleIncNoLabels         thrpt   25  25761.677 ± 122.947  ops/s
- * CounterBenchmark.openTelemetryAdd            thrpt   25    545.026 ±  33.913  ops/s
- * CounterBenchmark.openTelemetryInc            thrpt   25    550.577 ±  45.415  ops/s
- * CounterBenchmark.openTelemetryIncNoLabels    thrpt   25    527.638 ±  32.020  ops/s
- * CounterBenchmark.prometheusAdd               thrpt   25  20341.474 ±  40.973  ops/s
- * CounterBenchmark.prometheusInc               thrpt   25  26414.616 ±  96.666  ops/s
- * CounterBenchmark.prometheusNoLabelsInc       thrpt   25  26177.676 ± 120.342  ops/s
- * CounterBenchmark.simpleclientAdd             thrpt   25   5503.867 ± 161.313  ops/s
- * CounterBenchmark.simpleclientInc             thrpt   25   5568.125 ±  53.291  ops/s
- * CounterBenchmark.simpleclientNoLabelsInc     thrpt   25   5394.692 ± 130.531  ops/s
+ * CounterBenchmark.codahaleIncNoLabels         thrpt   25  21300.752 ± 3867.708  ops/s
+ * CounterBenchmark.openTelemetryAdd            thrpt   25    299.712 ±   22.742  ops/s
+ * CounterBenchmark.openTelemetryInc            thrpt   25    315.111 ±   22.887  ops/s
+ * CounterBenchmark.openTelemetryIncNoLabels    thrpt   25    350.879 ±   22.652  ops/s
+ * CounterBenchmark.prometheusAdd               thrpt   25  16192.724 ± 4284.309  ops/s
+ * CounterBenchmark.prometheusInc               thrpt   25  39449.763 ± 6379.600  ops/s
+ * CounterBenchmark.prometheusNoLabelsInc       thrpt   25  33968.719 ± 1857.390  ops/s
+ * CounterBenchmark.simpleclientAdd             thrpt   25   6002.535 ±  308.764  ops/s
+ * CounterBenchmark.simpleclientInc             thrpt   25   5726.206 ±  584.231  ops/s
+ * CounterBenchmark.simpleclientNoLabelsInc     thrpt   25   6119.148 ±  195.583  ops/s
  * 
* * Prometheus counters are faster than counters of other libraries. For example, incrementing a diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java index 1bebec573..f287d4f2b 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java @@ -21,11 +21,11 @@ * *
  * Benchmark                                     Mode  Cnt      Score     Error  Units
- * HistogramBenchmark.openTelemetryClassic      thrpt   25    258.660 ±   6.736  ops/s
- * HistogramBenchmark.openTelemetryExponential  thrpt   25    210.963 ±  11.288  ops/s
- * HistogramBenchmark.prometheusClassic         thrpt   25   1528.871 ±  43.598  ops/s
- * HistogramBenchmark.prometheusNative          thrpt   25   1282.643 ± 110.210  ops/s
- * HistogramBenchmark.simpleclient              thrpt   25   3376.016 ± 173.545  ops/s
+ * HistogramBenchmark.openTelemetryClassic      thrpt   25    333.576 ±   17.158  ops/s
+ * HistogramBenchmark.openTelemetryExponential  thrpt   25    232.564 ±    8.653  ops/s
+ * HistogramBenchmark.prometheusClassic         thrpt   25   1650.551 ±   63.382  ops/s
+ * HistogramBenchmark.prometheusNative          thrpt   25   1295.520 ±  104.483  ops/s
+ * HistogramBenchmark.simpleclient              thrpt   25   3682.014 ±  287.201  ops/s
  * 
* * The simpleclient (i.e. client_java version 0.16.0 and older) histograms perform about the same as diff --git a/pom.xml b/pom.xml index 3105a1b22..64435da3f 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,11 @@ Gregor Zeitlinger gregor.zeitlinger@grafana.com + + dhoard + Doug Hoard + doug.hoard@gmail.com + From 5cb68faa0a1926347f4eb618cf82c2bcc7b85426 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 10 Oct 2024 13:02:35 +0200 Subject: [PATCH 4/5] add errorprone Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/core/metrics/Buffer.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java index 031939422..135b09044 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java @@ -38,8 +38,8 @@ boolean append(double value) { } private void doAppend(double amount) { + appendLock.lock(); try { - appendLock.lock(); if (bufferPos >= observationBuffer.length) { observationBuffer = Arrays.copyOf(observationBuffer, observationBuffer.length + 128); } @@ -64,14 +64,14 @@ T run( double[] buffer; int bufferSize; T result; - try { - runLock.lock(); + runLock.lock(); + try { // Signal that the buffer is active. Long expectedCount = observationCount.getAndAdd(bufferActiveBit); - try { - appendLock.lock(); + appendLock.lock(); + try { while (!complete.apply(expectedCount)) { // Wait until all in-flight threads have added their observations to the buffer. bufferFilled.await(); From 5095238a30bf2aa5a056d9e786dd4a846c38cea7 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 11 Oct 2024 07:10:25 +0200 Subject: [PATCH 5/5] update benchmarks Signed-off-by: Gregor Zeitlinger --- benchmarks/pom.xml | 31 ++++++++++++++++--- .../metrics/benchmarks/CounterBenchmark.java | 22 ++++++------- .../benchmarks/HistogramBenchmark.java | 12 +++---- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 90e5800d2..2bb1c15a6 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -51,9 +52,9 @@ ${simpleclient.version} - com.codahale.metrics - metrics-core - ${codahale.version} + com.codahale.metrics + metrics-core + ${codahale.version} io.opentelemetry @@ -74,6 +75,25 @@ ${project.artifactId} + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + -parameters + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + org.apache.maven.plugins maven-shade-plugin @@ -86,7 +106,8 @@ benchmarks - + io.prometheus.metrics.benchmarks.BenchmarkRunner diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java index 7e59b42b7..f3c1e9309 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java @@ -18,21 +18,21 @@ import org.openjdk.jmh.annotations.Threads; /** - * Results on a machine with dedicated 8 vCPU cores: + * Results on a machine with dedicated Core i7 1265U: * *
  *
  * Benchmark                                     Mode  Cnt      Score     Error  Units
- * CounterBenchmark.codahaleIncNoLabels         thrpt   25  21300.752 ± 3867.708  ops/s
- * CounterBenchmark.openTelemetryAdd            thrpt   25    299.712 ±   22.742  ops/s
- * CounterBenchmark.openTelemetryInc            thrpt   25    315.111 ±   22.887  ops/s
- * CounterBenchmark.openTelemetryIncNoLabels    thrpt   25    350.879 ±   22.652  ops/s
- * CounterBenchmark.prometheusAdd               thrpt   25  16192.724 ± 4284.309  ops/s
- * CounterBenchmark.prometheusInc               thrpt   25  39449.763 ± 6379.600  ops/s
- * CounterBenchmark.prometheusNoLabelsInc       thrpt   25  33968.719 ± 1857.390  ops/s
- * CounterBenchmark.simpleclientAdd             thrpt   25   6002.535 ±  308.764  ops/s
- * CounterBenchmark.simpleclientInc             thrpt   25   5726.206 ±  584.231  ops/s
- * CounterBenchmark.simpleclientNoLabelsInc     thrpt   25   6119.148 ±  195.583  ops/s
+ * CounterBenchmark.codahaleIncNoLabels         thrpt   25  32969.795 ± 1547.775  ops/s
+ * CounterBenchmark.openTelemetryAdd            thrpt   25    747.068 ±   93.128  ops/s
+ * CounterBenchmark.openTelemetryInc            thrpt   25    760.784 ±   47.595  ops/s
+ * CounterBenchmark.openTelemetryIncNoLabels    thrpt   25    824.346 ±   45.131  ops/s
+ * CounterBenchmark.prometheusAdd               thrpt   25  28403.000 ±  250.774  ops/s
+ * CounterBenchmark.prometheusInc               thrpt   25  38368.142 ±  361.914  ops/s
+ * CounterBenchmark.prometheusNoLabelsInc       thrpt   25  35558.069 ± 4020.926  ops/s
+ * CounterBenchmark.simpleclientAdd             thrpt   25   4081.152 ±  620.094  ops/s
+ * CounterBenchmark.simpleclientInc             thrpt   25   5735.644 ± 1205.329  ops/s
+ * CounterBenchmark.simpleclientNoLabelsInc     thrpt   25   6852.563 ±  544.481  ops/s
  * 
* * Prometheus counters are faster than counters of other libraries. For example, incrementing a diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java index f287d4f2b..a8e6bddb1 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java @@ -17,15 +17,15 @@ import org.openjdk.jmh.annotations.Threads; /** - * Results on a machine with dedicated 8 vCPU cores: + * Results on a machine with dedicated Core i7 1265U: * *
  * Benchmark                                     Mode  Cnt      Score     Error  Units
- * HistogramBenchmark.openTelemetryClassic      thrpt   25    333.576 ±   17.158  ops/s
- * HistogramBenchmark.openTelemetryExponential  thrpt   25    232.564 ±    8.653  ops/s
- * HistogramBenchmark.prometheusClassic         thrpt   25   1650.551 ±   63.382  ops/s
- * HistogramBenchmark.prometheusNative          thrpt   25   1295.520 ±  104.483  ops/s
- * HistogramBenchmark.simpleclient              thrpt   25   3682.014 ±  287.201  ops/s
+ * HistogramBenchmark.openTelemetryClassic      thrpt   25    390.982 ±   16.058  ops/s
+ * HistogramBenchmark.openTelemetryExponential  thrpt   25    320.160 ±   18.056  ops/s
+ * HistogramBenchmark.prometheusClassic         thrpt   25   2385.862 ±   34.766  ops/s
+ * HistogramBenchmark.prometheusNative          thrpt   25   1947.371 ±   48.193  ops/s
+ * HistogramBenchmark.simpleclient              thrpt   25   4324.961 ±   50.938  ops/s
  * 
* * The simpleclient (i.e. client_java version 0.16.0 and older) histograms perform about the same as