From df7cf6338b596bfe537a474f4d7367d145d52c9f Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 6 Jun 2019 16:43:35 +0200 Subject: [PATCH 01/19] Implement HdrSummary based on Summary and HdrHistogram Signed-off-by: Rudolf Rakos --- benchmark/pom.xml | 5 + .../benchmark/HdrSummaryBenchmark.java | 103 +++++ pom.xml | 1 + simpleclient_hdrhistogram/pom.xml | 62 +++ .../java/io/prometheus/client/HdrSummary.java | 388 ++++++++++++++++++ .../client/HdrTimeWindowQuantiles.java | 91 ++++ .../io/prometheus/client/HdrSummaryTest.java | 236 +++++++++++ 7 files changed, 886 insertions(+) create mode 100644 benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java create mode 100644 simpleclient_hdrhistogram/pom.xml create mode 100644 simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java create mode 100644 simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java create mode 100644 simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java diff --git a/benchmark/pom.xml b/benchmark/pom.xml index adbda13af..bab18ce0f 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -51,6 +51,11 @@ simpleclient 0.8.2-SNAPSHOT + + io.prometheus + simpleclient_hdrhistogram + 0.8.2-SNAPSHOT + com.codahale.metrics metrics-core diff --git a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java new file mode 100644 index 000000000..9fc222d39 --- /dev/null +++ b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java @@ -0,0 +1,103 @@ +package io.prometheus.benchmark; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +public class HdrSummaryBenchmark { + + io.prometheus.client.HdrSummary prometheusSimpleHdrSummary; + io.prometheus.client.HdrSummary.Child prometheusSimpleHdrSummaryChild; + io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryNoLabels; + io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryQuantiles; + io.prometheus.client.HdrSummary.Child prometheusSimpleHdrSummaryQuantilesChild; + io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryQuantilesNoLabels; + + @Setup + public void setup() { + prometheusSimpleHdrSummary = io.prometheus.client.HdrSummary.build() + .name("name") + .help("some description..") + .labelNames("some", "group").create(); + prometheusSimpleHdrSummaryChild = prometheusSimpleHdrSummary.labels("test", "group"); + + prometheusSimpleHdrSummaryNoLabels = io.prometheus.client.HdrSummary.build() + .name("name") + .help("some description..") + .create(); + + prometheusSimpleHdrSummaryQuantiles = io.prometheus.client.HdrSummary.build() + .name("name") + .help("some description..") + .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) + .labelNames("some", "group").create(); + prometheusSimpleHdrSummaryQuantilesChild = prometheusSimpleHdrSummaryQuantiles.labels("test", "group"); + + prometheusSimpleHdrSummaryQuantilesNoLabels = io.prometheus.client.HdrSummary.build() + .name("name") + .help("some description..") + .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) + .create(); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryBenchmark() { + prometheusSimpleHdrSummary.labels("test", "group").observe(1) ; + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryChildBenchmark() { + prometheusSimpleHdrSummaryChild.observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryNoLabelsBenchmark() { + prometheusSimpleHdrSummaryNoLabels.observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryQuantilesBenchmark() { + prometheusSimpleHdrSummaryQuantiles.labels("test", "group").observe(1) ; + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryQuantilesChildBenchmark() { + prometheusSimpleHdrSummaryQuantilesChild.observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleHdrSummaryQuantilesNoLabelsBenchmark() { + prometheusSimpleHdrSummaryQuantilesNoLabels.observe(1); + } + + public static void main(String[] args) throws RunnerException { + + Options opt = new OptionsBuilder() + .include(HdrSummaryBenchmark.class.getSimpleName()) + .warmupIterations(5) + .measurementIterations(4) + .threads(4) + .forks(1) + .build(); + + new Runner(opt).run(); + } + +} diff --git a/pom.xml b/pom.xml index d3f19a302..b8647dc44 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ simpleclient_graphite_bridge simpleclient_hibernate simpleclient_guava + simpleclient_hdrhistogram simpleclient_hotspot simpleclient_httpserver simpleclient_log4j diff --git a/simpleclient_hdrhistogram/pom.xml b/simpleclient_hdrhistogram/pom.xml new file mode 100644 index 000000000..62af9fded --- /dev/null +++ b/simpleclient_hdrhistogram/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + io.prometheus + parent + 0.8.2-SNAPSHOT + + + io.prometheus + simpleclient_hdrhistogram + bundle + + Prometheus Java Simpleclient HdrHistogram + + Collectors using HdrHistogram. + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + rrakos-evo + Rudolf Rakos + rrakos@evolutiongaming.com + + + brian-brazil + Brian Brazil + brian.brazil@boxever.com + + + + + + io.prometheus + simpleclient + 0.8.2-SNAPSHOT + + + org.hdrhistogram + HdrHistogram + 2.1.12 + bundle + + + + + junit + junit + 4.11 + test + + + diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java new file mode 100644 index 000000000..2f37c8dbd --- /dev/null +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java @@ -0,0 +1,388 @@ +package io.prometheus.client; + +import java.io.Closeable; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * Summary metric using HdrHistogram, to track the size of events. + *

+ * Example of uses for Summaries include: + *

    + *
  • Response latency
  • + *
  • Request size
  • + *
+ * + * See http://hdrhistogram.org and https://github.com/HdrHistogram/HdrHistogram for more info on HdrHistogram. + * + *

+ * Example Summaries: + *

+ * {@code
+ *   class YourClass {
+ *     static final HdrSummary receivedBytes = HdrSummary.build()
+ *         .name("requests_size_bytes").help("Request size in bytes.").register();
+ *     static final HdrSummary requestLatency = HdrSummary.build()
+ *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
+ *
+ *     void processRequest(Request req) {
+ *       HdrSummary.Timer requestTimer = requestLatency.startTimer();
+ *       try {
+ *         // Your code here.
+ *       } finally {
+ *         receivedBytes.observe(req.size());
+ *         requestTimer.observeDuration();
+ *       }
+ *     }
+ *
+ *     // Or if using Java 8 and lambdas.
+ *     void processRequestLambda(Request req) {
+ *       receivedBytes.observe(req.size());
+ *       requestLatency.time(() -> {
+ *         // Your code here.
+ *       });
+ *     }
+ *   }
+ * }
+ * 
+ * This would allow you to track request rate, average latency and average request size. + * + *

+ * How to add custom quantiles: + *

+ * {@code
+ *   static final HdrSummary myMetric = HdrSummary.build()
+ *       .quantile(0.5)  // Add 50th percentile (= median)
+ *       .quantile(0.9)  // Add 90th percentile
+ *       .quantile(0.99) // Add 99th percentile
+ *       .name("requests_size_bytes")
+ *       .help("Request size in bytes.")
+ *       .register();
+ * }
+ * 
+ * + * The quantiles are calculated over a sliding window of time. There are two options to configure this time window: + *
    + *
  • maxAgeSeconds(long): Set the duration of the time window is, i.e. how long observations are kept before they are discarded. + * Default is 10 minutes. + *
  • ageBuckets(int): Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, + * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) + * and how smooth the time window is moved. Default value is 5. + *
+ * + * See https://prometheus.io/docs/practices/histograms/ for more info on quantiles. + */ +public class HdrSummary extends SimpleCollector implements Counter.Describable { + + private final List quantiles; // Can be empty, but can never be null. + private final int numberOfSignificantValueDigits; + private final long maxAgeSeconds; + private final int ageBuckets; + + private HdrSummary(Builder b) { + super(b); + this.quantiles = Collections.unmodifiableList(new ArrayList(b.quantiles)); + this.numberOfSignificantValueDigits = b.numberOfSignificantValueDigits; + this.maxAgeSeconds = b.maxAgeSeconds; + this.ageBuckets = b.ageBuckets; + initializeNoLabelsChild(); + } + + public static class Builder extends SimpleCollector.Builder { + + private final List quantiles = new ArrayList(); + private int numberOfSignificantValueDigits = 3; + private long maxAgeSeconds = TimeUnit.MINUTES.toSeconds(10); + private int ageBuckets = 5; + + public Builder quantile(double quantile) { + if (quantile < 0.0 || quantile > 1.0) { + throw new IllegalArgumentException("Quantile " + quantile + " invalid: Expected number between 0.0 and 1.0."); + } + quantiles.add(quantile); + return this; + } + + public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { + if (numberOfSignificantValueDigits < 0 || numberOfSignificantValueDigits > 5) { + throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits); + } + this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; + return this; + } + + public Builder maxAgeSeconds(long maxAgeSeconds) { + if (maxAgeSeconds <= 0) { + throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds); + } + this.maxAgeSeconds = maxAgeSeconds; + return this; + } + + public Builder ageBuckets(int ageBuckets) { + if (ageBuckets <= 0) { + throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets); + } + this.ageBuckets = ageBuckets; + return this; + } + + @Override + public HdrSummary create() { + for (String label : labelNames) { + if (label.equals("quantile")) { + throw new IllegalStateException("Summary cannot have a label named 'quantile'."); + } + } + dontInitializeNoLabelsChild = true; + return new HdrSummary(this); + } + + } + + /** + * Return a Builder to allow configuration of a new HdrSummary. Ensures required fields are provided. + * + * @param name The name of the metric + * @param help The help string of the metric + */ + public static Builder build(String name, String help) { + return new Builder().name(name).help(help); + } + + /** + * Return a Builder to allow configuration of a new HdrSummary. + */ + public static Builder build() { + return new Builder(); + } + + @Override + protected Child newChild() { + return new Child(quantiles, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); + } + + /** + * Represents an event being timed. + */ + public static class Timer implements Closeable { + + private final Child child; + private final long start; + + private Timer(Child child) { + this.child = child; + this.start = SimpleTimer.defaultTimeProvider.nanoTime(); + } + + /** + * Observe the amount of time in seconds since {@link Child#startTimer} was called. + * + * @return Measured duration in seconds since {@link Child#startTimer} was called. + */ + public double observeDuration() { + long end = SimpleTimer.defaultTimeProvider.nanoTime(); + double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, end); + child.observe(elapsed); + return elapsed; + } + + /** + * Equivalent to calling {@link #observeDuration()}. + */ + @Override + public void close() { + observeDuration(); + } + + } + + /** + * The value of a single HdrSummary. + *

+ * Warning: References to a Child become invalid after using + * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. + */ + public static class Child { + + public static class Value { + + public final double count; + public final double sum; + public final double min; + public final double max; + public final SortedMap quantiles; + + private Value(DoubleAdder count, DoubleAdder sum, List quantiles, HdrTimeWindowQuantiles quantileValues) { + this.count = count.sum(); + this.sum = sum.sum(); + this.min = quantileValues == null ? Double.NaN : quantileValues.getMin(); + this.max = quantileValues == null ? Double.NaN : quantileValues.getMax(); + this.quantiles = Collections.unmodifiableSortedMap(snapshot(quantiles, quantileValues)); + } + + private SortedMap snapshot(List quantiles, HdrTimeWindowQuantiles quantileValues) { + SortedMap result = new TreeMap(); + for (Double quantile : quantiles) { + result.put(quantile, quantileValues.get(quantile)); + } + return result; + } + + } + + // Having these separate leaves us open to races, + // however Prometheus as whole has other races + // that mean adding atomicity here wouldn't be useful. + // This should be reevaluated in the future. + private final DoubleAdder count = new DoubleAdder(); + private final DoubleAdder sum = new DoubleAdder(); + private final List quantiles; + private final HdrTimeWindowQuantiles quantileValues; + + private Child(List quantiles, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + this.quantiles = quantiles; + this.quantileValues = quantiles.isEmpty() ? null : new HdrTimeWindowQuantiles(numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); + } + + /** + * Observe the given amount. + */ + public void observe(double amt) { + count.add(1); + sum.add(amt); + if (quantileValues != null) { + quantileValues.insert(amt); + } + } + + /** + * Start a timer to track a duration. + *

+ * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. + */ + public Timer startTimer() { + return new Timer(this); + } + + /** + * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Measured duration in seconds for timeable to complete. + */ + public double time(Runnable timeable) { + Timer timer = startTimer(); + double elapsed; + try { + timeable.run(); + } finally { + elapsed = timer.observeDuration(); + } + return elapsed; + } + + /** + * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Result returned by callable. + */ + public E time(Callable timeable) { + Timer timer = startTimer(); + try { + return timeable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + timer.observeDuration(); + } + } + + /** + * Get the value of the Summary. + *

+ * Warning: The definition of {@link Value} is subject to change. + */ + public Value get() { + return new Value(count, sum, quantiles, quantileValues); + } + + } + + // Convenience methods. + + /** + * Observe the given amount on the summary with no labels. + */ + public void observe(double amt) { + noLabelsChild.observe(amt); + } + + /** + * Start a timer to track a duration on the summary with no labels. + *

+ * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. + */ + public Timer startTimer() { + return noLabelsChild.startTimer(); + } + + /** + * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Measured duration in seconds for timeable to complete. + */ + public double time(Runnable timeable) { + return noLabelsChild.time(timeable); + } + + /** + * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Result returned by callable. + */ + public E time(Callable timeable) { + return noLabelsChild.time(timeable); + } + + /** + * Get the value of the HdrSummary. + *

+ * Warning: The definition of {@link Child.Value} is subject to change. + */ + public Child.Value get() { + return noLabelsChild.get(); + } + + @Override + public List collect() { + List samples = new ArrayList(); + for (Map.Entry, Child> child : children.entrySet()) { + Child.Value value = child.getValue().get(); + List labelNamesWithQuantile = new ArrayList(labelNames); + labelNamesWithQuantile.add("quantile"); + for (Map.Entry quantile : value.quantiles.entrySet()) { + List labelValuesWithQuantile = new ArrayList(child.getKey()); + labelValuesWithQuantile.add(doubleToGoString(quantile.getKey())); + samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithQuantile, labelValuesWithQuantile, quantile.getValue())); + } + if (!value.quantiles.isEmpty()) { + samples.add(new MetricFamilySamples.Sample(fullname + "_min", labelNames, child.getKey(), value.min)); + samples.add(new MetricFamilySamples.Sample(fullname + "_max", labelNames, child.getKey(), value.max)); + } + samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, child.getKey(), value.count)); + samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, child.getKey(), value.sum)); + } + return familySamplesList(Type.SUMMARY, samples); + } + + @Override + public List describe() { + return Collections.singletonList(new SummaryMetricFamily(fullname, help, labelNames)); + } + +} diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java new file mode 100644 index 000000000..cd0f70040 --- /dev/null +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -0,0 +1,91 @@ +package io.prometheus.client; + +import org.HdrHistogram.ConcurrentDoubleHistogram; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Wrapper around HdrHistogram. + *

+ * Maintains a ring buffer of HdrHistogram to provide quantiles over a sliding windows of time. + */ +class HdrTimeWindowQuantiles { + + private final int numberOfSignificantValueDigits; + private final ConcurrentLinkedQueue buckets; + private final AtomicLong lastRotateTimestampMillis; + private final long durationBetweenRotatesMillis; + + public HdrTimeWindowQuantiles(int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; + this.buckets = new ConcurrentLinkedQueue(); + for (int i = 0; i < ageBuckets; i++) { + this.buckets.add(new ConcurrentDoubleHistogram(numberOfSignificantValueDigits)); + } + this.lastRotateTimestampMillis = new AtomicLong(System.currentTimeMillis()); + this.durationBetweenRotatesMillis = TimeUnit.SECONDS.toMillis(maxAgeSeconds) / ageBuckets; + } + + public double get(double quantile) { + // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getValueAtPercentile(quantile * 100.0); + } + + public double getMin() { + // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMinValue(); + } + + public double getMax() { + // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMaxValue(); + } + + public void insert(double value) { + // On concurrent `insert` and `rotate`, it might be acceptable to lose the measurement in the newest `bucket`. + rotate(); + + for (ConcurrentDoubleHistogram bucket : buckets) { + bucket.recordValue(value); + } + } + + private ConcurrentDoubleHistogram getCurrentBucket() { + // On concurrent `get` and `rotate`: + // - `currentBucket` could be `null` when there is only a single bucket (edge case). + rotate(); + + ConcurrentDoubleHistogram currentBucket; + do { + currentBucket = buckets.peek(); + } while (currentBucket == null); + + return currentBucket; + } + + private void rotate() { + // On concurrent `rotate` and `rotate`: + // - `currentTimeMillis` is cached to reduce thread contention. + // - `lastRotateTimestampMillis` is used to ensure the correct number of rotations. + // - `currentBucket` could be `null` when there is only a single bucket (edge case). + long currentTimeMillis = System.currentTimeMillis(); + long lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); + ConcurrentDoubleHistogram currentBucket = buckets.peek(); + while (currentTimeMillis - lastRotateTimestampMillis > durationBetweenRotatesMillis) { + ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); + if (this.lastRotateTimestampMillis.compareAndSet( + lastRotateTimestampMillis, lastRotateTimestampMillis + durationBetweenRotatesMillis) + && buckets.remove(currentBucket)) { + buckets.add(bucket); + } + lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); + currentBucket = buckets.peek(); + } + } + +} diff --git a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java new file mode 100644 index 000000000..a8ec86153 --- /dev/null +++ b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java @@ -0,0 +1,236 @@ +package io.prometheus.client; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HdrSummaryTest { + + private CollectorRegistry registry; + private HdrSummary noLabels, labels, noLabelsAndQuantiles, labelsAndQuantiles; + + @Before + public void setUp() { + registry = new CollectorRegistry(); + noLabels = HdrSummary.build() + .name("nolabels").help("help").register(registry); + labels = HdrSummary.build() + .labelNames("l") + .name("labels").help("help").register(registry); + noLabelsAndQuantiles = HdrSummary.build() + .quantile(0.5).quantile(0.9).quantile(0.99) + .name("no_labels_and_quantiles").help("help").register(registry); + labelsAndQuantiles = HdrSummary.build() + .labelNames("l").quantile(0.5).quantile(0.9).quantile(0.99) + .name("labels_and_quantiles").help("help").register(registry); + } + + @After + public void tearDown() { + SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider(); + } + + private double getCount() { + return registry.getSampleValue("nolabels_count"); + } + + private double getSum() { + return registry.getSampleValue("nolabels_sum"); + } + + private double getNoLabelQuantile(double q) { + return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}); + } + + private double getLabeledQuantile(double q) { + return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{"a", Collector.doubleToGoString(q)}); + } + + private Double getLabelsCount(String labelValue) { + return registry.getSampleValue("labels_count", new String[]{"l"}, new String[]{labelValue}); + } + + private Double getLabelsSum(String labelValue) { + return registry.getSampleValue("labels_sum", new String[]{"l"}, new String[]{labelValue}); + } + + @Test + public void testObserve() { + noLabels.observe(2); + assertEquals(1.0, getCount(), .001); + assertEquals(2.0, getSum(), .001); + assertEquals(1.0, noLabels.get().count, .001); + assertEquals(2.0, noLabels.get().sum, .001); + + noLabels.labels().observe(4); + assertEquals(2.0, getCount(), .001); + assertEquals(6.0, getSum(), .001); + assertEquals(2.0, noLabels.get().count, .001); + assertEquals(6.0, noLabels.get().sum, .001); + } + + @Test + public void testQuantiles() { + int nSamples = 1000000; // simulate one million samples + + for (int i = 1; i <= nSamples; i++) { + // In this test, we observe the numbers from 1 to nSamples, + // because that makes it easy to verify if the quantiles are correct. + labelsAndQuantiles.labels("a").observe(i); + noLabelsAndQuantiles.observe(i); + } + + assertEquals(0.5 * nSamples, getNoLabelQuantile(0.5), 0.05 * nSamples); + assertEquals(0.9 * nSamples, getNoLabelQuantile(0.9), 0.01 * nSamples); + assertEquals(0.99 * nSamples, getNoLabelQuantile(0.99), 0.001 * nSamples); + assertEquals(1.0, noLabelsAndQuantiles.get().min, 0.001 * 1.0); + assertEquals((double) nSamples, noLabelsAndQuantiles.get().max, 0.001 * nSamples); + + assertEquals(0.5 * nSamples, getLabeledQuantile(0.5), 0.05 * nSamples); + assertEquals(0.9 * nSamples, getLabeledQuantile(0.9), 0.01 * nSamples); + assertEquals(0.99 * nSamples, getLabeledQuantile(0.99), 0.001 * nSamples); + assertEquals(1.0, labelsAndQuantiles.labels("a").get().min, 0.001 * 1.0); + assertEquals((double) nSamples, labelsAndQuantiles.labels("a").get().max, 0.001 * nSamples); + } + + @Test + public void testMaxAge() throws InterruptedException { + HdrSummary summary = HdrSummary.build() + .quantile(0.99) + .maxAgeSeconds(1) // After 1s, all observations will be discarded. + .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. + .name("short_attention_span").help("help").register(registry); + + summary.observe(8.0); + double val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val, 0.0); // From bucket 1. + + Thread.sleep(600); + val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val, 0.0); // From bucket 2. + + Thread.sleep(600); + val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(Double.NaN, val, 0.0); // Bucket 1 again, now it is empty. + } + + @Test + public void testTimer() { + SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider() { + long value = (long) (30 * 1e9); + long nanoTime() { + value += (long) (10 * 1e9); + return value; + } + }; + + double elapsed = noLabels.time(new Runnable() { + @Override + public void run() { + // no op + } + }); + assertEquals(10, elapsed, .001); + + int result = noLabels.time(new Callable() { + @Override + public Integer call() { + return 123; + } + }); + assertEquals(123, result); + + HdrSummary.Timer timer = noLabels.startTimer(); + elapsed = timer.observeDuration(); + assertEquals(10, elapsed, .001); + + assertEquals(3, getCount(), .001); + assertEquals(30, getSum(), .001); + } + + @Test + public void noLabelsDefaultZeroValue() { + assertEquals(0.0, getCount(), .001); + assertEquals(0.0, getSum(), .001); + } + + @Test + public void testLabels() { + assertEquals(null, getLabelsCount("a")); + assertEquals(null, getLabelsSum("a")); + assertEquals(null, getLabelsCount("b")); + assertEquals(null, getLabelsSum("b")); + + labels.labels("a").observe(2); + assertEquals(1.0, getLabelsCount("a"), .001); + assertEquals(2.0, getLabelsSum("a"), .001); + assertEquals(null, getLabelsCount("b")); + assertEquals(null, getLabelsSum("b")); + + labels.labels("b").observe(3); + assertEquals(1.0, getLabelsCount("a"), .001); + assertEquals(2.0, getLabelsSum("a"), .001); + assertEquals(1.0, getLabelsCount("b"), .001); + assertEquals(3.0, getLabelsSum("b"), .001); + } + + @Test + public void testCollect() { + labels.labels("a").observe(2); + List mfs = labels.collect(); + + ArrayList samples = new ArrayList(); + samples.add(new Collector.MetricFamilySamples.Sample("labels_count", asList("l"), asList("a"), 1.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", asList("l"), asList("a"), 2.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.SUMMARY, "help", samples); + + assertEquals(1, mfs.size()); + assertEquals(mfsFixture, mfs.get(0)); + } + + @Test + public void testCollectWithQuantiles() { + labelsAndQuantiles.labels("a").observe(2); + List mfs = labelsAndQuantiles.collect(); + + ArrayList samples = new ArrayList(); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.5"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.9"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.99"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_min", asList("l"), asList("a"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_max", asList("l"), asList("a"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_count", asList("l"), asList("a"), 1.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_sum", asList("l"), asList("a"), 2.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_and_quantiles", Collector.Type.SUMMARY, "help", samples); + + assertEquals(1, mfs.size()); + assertEquals(mfsFixture, mfs.get(0)); + } + + @Test + public void testChildAndValuePublicApi() throws Exception { + assertTrue(Modifier.isPublic(HdrSummary.Child.class.getModifiers())); + + final Method getMethod = HdrSummary.Child.class.getMethod("get"); + assertTrue(Modifier.isPublic(getMethod.getModifiers())); + assertEquals(HdrSummary.Child.Value.class, getMethod.getReturnType()); + + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getModifiers())); + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("min").getModifiers())); + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("max").getModifiers())); + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("count").getModifiers())); + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("sum").getModifiers())); + assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("quantiles").getModifiers())); + } + +} From 0f4bce67f764ff2a0b83f3557d0e2ad0edd6ba4a Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Fri, 7 Jun 2019 21:53:44 +0200 Subject: [PATCH 02/19] Fix and simplify HdrTimeWindowQuantiles concurrent bucket rotation Signed-off-by: Rudolf Rakos --- .../client/HdrTimeWindowQuantiles.java | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java index cd0f70040..9c9743476 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -56,35 +56,25 @@ public void insert(double value) { } private ConcurrentDoubleHistogram getCurrentBucket() { - // On concurrent `get` and `rotate`: - // - `currentBucket` could be `null` when there is only a single bucket (edge case). rotate(); - ConcurrentDoubleHistogram currentBucket; - do { - currentBucket = buckets.peek(); - } while (currentBucket == null); - - return currentBucket; + return buckets.peek(); } private void rotate() { // On concurrent `rotate` and `rotate`: // - `currentTimeMillis` is cached to reduce thread contention. // - `lastRotateTimestampMillis` is used to ensure the correct number of rotations. - // - `currentBucket` could be `null` when there is only a single bucket (edge case). long currentTimeMillis = System.currentTimeMillis(); long lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); - ConcurrentDoubleHistogram currentBucket = buckets.peek(); while (currentTimeMillis - lastRotateTimestampMillis > durationBetweenRotatesMillis) { - ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); if (this.lastRotateTimestampMillis.compareAndSet( - lastRotateTimestampMillis, lastRotateTimestampMillis + durationBetweenRotatesMillis) - && buckets.remove(currentBucket)) { + lastRotateTimestampMillis, lastRotateTimestampMillis + durationBetweenRotatesMillis)) { + ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); buckets.add(bucket); + buckets.remove(); } lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); - currentBucket = buckets.peek(); } } From 3bc44df1ce95178fd3c647be227827d605e87525 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Tue, 11 Jun 2019 14:52:54 +0200 Subject: [PATCH 03/19] Cleanup code style (naming conventions) [code review] Signed-off-by: Rudolf Rakos --- .../prometheus/client/HdrTimeWindowQuantiles.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java index 9c9743476..f4d3f92e7 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -63,18 +63,17 @@ private ConcurrentDoubleHistogram getCurrentBucket() { private void rotate() { // On concurrent `rotate` and `rotate`: - // - `currentTimeMillis` is cached to reduce thread contention. - // - `lastRotateTimestampMillis` is used to ensure the correct number of rotations. - long currentTimeMillis = System.currentTimeMillis(); - long lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); - while (currentTimeMillis - lastRotateTimestampMillis > durationBetweenRotatesMillis) { - if (this.lastRotateTimestampMillis.compareAndSet( - lastRotateTimestampMillis, lastRotateTimestampMillis + durationBetweenRotatesMillis)) { + // - `currentTime` is cached to reduce thread contention. + // - `lastRotate` is used to ensure the correct number of rotations. + long currentTime = System.currentTimeMillis(); + long lastRotate = lastRotateTimestampMillis.get(); + while (currentTime - lastRotate > durationBetweenRotatesMillis) { + if (lastRotateTimestampMillis.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesMillis)) { ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); buckets.add(bucket); buckets.remove(); } - lastRotateTimestampMillis = this.lastRotateTimestampMillis.get(); + lastRotate = lastRotateTimestampMillis.get(); } } From c73ef77463bc48e96b3fc10f3f23d4eebf716aa3 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 13 Jun 2019 16:27:48 +0200 Subject: [PATCH 04/19] Use System#nanoTime instead of System#currentTimeMillis [code review] Signed-off-by: Rudolf Rakos --- .../benchmark/HdrSummaryBenchmark.java | 4 ++-- .../client/HdrTimeWindowQuantiles.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java index 9fc222d39..69ba3a105 100644 --- a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java +++ b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java @@ -49,7 +49,7 @@ public void setup() { @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void prometheusSimpleHdrSummaryBenchmark() { - prometheusSimpleHdrSummary.labels("test", "group").observe(1) ; + prometheusSimpleHdrSummary.labels("test", "group").observe(1); } @Benchmark @@ -70,7 +70,7 @@ public void prometheusSimpleHdrSummaryNoLabelsBenchmark() { @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void prometheusSimpleHdrSummaryQuantilesBenchmark() { - prometheusSimpleHdrSummaryQuantiles.labels("test", "group").observe(1) ; + prometheusSimpleHdrSummaryQuantiles.labels("test", "group").observe(1); } @Benchmark diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java index f4d3f92e7..aa857aff3 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -15,8 +15,8 @@ class HdrTimeWindowQuantiles { private final int numberOfSignificantValueDigits; private final ConcurrentLinkedQueue buckets; - private final AtomicLong lastRotateTimestampMillis; - private final long durationBetweenRotatesMillis; + private final AtomicLong lastRotateTimestampNanos; + private final long durationBetweenRotatesNanos; public HdrTimeWindowQuantiles(int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; @@ -24,8 +24,8 @@ public HdrTimeWindowQuantiles(int numberOfSignificantValueDigits, long maxAgeSec for (int i = 0; i < ageBuckets; i++) { this.buckets.add(new ConcurrentDoubleHistogram(numberOfSignificantValueDigits)); } - this.lastRotateTimestampMillis = new AtomicLong(System.currentTimeMillis()); - this.durationBetweenRotatesMillis = TimeUnit.SECONDS.toMillis(maxAgeSeconds) / ageBuckets; + this.lastRotateTimestampNanos = new AtomicLong(System.nanoTime()); + this.durationBetweenRotatesNanos = TimeUnit.SECONDS.toNanos(maxAgeSeconds) / ageBuckets; } public double get(double quantile) { @@ -35,19 +35,17 @@ public double get(double quantile) { } public double getMin() { - // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMinValue(); } public double getMax() { - // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMaxValue(); } public void insert(double value) { - // On concurrent `insert` and `rotate`, it might be acceptable to lose the measurement in the newest `bucket`. + // On concurrent `insert` and `rotate`, it should be acceptable to lose the measurement in the newest `bucket`. rotate(); for (ConcurrentDoubleHistogram bucket : buckets) { @@ -65,15 +63,15 @@ private void rotate() { // On concurrent `rotate` and `rotate`: // - `currentTime` is cached to reduce thread contention. // - `lastRotate` is used to ensure the correct number of rotations. - long currentTime = System.currentTimeMillis(); - long lastRotate = lastRotateTimestampMillis.get(); - while (currentTime - lastRotate > durationBetweenRotatesMillis) { - if (lastRotateTimestampMillis.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesMillis)) { + long currentTime = System.nanoTime(); + long lastRotate = lastRotateTimestampNanos.get(); + while (currentTime - lastRotate > durationBetweenRotatesNanos) { + if (lastRotateTimestampNanos.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesNanos)) { ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); buckets.add(bucket); buckets.remove(); } - lastRotate = lastRotateTimestampMillis.get(); + lastRotate = lastRotateTimestampNanos.get(); } } From 2f67d5a90786c2ba5b54744e850450a1c0fc2b7b Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Mon, 17 Jun 2019 18:06:15 +0200 Subject: [PATCH 05/19] Experiment with AtomicReference instead of ConcurrentLinkedQueue Signed-off-by: Rudolf Rakos --- .../client/HdrTimeWindowQuantiles.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java index aa857aff3..6ee211dc9 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -2,9 +2,9 @@ import org.HdrHistogram.ConcurrentDoubleHistogram; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; /** * Wrapper around HdrHistogram. @@ -14,16 +14,17 @@ class HdrTimeWindowQuantiles { private final int numberOfSignificantValueDigits; - private final ConcurrentLinkedQueue buckets; + private final AtomicReference buckets; private final AtomicLong lastRotateTimestampNanos; private final long durationBetweenRotatesNanos; public HdrTimeWindowQuantiles(int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; - this.buckets = new ConcurrentLinkedQueue(); + ConcurrentDoubleHistogram[] emptyBuckets = new ConcurrentDoubleHistogram[ageBuckets]; for (int i = 0; i < ageBuckets; i++) { - this.buckets.add(new ConcurrentDoubleHistogram(numberOfSignificantValueDigits)); + emptyBuckets[i] = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); } + this.buckets = new AtomicReference(emptyBuckets); this.lastRotateTimestampNanos = new AtomicLong(System.nanoTime()); this.durationBetweenRotatesNanos = TimeUnit.SECONDS.toNanos(maxAgeSeconds) / ageBuckets; } @@ -48,7 +49,7 @@ public void insert(double value) { // On concurrent `insert` and `rotate`, it should be acceptable to lose the measurement in the newest `bucket`. rotate(); - for (ConcurrentDoubleHistogram bucket : buckets) { + for (ConcurrentDoubleHistogram bucket : buckets.get()) { bucket.recordValue(value); } } @@ -56,20 +57,31 @@ public void insert(double value) { private ConcurrentDoubleHistogram getCurrentBucket() { rotate(); - return buckets.peek(); + return buckets.get()[0]; // oldest bucket } private void rotate() { // On concurrent `rotate` and `rotate`: // - `currentTime` is cached to reduce thread contention. // - `lastRotate` is used to ensure the correct number of rotations. + + // Correctness is guaranteed by `volatile` memory access ordering and visibility semantics. + // Note that it is not possible for other threads to read partially initialized `buckets`. + // In other words the `volatile` write to `buckets` propagates preceding `plain` writes to `buckets[i]`. long currentTime = System.nanoTime(); long lastRotate = lastRotateTimestampNanos.get(); while (currentTime - lastRotate > durationBetweenRotatesNanos) { if (lastRotateTimestampNanos.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesNanos)) { - ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); - buckets.add(bucket); - buckets.remove(); + // rotate buckets (atomic) + ConcurrentDoubleHistogram[] oldBuckets = buckets.get(); + int ageBuckets = oldBuckets.length; + ConcurrentDoubleHistogram[] newBuckets = new ConcurrentDoubleHistogram[ageBuckets]; + newBuckets[ageBuckets - 1] = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); // newest bucket + System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets + while (!buckets.compareAndSet(oldBuckets, newBuckets)) { + oldBuckets = buckets.get(); + System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets + } } lastRotate = lastRotateTimestampNanos.get(); } From e232066fadae4ecf328d85455dfe55934dd827a2 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Wed, 3 Jul 2019 12:57:10 +0200 Subject: [PATCH 06/19] Increase initial dynamic range and make it configurable Signed-off-by: Rudolf Rakos --- .../java/io/prometheus/client/HdrSummary.java | 17 ++++++++++++++--- .../client/HdrTimeWindowQuantiles.java | 15 ++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java index 2f37c8dbd..964826329 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java @@ -76,6 +76,7 @@ public class HdrSummary extends SimpleCollector implements Counter.Describable { private final List quantiles; // Can be empty, but can never be null. + private final long highestToLowestValueRatio; private final int numberOfSignificantValueDigits; private final long maxAgeSeconds; private final int ageBuckets; @@ -83,6 +84,7 @@ public class HdrSummary extends SimpleCollector implements Cou private HdrSummary(Builder b) { super(b); this.quantiles = Collections.unmodifiableList(new ArrayList(b.quantiles)); + this.highestToLowestValueRatio = b.highestToLowestValueRatio; this.numberOfSignificantValueDigits = b.numberOfSignificantValueDigits; this.maxAgeSeconds = b.maxAgeSeconds; this.ageBuckets = b.ageBuckets; @@ -92,6 +94,7 @@ private HdrSummary(Builder b) { public static class Builder extends SimpleCollector.Builder { private final List quantiles = new ArrayList(); + private long highestToLowestValueRatio = 1000; private int numberOfSignificantValueDigits = 3; private long maxAgeSeconds = TimeUnit.MINUTES.toSeconds(10); private int ageBuckets = 5; @@ -104,6 +107,14 @@ public Builder quantile(double quantile) { return this; } + public Builder highestToLowestValueRatio(long highestToLowestValueRatio) { + if (highestToLowestValueRatio < 2) { + throw new IllegalArgumentException("highestToLowestValueRatio cannot be " + highestToLowestValueRatio); + } + this.highestToLowestValueRatio = highestToLowestValueRatio; + return this; + } + public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { if (numberOfSignificantValueDigits < 0 || numberOfSignificantValueDigits > 5) { throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits); @@ -160,7 +171,7 @@ public static Builder build() { @Override protected Child newChild() { - return new Child(quantiles, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); + return new Child(quantiles, highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); } /** @@ -241,9 +252,9 @@ private SortedMap snapshot(List quantiles, HdrTimeWindow private final List quantiles; private final HdrTimeWindowQuantiles quantileValues; - private Child(List quantiles, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + private Child(List quantiles, long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { this.quantiles = quantiles; - this.quantileValues = quantiles.isEmpty() ? null : new HdrTimeWindowQuantiles(numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); + this.quantileValues = quantiles.isEmpty() ? null : new HdrTimeWindowQuantiles(highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); } /** diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java index 6ee211dc9..c6945c1d1 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java @@ -13,22 +13,31 @@ */ class HdrTimeWindowQuantiles { + private final long highestToLowestValueRatio; private final int numberOfSignificantValueDigits; private final AtomicReference buckets; private final AtomicLong lastRotateTimestampNanos; private final long durationBetweenRotatesNanos; - public HdrTimeWindowQuantiles(int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + public HdrTimeWindowQuantiles(long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + this.highestToLowestValueRatio = highestToLowestValueRatio; this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; ConcurrentDoubleHistogram[] emptyBuckets = new ConcurrentDoubleHistogram[ageBuckets]; for (int i = 0; i < ageBuckets; i++) { - emptyBuckets[i] = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); + emptyBuckets[i] = createBucket(); } this.buckets = new AtomicReference(emptyBuckets); this.lastRotateTimestampNanos = new AtomicLong(System.nanoTime()); this.durationBetweenRotatesNanos = TimeUnit.SECONDS.toNanos(maxAgeSeconds) / ageBuckets; } + private ConcurrentDoubleHistogram createBucket() { + ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(highestToLowestValueRatio, numberOfSignificantValueDigits); + bucket.setAutoResize(true); + + return bucket; + } + public double get(double quantile) { // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); @@ -76,7 +85,7 @@ private void rotate() { ConcurrentDoubleHistogram[] oldBuckets = buckets.get(); int ageBuckets = oldBuckets.length; ConcurrentDoubleHistogram[] newBuckets = new ConcurrentDoubleHistogram[ageBuckets]; - newBuckets[ageBuckets - 1] = new ConcurrentDoubleHistogram(numberOfSignificantValueDigits); // newest bucket + newBuckets[ageBuckets - 1] = createBucket(); // newest bucket System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets while (!buckets.compareAndSet(oldBuckets, newBuckets)) { oldBuckets = buckets.get(); From 48af822c92eea8cf90c2048887fbc907be0b3318 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Wed, 31 Jul 2019 19:41:59 +0200 Subject: [PATCH 07/19] Decrease default number of significant value digits Signed-off-by: Rudolf Rakos --- .../benchmark/HdrSummaryBenchmark.java | 18 +- .../java/io/prometheus/client/HdrSummary.java | 2 +- .../io/prometheus/client/HdrSummaryTest.java | 210 ++++++++++++------ 3 files changed, 154 insertions(+), 76 deletions(-) diff --git a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java index 69ba3a105..b49c276fc 100644 --- a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java +++ b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java @@ -21,26 +21,24 @@ public class HdrSummaryBenchmark { @Setup public void setup() { prometheusSimpleHdrSummary = io.prometheus.client.HdrSummary.build() - .name("name") - .help("some description..") - .labelNames("some", "group").create(); + .name("name").help("some description..") + .labelNames("some", "group") + .create(); prometheusSimpleHdrSummaryChild = prometheusSimpleHdrSummary.labels("test", "group"); prometheusSimpleHdrSummaryNoLabels = io.prometheus.client.HdrSummary.build() - .name("name") - .help("some description..") + .name("name").help("some description..") .create(); prometheusSimpleHdrSummaryQuantiles = io.prometheus.client.HdrSummary.build() - .name("name") - .help("some description..") + .name("name").help("some description..") + .labelNames("some", "group") .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) - .labelNames("some", "group").create(); + .create(); prometheusSimpleHdrSummaryQuantilesChild = prometheusSimpleHdrSummaryQuantiles.labels("test", "group"); prometheusSimpleHdrSummaryQuantilesNoLabels = io.prometheus.client.HdrSummary.build() - .name("name") - .help("some description..") + .name("name").help("some description..") .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) .create(); } diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java index 964826329..3af28894e 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java @@ -95,7 +95,7 @@ public static class Builder extends SimpleCollector.Builder private final List quantiles = new ArrayList(); private long highestToLowestValueRatio = 1000; - private int numberOfSignificantValueDigits = 3; + private int numberOfSignificantValueDigits = 2; private long maxAgeSeconds = TimeUnit.MINUTES.toSeconds(10); private int ageBuckets = 5; diff --git a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java index a8ec86153..17f0421f0 100644 --- a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java +++ b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java @@ -17,22 +17,29 @@ public class HdrSummaryTest { private CollectorRegistry registry; + private HdrSummary noLabels, labels, noLabelsAndQuantiles, labelsAndQuantiles; @Before public void setUp() { registry = new CollectorRegistry(); + noLabels = HdrSummary.build() - .name("nolabels").help("help").register(registry); + .name("no_labels").help("help") + .register(registry); labels = HdrSummary.build() - .labelNames("l") - .name("labels").help("help").register(registry); + .name("labels").help("help") + .labelNames("l") + .register(registry); noLabelsAndQuantiles = HdrSummary.build() - .quantile(0.5).quantile(0.9).quantile(0.99) - .name("no_labels_and_quantiles").help("help").register(registry); + .name("no_labels_and_quantiles").help("help") + .quantile(0.5).quantile(0.9).quantile(0.99) + .register(registry); labelsAndQuantiles = HdrSummary.build() - .labelNames("l").quantile(0.5).quantile(0.9).quantile(0.99) - .name("labels_and_quantiles").help("help").register(registry); + .name("labels_and_quantiles").help("help") + .labelNames("l") + .quantile(0.5).quantile(0.9).quantile(0.99) + .register(registry); } @After @@ -40,88 +47,139 @@ public void tearDown() { SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider(); } - private double getCount() { - return registry.getSampleValue("nolabels_count"); + private Double getCount() { + return registry.getSampleValue("no_labels_count"); } - private double getSum() { - return registry.getSampleValue("nolabels_sum"); + private Double getSum() { + return registry.getSampleValue("no_labels_sum"); } - private double getNoLabelQuantile(double q) { - return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}); + private Double getMin() { + return registry.getSampleValue("no_labels_min"); } - private double getLabeledQuantile(double q) { - return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{"a", Collector.doubleToGoString(q)}); + private Double getMax() { + return registry.getSampleValue("no_labels_max"); } - private Double getLabelsCount(String labelValue) { + private Double getCount(String labelValue) { return registry.getSampleValue("labels_count", new String[]{"l"}, new String[]{labelValue}); } - private Double getLabelsSum(String labelValue) { + private Double getSum(String labelValue) { return registry.getSampleValue("labels_sum", new String[]{"l"}, new String[]{labelValue}); } + private Double getMin(String labelValue) { + return registry.getSampleValue("labels_min", new String[]{"l"}, new String[]{labelValue}); + } + + private Double getMax(String labelValue) { + return registry.getSampleValue("labels_max", new String[]{"l"}, new String[]{labelValue}); + } + + private Double getNoLabelsQuantile(double q) { + return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}); + } + + private Double getLabelsQuantile(double q) { + return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{"a", Collector.doubleToGoString(q)}); + } + @Test public void testObserve() { - noLabels.observe(2); + noLabels.observe(2.0); assertEquals(1.0, getCount(), .001); assertEquals(2.0, getSum(), .001); - assertEquals(1.0, noLabels.get().count, .001); - assertEquals(2.0, noLabels.get().sum, .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); - noLabels.labels().observe(4); + noLabels.labels().observe(4.0); assertEquals(2.0, getCount(), .001); assertEquals(6.0, getSum(), .001); - assertEquals(2.0, noLabels.get().count, .001); - assertEquals(6.0, noLabels.get().sum, .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); } @Test public void testQuantiles() { int nSamples = 1000000; // simulate one million samples + double error = .01; // default `numberOfSignificantValueDigits` is `2` for (int i = 1; i <= nSamples; i++) { // In this test, we observe the numbers from 1 to nSamples, // because that makes it easy to verify if the quantiles are correct. - labelsAndQuantiles.labels("a").observe(i); noLabelsAndQuantiles.observe(i); + labelsAndQuantiles.labels("a").observe(i); } - assertEquals(0.5 * nSamples, getNoLabelQuantile(0.5), 0.05 * nSamples); - assertEquals(0.9 * nSamples, getNoLabelQuantile(0.9), 0.01 * nSamples); - assertEquals(0.99 * nSamples, getNoLabelQuantile(0.99), 0.001 * nSamples); - assertEquals(1.0, noLabelsAndQuantiles.get().min, 0.001 * 1.0); - assertEquals((double) nSamples, noLabelsAndQuantiles.get().max, 0.001 * nSamples); + assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_count"), .001); + assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("no_labels_and_quantiles_sum"), .001); + assertEquals(1.0, registry.getSampleValue("no_labels_and_quantiles_min"), error * 1.0); + assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_max"), error * nSamples); + assertEquals(0.5 * nSamples, getNoLabelsQuantile(0.5), error * nSamples); + assertEquals(0.9 * nSamples, getNoLabelsQuantile(0.9), error * nSamples); + assertEquals(0.99 * nSamples, getNoLabelsQuantile(0.99), error * nSamples); + + assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_count", new String[]{"l"}, new String[]{"a"}), .001); + assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("labels_and_quantiles_sum", new String[]{"l"}, new String[]{"a"}), .001); + assertEquals(1.0, registry.getSampleValue("labels_and_quantiles_min", new String[]{"l"}, new String[]{"a"}), error * 1.0); + assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_max", new String[]{"l"}, new String[]{"a"}), error * nSamples); + assertEquals(0.5 * nSamples, getLabelsQuantile(0.5), error * nSamples); + assertEquals(0.9 * nSamples, getLabelsQuantile(0.9), error * nSamples); + assertEquals(0.99 * nSamples, getLabelsQuantile(0.99), error * nSamples); + } + + @Test + public void testError() { + for (int n = 1; n <= 5; ++n) { + double error = Math.pow(10, -n); + + HdrSummary summary = HdrSummary.build() + .name("test_precision_" + n).help("help") + .quantile(0.99) + .numberOfSignificantValueDigits(n) + .register(registry); + + summary.observe(1.0); + double val1 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1.0, val1, error * 1.0); + + summary.observe(1000.0); + double val2 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000.0, val2, error * 1000.0); - assertEquals(0.5 * nSamples, getLabeledQuantile(0.5), 0.05 * nSamples); - assertEquals(0.9 * nSamples, getLabeledQuantile(0.9), 0.01 * nSamples); - assertEquals(0.99 * nSamples, getLabeledQuantile(0.99), 0.001 * nSamples); - assertEquals(1.0, labelsAndQuantiles.labels("a").get().min, 0.001 * 1.0); - assertEquals((double) nSamples, labelsAndQuantiles.labels("a").get().max, 0.001 * nSamples); + summary.observe(1000000.0); + double val3 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000000.0, val3, error * 1000000.0); + + summary.observe(1000000000.0); + double val4 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000000000.0, val4, error * 1000000000.0); + } } @Test public void testMaxAge() throws InterruptedException { HdrSummary summary = HdrSummary.build() - .quantile(0.99) - .maxAgeSeconds(1) // After 1s, all observations will be discarded. - .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. - .name("short_attention_span").help("help").register(registry); + .name("short_attention_span").help("help") + .quantile(0.99) + .maxAgeSeconds(1) // After 1s, all observations will be discarded. + .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. + .register(registry); summary.observe(8.0); - double val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(8.0, val, 0.0); // From bucket 1. + double val1 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val1, .001); // From bucket 1. Thread.sleep(600); - val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(8.0, val, 0.0); // From bucket 2. + double val2 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val2, .001); // From bucket 2. Thread.sleep(600); - val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(Double.NaN, val, 0.0); // Bucket 1 again, now it is empty. + double val3 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(Double.NaN, val3, .001); // From bucket 1 again, but now it is empty. } @Test @@ -134,13 +192,13 @@ long nanoTime() { } }; - double elapsed = noLabels.time(new Runnable() { + double elapsed1 = noLabels.time(new Runnable() { @Override public void run() { // no op } }); - assertEquals(10, elapsed, .001); + assertEquals(10, elapsed1, .001); int result = noLabels.time(new Callable() { @Override @@ -151,42 +209,64 @@ public Integer call() { assertEquals(123, result); HdrSummary.Timer timer = noLabels.startTimer(); - elapsed = timer.observeDuration(); - assertEquals(10, elapsed, .001); + double elapsed2 = timer.observeDuration(); + assertEquals(10, elapsed2, .001); assertEquals(3, getCount(), .001); assertEquals(30, getSum(), .001); } @Test - public void noLabelsDefaultZeroValue() { + public void testNoLabels() { assertEquals(0.0, getCount(), .001); assertEquals(0.0, getSum(), .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); + + noLabels.observe(2.0); + assertEquals(1.0, getCount(), .001); + assertEquals(2.0, getSum(), .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); } @Test public void testLabels() { - assertEquals(null, getLabelsCount("a")); - assertEquals(null, getLabelsSum("a")); - assertEquals(null, getLabelsCount("b")); - assertEquals(null, getLabelsSum("b")); - - labels.labels("a").observe(2); - assertEquals(1.0, getLabelsCount("a"), .001); - assertEquals(2.0, getLabelsSum("a"), .001); - assertEquals(null, getLabelsCount("b")); - assertEquals(null, getLabelsSum("b")); + assertEquals(null, getCount("a")); + assertEquals(null, getSum("a")); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(null, getCount("b")); + assertEquals(null, getSum("b")); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); + + labels.labels("a").observe(2.0); + assertEquals(1.0, getCount("a"), .001); + assertEquals(2.0, getSum("a"), .001); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(null, getCount("b")); + assertEquals(null, getSum("b")); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); + + + labels.labels("b").observe(3.0); + assertEquals(1.0, getCount("a"), .001); + assertEquals(2.0, getSum("a"), .001); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(1.0, getCount("b"), .001); + assertEquals(3.0, getSum("b"), .001); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); - labels.labels("b").observe(3); - assertEquals(1.0, getLabelsCount("a"), .001); - assertEquals(2.0, getLabelsSum("a"), .001); - assertEquals(1.0, getLabelsCount("b"), .001); - assertEquals(3.0, getLabelsSum("b"), .001); } @Test public void testCollect() { - labels.labels("a").observe(2); + labels.labels("a").observe(2.0); List mfs = labels.collect(); ArrayList samples = new ArrayList(); From 2b9b1a647481873ab49c3b8b08c30e7217ff1a5b Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 17 Oct 2019 18:03:53 +0200 Subject: [PATCH 08/19] Disallow negative measurements (explicitly) Signed-off-by: Rudolf Rakos --- .../src/main/java/io/prometheus/client/HdrSummary.java | 5 +++++ .../test/java/io/prometheus/client/HdrSummaryTest.java | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java index 3af28894e..c67d28507 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java @@ -261,6 +261,11 @@ private Child(List quantiles, long highestToLowestValueRatio, int number * Observe the given amount. */ public void observe(double amt) { + if (amt < 0.0) { + // See DoubleHistogram#autoAdjustRangeForValueSlowPath + throw new IllegalArgumentException("Value " + amt + " invalid: Negative values are not supported by HdrHistogram."); + } + count.add(1); sum.add(amt); if (quantileValues != null) { diff --git a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java index 17f0421f0..5914fd9a3 100644 --- a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java +++ b/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java @@ -131,6 +131,16 @@ public void testQuantiles() { assertEquals(0.99 * nSamples, getLabelsQuantile(0.99), error * nSamples); } + @Test(expected = IllegalArgumentException.class) + public void testObserveNegative() { + noLabels.observe(-2.0); + } + + @Test(expected = IllegalArgumentException.class) + public void testObserveNegativeQuantiles() { + noLabelsAndQuantiles.observe(-2.0); + } + @Test public void testError() { for (int n = 1; n <= 5; ++n) { From f2e1ed4cc3dbd6ecb001265fbea5ee6dd3230040 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 17 Oct 2019 18:13:55 +0200 Subject: [PATCH 09/19] Improve Javadocs Signed-off-by: Rudolf Rakos --- .../java/io/prometheus/client/HdrSummary.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java index c67d28507..de9e28dc4 100644 --- a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java +++ b/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java @@ -14,7 +14,7 @@ *

  • Request size
  • * * - * See http://hdrhistogram.org and https://github.com/HdrHistogram/HdrHistogram for more info on HdrHistogram. + * Note that observing negative measurements are not supported and will cause an {@link IllegalArgumentException}. * *

    * Example Summaries: @@ -69,9 +69,14 @@ *

  • ageBuckets(int): Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) * and how smooth the time window is moved. Default value is 5. + *
  • numberOfSignificantValueDigits(int): Set the precision (significant decimal digits) of the underlying HdrHistogram. + * Default value is 2. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} + *
  • highestToLowestValueRatio(long): Set the initial dynamic range (and memory usage) of the underlying HdrHistogram. + * Default value is 1000. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} * * * See https://prometheus.io/docs/practices/histograms/ for more info on quantiles. + * See http://hdrhistogram.org and https://github.com/HdrHistogram/HdrHistogram for more info on HdrHistogram. */ public class HdrSummary extends SimpleCollector implements Counter.Describable { @@ -109,7 +114,7 @@ public Builder quantile(double quantile) { public Builder highestToLowestValueRatio(long highestToLowestValueRatio) { if (highestToLowestValueRatio < 2) { - throw new IllegalArgumentException("highestToLowestValueRatio cannot be " + highestToLowestValueRatio); + throw new IllegalArgumentException("highestToLowestValueRatio cannot be " + highestToLowestValueRatio + " : Expected at least 2."); } this.highestToLowestValueRatio = highestToLowestValueRatio; return this; @@ -117,7 +122,7 @@ public Builder highestToLowestValueRatio(long highestToLowestValueRatio) { public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { if (numberOfSignificantValueDigits < 0 || numberOfSignificantValueDigits > 5) { - throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits); + throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits + " : Expected number between 0 and 5."); } this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; return this; @@ -125,7 +130,7 @@ public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits public Builder maxAgeSeconds(long maxAgeSeconds) { if (maxAgeSeconds <= 0) { - throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds); + throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds + " : Expected non negative number."); } this.maxAgeSeconds = maxAgeSeconds; return this; @@ -133,7 +138,7 @@ public Builder maxAgeSeconds(long maxAgeSeconds) { public Builder ageBuckets(int ageBuckets) { if (ageBuckets <= 0) { - throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets); + throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets + " : Expected non negative number."); } this.ageBuckets = ageBuckets; return this; @@ -259,6 +264,8 @@ private Child(List quantiles, long highestToLowestValueRatio, int number /** * Observe the given amount. + * + * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { if (amt < 0.0) { @@ -331,6 +338,8 @@ public Value get() { /** * Observe the given amount on the summary with no labels. + * + * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { noLabelsChild.observe(amt); From c1900ddedcccf417c4619f2aac47796ed8d4f9a9 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Tue, 18 Jun 2019 13:11:53 +0200 Subject: [PATCH 10/19] Move `HdrSummary` into `simpleclient` Signed-off-by: Rudolf Rakos --- benchmark/pom.xml | 5 -- pom.xml | 1 - simpleclient/pom.xml | 13 ++++ .../java/io/prometheus/client/HdrSummary.java | 0 .../client/HdrTimeWindowQuantiles.java | 0 .../io/prometheus/client/HdrSummaryTest.java | 0 simpleclient_hdrhistogram/pom.xml | 62 ------------------- 7 files changed, 13 insertions(+), 68 deletions(-) rename {simpleclient_hdrhistogram => simpleclient}/src/main/java/io/prometheus/client/HdrSummary.java (100%) rename {simpleclient_hdrhistogram => simpleclient}/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java (100%) rename {simpleclient_hdrhistogram => simpleclient}/src/test/java/io/prometheus/client/HdrSummaryTest.java (100%) delete mode 100644 simpleclient_hdrhistogram/pom.xml diff --git a/benchmark/pom.xml b/benchmark/pom.xml index bab18ce0f..adbda13af 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -51,11 +51,6 @@ simpleclient 0.8.2-SNAPSHOT - - io.prometheus - simpleclient_hdrhistogram - 0.8.2-SNAPSHOT - com.codahale.metrics metrics-core diff --git a/pom.xml b/pom.xml index b8647dc44..d3f19a302 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,6 @@ simpleclient_graphite_bridge simpleclient_hibernate simpleclient_guava - simpleclient_hdrhistogram simpleclient_hotspot simpleclient_httpserver simpleclient_log4j diff --git a/simpleclient/pom.xml b/simpleclient/pom.xml index 1136898b4..77840335f 100644 --- a/simpleclient/pom.xml +++ b/simpleclient/pom.xml @@ -15,6 +15,7 @@ Prometheus Java Simpleclient Core instrumentation library for the simpleclient. + Collectors using HdrHistogram. @@ -31,6 +32,11 @@ Brian Brazil brian.brazil@boxever.com + + rrakos-evo + Rudolf Rakos + rrakos@evolutiongaming.com + @@ -38,6 +44,13 @@ + + org.hdrhistogram + HdrHistogram + 2.1.12 + bundle + + junit diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient/src/main/java/io/prometheus/client/HdrSummary.java similarity index 100% rename from simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrSummary.java rename to simpleclient/src/main/java/io/prometheus/client/HdrSummary.java diff --git a/simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java similarity index 100% rename from simpleclient_hdrhistogram/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java rename to simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java diff --git a/simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java b/simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java similarity index 100% rename from simpleclient_hdrhistogram/src/test/java/io/prometheus/client/HdrSummaryTest.java rename to simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java diff --git a/simpleclient_hdrhistogram/pom.xml b/simpleclient_hdrhistogram/pom.xml deleted file mode 100644 index 62af9fded..000000000 --- a/simpleclient_hdrhistogram/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - - io.prometheus - parent - 0.8.2-SNAPSHOT - - - io.prometheus - simpleclient_hdrhistogram - bundle - - Prometheus Java Simpleclient HdrHistogram - - Collectors using HdrHistogram. - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - rrakos-evo - Rudolf Rakos - rrakos@evolutiongaming.com - - - brian-brazil - Brian Brazil - brian.brazil@boxever.com - - - - - - io.prometheus - simpleclient - 0.8.2-SNAPSHOT - - - org.hdrhistogram - HdrHistogram - 2.1.12 - bundle - - - - - junit - junit - 4.11 - test - - - From 4ef474d6a0ec99985e1b329931a4de8e47192a30 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 1 Aug 2019 18:44:19 +0200 Subject: [PATCH 11/19] Replace `Summary` with `HdrSummary` Signed-off-by: Rudolf Rakos --- .../benchmark/HdrSummaryBenchmark.java | 101 ----- .../benchmark/SummaryBenchmark.java | 84 ++-- .../io/prometheus/client/CKMSQuantiles.java | 293 ------------- .../java/io/prometheus/client/HdrSummary.java | 413 ------------------ .../client/HdrTimeWindowQuantiles.java | 99 ----- .../java/io/prometheus/client/Summary.java | 265 ++++++----- .../client/TimeWindowQuantiles.java | 107 +++-- .../io/prometheus/client/HdrSummaryTest.java | 326 -------------- .../io/prometheus/client/SummaryTest.java | 266 +++++++---- 9 files changed, 471 insertions(+), 1483 deletions(-) delete mode 100644 benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java delete mode 100644 simpleclient/src/main/java/io/prometheus/client/CKMSQuantiles.java delete mode 100644 simpleclient/src/main/java/io/prometheus/client/HdrSummary.java delete mode 100644 simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java delete mode 100644 simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java diff --git a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java deleted file mode 100644 index b49c276fc..000000000 --- a/benchmark/src/main/java/io/prometheus/benchmark/HdrSummaryBenchmark.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.prometheus.benchmark; - -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -import java.util.concurrent.TimeUnit; - -@State(Scope.Benchmark) -public class HdrSummaryBenchmark { - - io.prometheus.client.HdrSummary prometheusSimpleHdrSummary; - io.prometheus.client.HdrSummary.Child prometheusSimpleHdrSummaryChild; - io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryNoLabels; - io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryQuantiles; - io.prometheus.client.HdrSummary.Child prometheusSimpleHdrSummaryQuantilesChild; - io.prometheus.client.HdrSummary prometheusSimpleHdrSummaryQuantilesNoLabels; - - @Setup - public void setup() { - prometheusSimpleHdrSummary = io.prometheus.client.HdrSummary.build() - .name("name").help("some description..") - .labelNames("some", "group") - .create(); - prometheusSimpleHdrSummaryChild = prometheusSimpleHdrSummary.labels("test", "group"); - - prometheusSimpleHdrSummaryNoLabels = io.prometheus.client.HdrSummary.build() - .name("name").help("some description..") - .create(); - - prometheusSimpleHdrSummaryQuantiles = io.prometheus.client.HdrSummary.build() - .name("name").help("some description..") - .labelNames("some", "group") - .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) - .create(); - prometheusSimpleHdrSummaryQuantilesChild = prometheusSimpleHdrSummaryQuantiles.labels("test", "group"); - - prometheusSimpleHdrSummaryQuantilesNoLabels = io.prometheus.client.HdrSummary.build() - .name("name").help("some description..") - .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) - .create(); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryBenchmark() { - prometheusSimpleHdrSummary.labels("test", "group").observe(1); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryChildBenchmark() { - prometheusSimpleHdrSummaryChild.observe(1); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryNoLabelsBenchmark() { - prometheusSimpleHdrSummaryNoLabels.observe(1); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryQuantilesBenchmark() { - prometheusSimpleHdrSummaryQuantiles.labels("test", "group").observe(1); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryQuantilesChildBenchmark() { - prometheusSimpleHdrSummaryQuantilesChild.observe(1); - } - - @Benchmark - @BenchmarkMode({Mode.AverageTime}) - @OutputTimeUnit(TimeUnit.NANOSECONDS) - public void prometheusSimpleHdrSummaryQuantilesNoLabelsBenchmark() { - prometheusSimpleHdrSummaryQuantilesNoLabels.observe(1); - } - - public static void main(String[] args) throws RunnerException { - - Options opt = new OptionsBuilder() - .include(HdrSummaryBenchmark.class.getSimpleName()) - .warmupIterations(5) - .measurementIterations(4) - .threads(4) - .forks(1) - .build(); - - new Runner(opt).run(); - } - -} diff --git a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java index 3a77dc5ec..652e92ace 100644 --- a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java +++ b/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java @@ -1,20 +1,14 @@ package io.prometheus.benchmark; import com.codahale.metrics.MetricRegistry; - -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import java.util.concurrent.TimeUnit; + @State(Scope.Benchmark) public class SummaryBenchmark { @@ -23,45 +17,53 @@ public class SummaryBenchmark { io.prometheus.client.metrics.Summary prometheusSummary; io.prometheus.client.metrics.Summary.Child prometheusSummaryChild; + io.prometheus.client.Summary prometheusSimpleSummary; io.prometheus.client.Summary.Child prometheusSimpleSummaryChild; io.prometheus.client.Summary prometheusSimpleSummaryNoLabels; + io.prometheus.client.Summary prometheusSimpleSummaryQuantiles; + io.prometheus.client.Summary.Child prometheusSimpleSummaryQuantilesChild; + io.prometheus.client.Summary prometheusSimpleSummaryQuantilesNoLabels; + io.prometheus.client.Histogram prometheusSimpleHistogram; io.prometheus.client.Histogram.Child prometheusSimpleHistogramChild; io.prometheus.client.Histogram prometheusSimpleHistogramNoLabels; @Setup public void setup() { + registry = new MetricRegistry(); + codahaleHistogram = registry.histogram("name"); + prometheusSummary = io.prometheus.client.metrics.Summary.newBuilder() - .name("name") - .documentation("some description..") + .name("name").documentation("some description..") .build(); prometheusSummaryChild = prometheusSummary.newPartial().apply(); - prometheusSimpleSummary = io.prometheus.client.Summary.build() - .name("name") - .help("some description..") - .labelNames("some", "group").create(); - prometheusSimpleSummaryChild = prometheusSimpleSummary.labels("test", "group"); - prometheusSimpleSummaryNoLabels = io.prometheus.client.Summary.build() - .name("name") - .help("some description..") + .name("name").help("some description..") + .create(); + + prometheusSimpleSummaryQuantiles = io.prometheus.client.Summary.build() + .name("name").help("some description..") + .labelNames("some", "group") + .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) + .create(); + prometheusSimpleSummaryQuantilesChild = prometheusSimpleSummaryQuantiles.labels("test", "group"); + + prometheusSimpleSummaryQuantilesNoLabels = io.prometheus.client.Summary.build() + .name("name").help("some description..") + .quantile(0.5).quantile(0.9).quantile(0.95).quantile(0.99) .create(); prometheusSimpleHistogram = io.prometheus.client.Histogram.build() - .name("name") - .help("some description..") - .labelNames("some", "group").create(); + .name("name").help("some description..") + .labelNames("some", "group") + .create(); prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels("test", "group"); prometheusSimpleHistogramNoLabels = io.prometheus.client.Histogram.build() - .name("name") - .help("some description..") + .name("name").help("some description..") .create(); - - registry = new MetricRegistry(); - codahaleHistogram = registry.histogram("name"); } @Benchmark @@ -82,21 +84,42 @@ public void prometheusSummaryChildBenchmark() { @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void prometheusSimpleSummaryBenchmark() { - prometheusSimpleSummary.labels("test", "group").observe(1) ; + prometheusSimpleSummary.labels("test", "group").observe(1); } @Benchmark @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void prometheusSimpleSummaryChildBenchmark() { - prometheusSimpleSummaryChild.observe(1); + prometheusSimpleSummaryChild.observe(1); } @Benchmark @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void prometheusSimpleSummaryNoLabelsBenchmark() { - prometheusSimpleSummaryNoLabels.observe(1); + prometheusSimpleSummaryNoLabels.observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleSummaryQuantilesBenchmark() { + prometheusSimpleSummaryQuantiles.labels("test", "group").observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleSummaryQuantilesChildBenchmark() { + prometheusSimpleSummaryQuantilesChild.observe(1); + } + + @Benchmark + @BenchmarkMode({Mode.AverageTime}) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void prometheusSimpleSummaryQuantilesNoLabelsBenchmark() { + prometheusSimpleSummaryQuantilesNoLabels.observe(1); } @Benchmark @@ -139,4 +162,5 @@ public static void main(String[] args) throws RunnerException { new Runner(opt).run(); } + } diff --git a/simpleclient/src/main/java/io/prometheus/client/CKMSQuantiles.java b/simpleclient/src/main/java/io/prometheus/client/CKMSQuantiles.java deleted file mode 100644 index 1ffb65382..000000000 --- a/simpleclient/src/main/java/io/prometheus/client/CKMSQuantiles.java +++ /dev/null @@ -1,293 +0,0 @@ -package io.prometheus.client; - -// Copied from https://raw.githubusercontent.com/Netflix/ocelli/master/ocelli-core/src/main/java/netflix/ocelli/stats/CKMSQuantiles.java -// Revision d0357b8bf5c17a173ce94d6b26823775b3f999f6 from Jan 21, 2015. -// -// This is the original code except for the following modifications: -// -// - Changed the type of the observed values from int to double. -// - Removed the Quantiles interface and corresponding @Override annotations. -// - Changed the package name. -// - Make get() return NaN when no sample was observed. -// - Make class package private - -/* - Copyright 2012 Andrew Wang (andrew@umbrant.com) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.ListIterator; - -/** - * Implementation of the Cormode, Korn, Muthukrishnan, and Srivastava algorithm - * for streaming calculation of targeted high-percentile epsilon-approximate - * quantiles. - * - * This is a generalization of the earlier work by Greenwald and Khanna (GK), - * which essentially allows different error bounds on the targeted quantiles, - * which allows for far more efficient calculation of high-percentiles. - * - * - * See: Cormode, Korn, Muthukrishnan, and Srivastava - * "Effective Computation of Biased Quantiles over Data Streams" in ICDE 2005 - * - * Greenwald and Khanna, - * "Space-efficient online computation of quantile summaries" in SIGMOD 2001 - * - */ -class CKMSQuantiles { - /** - * Total number of items in stream. - */ - private int count = 0; - - /** - * Used for tracking incremental compression. - */ - private int compressIdx = 0; - - /** - * Current list of sampled items, maintained in sorted order with error - * bounds. - */ - protected LinkedList sample; - - /** - * Buffers incoming items to be inserted in batch. - */ - private double[] buffer = new double[500]; - - private int bufferCount = 0; - - /** - * Array of Quantiles that we care about, along with desired error. - */ - private final Quantile quantiles[]; - - public CKMSQuantiles(Quantile[] quantiles) { - this.quantiles = quantiles; - this.sample = new LinkedList(); - } - - /** - * Add a new value from the stream. - * - * @param value - */ - public void insert(double value) { - buffer[bufferCount] = value; - bufferCount++; - - if (bufferCount == buffer.length) { - insertBatch(); - compress(); - } - } - - /** - * Get the estimated value at the specified quantile. - * - * @param q - * Queried quantile, e.g. 0.50 or 0.99. - * @return Estimated value at that quantile. - */ - public double get(double q) { - // clear the buffer - insertBatch(); - compress(); - - if (sample.size() == 0) { - return Double.NaN; - } - - int rankMin = 0; - int desired = (int) (q * count); - - ListIterator it = sample.listIterator(); - Item prev, cur; - cur = it.next(); - while (it.hasNext()) { - prev = cur; - cur = it.next(); - - rankMin += prev.g; - - if (rankMin + cur.g + cur.delta > desired - + (allowableError(desired) / 2)) { - return prev.value; - } - } - - // edge case of wanting max value - return sample.getLast().value; - } - - /** - * Specifies the allowable error for this rank, depending on which quantiles - * are being targeted. - * - * This is the f(r_i, n) function from the CKMS paper. It's basically how - * wide the range of this rank can be. - * - * @param rank - * the index in the list of samples - */ - private double allowableError(int rank) { - // NOTE: according to CKMS, this should be count, not size, but this - // leads - // to error larger than the error bounds. Leaving it like this is - // essentially a HACK, and blows up memory, but does "work". - // int size = count; - int size = sample.size(); - double minError = size + 1; - - for (Quantile q : quantiles) { - double error; - if (rank <= q.quantile * size) { - error = q.u * (size - rank); - } else { - error = q.v * rank; - } - if (error < minError) { - minError = error; - } - } - - return minError; - } - - private boolean insertBatch() { - if (bufferCount == 0) { - return false; - } - - Arrays.sort(buffer, 0, bufferCount); - - // Base case: no samples - int start = 0; - if (sample.size() == 0) { - Item newItem = new Item(buffer[0], 1, 0); - sample.add(newItem); - start++; - count++; - } - - ListIterator it = sample.listIterator(); - Item item = it.next(); - - for (int i = start; i < bufferCount; i++) { - double v = buffer[i]; - while (it.nextIndex() < sample.size() && item.value < v) { - item = it.next(); - } - - // If we found that bigger item, back up so we insert ourselves - // before it - if (item.value > v) { - it.previous(); - } - - // We use different indexes for the edge comparisons, because of the - // above - // if statement that adjusts the iterator - int delta; - if (it.previousIndex() == 0 || it.nextIndex() == sample.size()) { - delta = 0; - } - else { - delta = ((int) Math.floor(allowableError(it.nextIndex()))) - 1; - } - - Item newItem = new Item(v, 1, delta); - it.add(newItem); - count++; - item = newItem; - } - - bufferCount = 0; - return true; - } - - /** - * Try to remove extraneous items from the set of sampled items. This checks - * if an item is unnecessary based on the desired error bounds, and merges - * it with the adjacent item if it is. - */ - private void compress() { - if (sample.size() < 2) { - return; - } - - ListIterator it = sample.listIterator(); - int removed = 0; - - Item prev = null; - Item next = it.next(); - - while (it.hasNext()) { - prev = next; - next = it.next(); - - if (prev.g + next.g + next.delta <= allowableError(it.previousIndex())) { - next.g += prev.g; - // Remove prev. it.remove() kills the last thing returned. - it.previous(); - it.previous(); - it.remove(); - // it.next() is now equal to next, skip it back forward again - it.next(); - removed++; - } - } - } - - private class Item { - public final double value; - public int g; - public final int delta; - - public Item(double value, int lower_delta, int delta) { - this.value = value; - this.g = lower_delta; - this.delta = delta; - } - - @Override - public String toString() { - return String.format("I{val=%.3f, g=%d, del=%d}", value, g, delta); - } - } - - public static class Quantile { - public final double quantile; - public final double error; - public final double u; - public final double v; - - public Quantile(double quantile, double error) { - this.quantile = quantile; - this.error = error; - u = 2.0 * error / (1.0 - quantile); - v = 2.0 * error / quantile; - } - - @Override - public String toString() { - return String.format("Q{q=%.3f, eps=%.3f}", quantile, error); - } - } - -} diff --git a/simpleclient/src/main/java/io/prometheus/client/HdrSummary.java b/simpleclient/src/main/java/io/prometheus/client/HdrSummary.java deleted file mode 100644 index de9e28dc4..000000000 --- a/simpleclient/src/main/java/io/prometheus/client/HdrSummary.java +++ /dev/null @@ -1,413 +0,0 @@ -package io.prometheus.client; - -import java.io.Closeable; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -/** - * Summary metric using HdrHistogram, to track the size of events. - *

    - * Example of uses for Summaries include: - *

      - *
    • Response latency
    • - *
    • Request size
    • - *
    - * - * Note that observing negative measurements are not supported and will cause an {@link IllegalArgumentException}. - * - *

    - * Example Summaries: - *

    - * {@code
    - *   class YourClass {
    - *     static final HdrSummary receivedBytes = HdrSummary.build()
    - *         .name("requests_size_bytes").help("Request size in bytes.").register();
    - *     static final HdrSummary requestLatency = HdrSummary.build()
    - *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
    - *
    - *     void processRequest(Request req) {
    - *       HdrSummary.Timer requestTimer = requestLatency.startTimer();
    - *       try {
    - *         // Your code here.
    - *       } finally {
    - *         receivedBytes.observe(req.size());
    - *         requestTimer.observeDuration();
    - *       }
    - *     }
    - *
    - *     // Or if using Java 8 and lambdas.
    - *     void processRequestLambda(Request req) {
    - *       receivedBytes.observe(req.size());
    - *       requestLatency.time(() -> {
    - *         // Your code here.
    - *       });
    - *     }
    - *   }
    - * }
    - * 
    - * This would allow you to track request rate, average latency and average request size. - * - *

    - * How to add custom quantiles: - *

    - * {@code
    - *   static final HdrSummary myMetric = HdrSummary.build()
    - *       .quantile(0.5)  // Add 50th percentile (= median)
    - *       .quantile(0.9)  // Add 90th percentile
    - *       .quantile(0.99) // Add 99th percentile
    - *       .name("requests_size_bytes")
    - *       .help("Request size in bytes.")
    - *       .register();
    - * }
    - * 
    - * - * The quantiles are calculated over a sliding window of time. There are two options to configure this time window: - *
      - *
    • maxAgeSeconds(long): Set the duration of the time window is, i.e. how long observations are kept before they are discarded. - * Default is 10 minutes. - *
    • ageBuckets(int): Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, - * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) - * and how smooth the time window is moved. Default value is 5. - *
    • numberOfSignificantValueDigits(int): Set the precision (significant decimal digits) of the underlying HdrHistogram. - * Default value is 2. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} - *
    • highestToLowestValueRatio(long): Set the initial dynamic range (and memory usage) of the underlying HdrHistogram. - * Default value is 1000. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} - *
    - * - * See https://prometheus.io/docs/practices/histograms/ for more info on quantiles. - * See http://hdrhistogram.org and https://github.com/HdrHistogram/HdrHistogram for more info on HdrHistogram. - */ -public class HdrSummary extends SimpleCollector implements Counter.Describable { - - private final List quantiles; // Can be empty, but can never be null. - private final long highestToLowestValueRatio; - private final int numberOfSignificantValueDigits; - private final long maxAgeSeconds; - private final int ageBuckets; - - private HdrSummary(Builder b) { - super(b); - this.quantiles = Collections.unmodifiableList(new ArrayList(b.quantiles)); - this.highestToLowestValueRatio = b.highestToLowestValueRatio; - this.numberOfSignificantValueDigits = b.numberOfSignificantValueDigits; - this.maxAgeSeconds = b.maxAgeSeconds; - this.ageBuckets = b.ageBuckets; - initializeNoLabelsChild(); - } - - public static class Builder extends SimpleCollector.Builder { - - private final List quantiles = new ArrayList(); - private long highestToLowestValueRatio = 1000; - private int numberOfSignificantValueDigits = 2; - private long maxAgeSeconds = TimeUnit.MINUTES.toSeconds(10); - private int ageBuckets = 5; - - public Builder quantile(double quantile) { - if (quantile < 0.0 || quantile > 1.0) { - throw new IllegalArgumentException("Quantile " + quantile + " invalid: Expected number between 0.0 and 1.0."); - } - quantiles.add(quantile); - return this; - } - - public Builder highestToLowestValueRatio(long highestToLowestValueRatio) { - if (highestToLowestValueRatio < 2) { - throw new IllegalArgumentException("highestToLowestValueRatio cannot be " + highestToLowestValueRatio + " : Expected at least 2."); - } - this.highestToLowestValueRatio = highestToLowestValueRatio; - return this; - } - - public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { - if (numberOfSignificantValueDigits < 0 || numberOfSignificantValueDigits > 5) { - throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits + " : Expected number between 0 and 5."); - } - this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; - return this; - } - - public Builder maxAgeSeconds(long maxAgeSeconds) { - if (maxAgeSeconds <= 0) { - throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds + " : Expected non negative number."); - } - this.maxAgeSeconds = maxAgeSeconds; - return this; - } - - public Builder ageBuckets(int ageBuckets) { - if (ageBuckets <= 0) { - throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets + " : Expected non negative number."); - } - this.ageBuckets = ageBuckets; - return this; - } - - @Override - public HdrSummary create() { - for (String label : labelNames) { - if (label.equals("quantile")) { - throw new IllegalStateException("Summary cannot have a label named 'quantile'."); - } - } - dontInitializeNoLabelsChild = true; - return new HdrSummary(this); - } - - } - - /** - * Return a Builder to allow configuration of a new HdrSummary. Ensures required fields are provided. - * - * @param name The name of the metric - * @param help The help string of the metric - */ - public static Builder build(String name, String help) { - return new Builder().name(name).help(help); - } - - /** - * Return a Builder to allow configuration of a new HdrSummary. - */ - public static Builder build() { - return new Builder(); - } - - @Override - protected Child newChild() { - return new Child(quantiles, highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); - } - - /** - * Represents an event being timed. - */ - public static class Timer implements Closeable { - - private final Child child; - private final long start; - - private Timer(Child child) { - this.child = child; - this.start = SimpleTimer.defaultTimeProvider.nanoTime(); - } - - /** - * Observe the amount of time in seconds since {@link Child#startTimer} was called. - * - * @return Measured duration in seconds since {@link Child#startTimer} was called. - */ - public double observeDuration() { - long end = SimpleTimer.defaultTimeProvider.nanoTime(); - double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, end); - child.observe(elapsed); - return elapsed; - } - - /** - * Equivalent to calling {@link #observeDuration()}. - */ - @Override - public void close() { - observeDuration(); - } - - } - - /** - * The value of a single HdrSummary. - *

    - * Warning: References to a Child become invalid after using - * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. - */ - public static class Child { - - public static class Value { - - public final double count; - public final double sum; - public final double min; - public final double max; - public final SortedMap quantiles; - - private Value(DoubleAdder count, DoubleAdder sum, List quantiles, HdrTimeWindowQuantiles quantileValues) { - this.count = count.sum(); - this.sum = sum.sum(); - this.min = quantileValues == null ? Double.NaN : quantileValues.getMin(); - this.max = quantileValues == null ? Double.NaN : quantileValues.getMax(); - this.quantiles = Collections.unmodifiableSortedMap(snapshot(quantiles, quantileValues)); - } - - private SortedMap snapshot(List quantiles, HdrTimeWindowQuantiles quantileValues) { - SortedMap result = new TreeMap(); - for (Double quantile : quantiles) { - result.put(quantile, quantileValues.get(quantile)); - } - return result; - } - - } - - // Having these separate leaves us open to races, - // however Prometheus as whole has other races - // that mean adding atomicity here wouldn't be useful. - // This should be reevaluated in the future. - private final DoubleAdder count = new DoubleAdder(); - private final DoubleAdder sum = new DoubleAdder(); - private final List quantiles; - private final HdrTimeWindowQuantiles quantileValues; - - private Child(List quantiles, long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { - this.quantiles = quantiles; - this.quantileValues = quantiles.isEmpty() ? null : new HdrTimeWindowQuantiles(highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); - } - - /** - * Observe the given amount. - * - * @throws IllegalArgumentException If amt is negative. - */ - public void observe(double amt) { - if (amt < 0.0) { - // See DoubleHistogram#autoAdjustRangeForValueSlowPath - throw new IllegalArgumentException("Value " + amt + " invalid: Negative values are not supported by HdrHistogram."); - } - - count.add(1); - sum.add(amt); - if (quantileValues != null) { - quantileValues.insert(amt); - } - } - - /** - * Start a timer to track a duration. - *

    - * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. - */ - public Timer startTimer() { - return new Timer(this); - } - - /** - * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Measured duration in seconds for timeable to complete. - */ - public double time(Runnable timeable) { - Timer timer = startTimer(); - double elapsed; - try { - timeable.run(); - } finally { - elapsed = timer.observeDuration(); - } - return elapsed; - } - - /** - * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Result returned by callable. - */ - public E time(Callable timeable) { - Timer timer = startTimer(); - try { - return timeable.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - timer.observeDuration(); - } - } - - /** - * Get the value of the Summary. - *

    - * Warning: The definition of {@link Value} is subject to change. - */ - public Value get() { - return new Value(count, sum, quantiles, quantileValues); - } - - } - - // Convenience methods. - - /** - * Observe the given amount on the summary with no labels. - * - * @throws IllegalArgumentException If amt is negative. - */ - public void observe(double amt) { - noLabelsChild.observe(amt); - } - - /** - * Start a timer to track a duration on the summary with no labels. - *

    - * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. - */ - public Timer startTimer() { - return noLabelsChild.startTimer(); - } - - /** - * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Measured duration in seconds for timeable to complete. - */ - public double time(Runnable timeable) { - return noLabelsChild.time(timeable); - } - - /** - * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Result returned by callable. - */ - public E time(Callable timeable) { - return noLabelsChild.time(timeable); - } - - /** - * Get the value of the HdrSummary. - *

    - * Warning: The definition of {@link Child.Value} is subject to change. - */ - public Child.Value get() { - return noLabelsChild.get(); - } - - @Override - public List collect() { - List samples = new ArrayList(); - for (Map.Entry, Child> child : children.entrySet()) { - Child.Value value = child.getValue().get(); - List labelNamesWithQuantile = new ArrayList(labelNames); - labelNamesWithQuantile.add("quantile"); - for (Map.Entry quantile : value.quantiles.entrySet()) { - List labelValuesWithQuantile = new ArrayList(child.getKey()); - labelValuesWithQuantile.add(doubleToGoString(quantile.getKey())); - samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithQuantile, labelValuesWithQuantile, quantile.getValue())); - } - if (!value.quantiles.isEmpty()) { - samples.add(new MetricFamilySamples.Sample(fullname + "_min", labelNames, child.getKey(), value.min)); - samples.add(new MetricFamilySamples.Sample(fullname + "_max", labelNames, child.getKey(), value.max)); - } - samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, child.getKey(), value.count)); - samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, child.getKey(), value.sum)); - } - return familySamplesList(Type.SUMMARY, samples); - } - - @Override - public List describe() { - return Collections.singletonList(new SummaryMetricFamily(fullname, help, labelNames)); - } - -} diff --git a/simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java b/simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java deleted file mode 100644 index c6945c1d1..000000000 --- a/simpleclient/src/main/java/io/prometheus/client/HdrTimeWindowQuantiles.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.prometheus.client; - -import org.HdrHistogram.ConcurrentDoubleHistogram; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Wrapper around HdrHistogram. - *

    - * Maintains a ring buffer of HdrHistogram to provide quantiles over a sliding windows of time. - */ -class HdrTimeWindowQuantiles { - - private final long highestToLowestValueRatio; - private final int numberOfSignificantValueDigits; - private final AtomicReference buckets; - private final AtomicLong lastRotateTimestampNanos; - private final long durationBetweenRotatesNanos; - - public HdrTimeWindowQuantiles(long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { - this.highestToLowestValueRatio = highestToLowestValueRatio; - this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; - ConcurrentDoubleHistogram[] emptyBuckets = new ConcurrentDoubleHistogram[ageBuckets]; - for (int i = 0; i < ageBuckets; i++) { - emptyBuckets[i] = createBucket(); - } - this.buckets = new AtomicReference(emptyBuckets); - this.lastRotateTimestampNanos = new AtomicLong(System.nanoTime()); - this.durationBetweenRotatesNanos = TimeUnit.SECONDS.toNanos(maxAgeSeconds) / ageBuckets; - } - - private ConcurrentDoubleHistogram createBucket() { - ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(highestToLowestValueRatio, numberOfSignificantValueDigits); - bucket.setAutoResize(true); - - return bucket; - } - - public double get(double quantile) { - // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. - ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); - return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getValueAtPercentile(quantile * 100.0); - } - - public double getMin() { - ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); - return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMinValue(); - } - - public double getMax() { - ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); - return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMaxValue(); - } - - public void insert(double value) { - // On concurrent `insert` and `rotate`, it should be acceptable to lose the measurement in the newest `bucket`. - rotate(); - - for (ConcurrentDoubleHistogram bucket : buckets.get()) { - bucket.recordValue(value); - } - } - - private ConcurrentDoubleHistogram getCurrentBucket() { - rotate(); - - return buckets.get()[0]; // oldest bucket - } - - private void rotate() { - // On concurrent `rotate` and `rotate`: - // - `currentTime` is cached to reduce thread contention. - // - `lastRotate` is used to ensure the correct number of rotations. - - // Correctness is guaranteed by `volatile` memory access ordering and visibility semantics. - // Note that it is not possible for other threads to read partially initialized `buckets`. - // In other words the `volatile` write to `buckets` propagates preceding `plain` writes to `buckets[i]`. - long currentTime = System.nanoTime(); - long lastRotate = lastRotateTimestampNanos.get(); - while (currentTime - lastRotate > durationBetweenRotatesNanos) { - if (lastRotateTimestampNanos.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesNanos)) { - // rotate buckets (atomic) - ConcurrentDoubleHistogram[] oldBuckets = buckets.get(); - int ageBuckets = oldBuckets.length; - ConcurrentDoubleHistogram[] newBuckets = new ConcurrentDoubleHistogram[ageBuckets]; - newBuckets[ageBuckets - 1] = createBucket(); // newest bucket - System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets - while (!buckets.compareAndSet(oldBuckets, newBuckets)) { - oldBuckets = buckets.get(); - System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets - } - } - lastRotate = lastRotateTimestampNanos.get(); - } - } - -} diff --git a/simpleclient/src/main/java/io/prometheus/client/Summary.java b/simpleclient/src/main/java/io/prometheus/client/Summary.java index 4d79e558a..d90a8c023 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Summary.java +++ b/simpleclient/src/main/java/io/prometheus/client/Summary.java @@ -1,19 +1,12 @@ package io.prometheus.client; -import io.prometheus.client.CKMSQuantiles.Quantile; - import java.io.Closeable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; /** - * Summary metric, to track the size of events. + * Summary metric using HdrHistogram, to track the size of events. *

    * Example of uses for Summaries include: *

      @@ -21,6 +14,8 @@ *
    • Request size
    • *
    * + * Note that observing negative measurements are not supported and will cause an {@link IllegalArgumentException}. + * *

    * Example Summaries: *

    @@ -32,13 +27,13 @@
      *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
      *
      *     void processRequest(Request req) {
    - *        Summary.Timer requestTimer = requestLatency.startTimer();
    - *        try {
    - *          // Your code here.
    - *        } finally {
    - *          receivedBytes.observe(req.size());
    - *          requestTimer.observeDuration();
    - *        }
    + *       Summary.Timer requestTimer = requestLatency.startTimer();
    + *       try {
    + *         // Your code here.
    + *       } finally {
    + *         receivedBytes.observe(req.size());
    + *         requestTimer.observeDuration();
    + *       }
      *     }
      *
      *     // Or if using Java 8 and lambdas.
    @@ -48,7 +43,7 @@
      *         // Your code here.
      *       });
      *     }
    - * }
    + *   }
      * }
      * 
    * This would allow you to track request rate, average latency and average request size. @@ -57,13 +52,13 @@ * How to add custom quantiles: *
      * {@code
    - *     static final Summary myMetric = Summary.build()
    - *             .quantile(0.5, 0.05)   // Add 50th percentile (= median) with 5% tolerated error
    - *             .quantile(0.9, 0.01)   // Add 90th percentile with 1% tolerated error
    - *             .quantile(0.99, 0.001) // Add 99th percentile with 0.1% tolerated error
    - *             .name("requests_size_bytes")
    - *             .help("Request size in bytes.")
    - *             .register();
    + *   static final Summary myMetric = Summary.build()
    + *       .quantile(0.5)  // Add 50th percentile (= median)
    + *       .quantile(0.9)  // Add 90th percentile
    + *       .quantile(0.99) // Add 99th percentile
    + *       .name("requests_size_bytes")
    + *       .help("Request size in bytes.")
    + *       .register();
      * }
      * 
    * @@ -74,19 +69,28 @@ *
  • ageBuckets(int): Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) * and how smooth the time window is moved. Default value is 5. + *
  • numberOfSignificantValueDigits(int): Set the precision (significant decimal digits) of the underlying HdrHistogram. + * Default value is 2. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} + *
  • highestToLowestValueRatio(long): Set the initial dynamic range (and memory usage) of the underlying HdrHistogram. + * Default value is 1000. See {@link org.HdrHistogram.ConcurrentDoubleHistogram} * * * See https://prometheus.io/docs/practices/histograms/ for more info on quantiles. + * See http://hdrhistogram.org and https://github.com/HdrHistogram/HdrHistogram for more info on HdrHistogram. */ public class Summary extends SimpleCollector implements Counter.Describable { - final List quantiles; // Can be empty, but can never be null. - final long maxAgeSeconds; - final int ageBuckets; + private final List quantiles; // Can be empty, but can never be null. + private final long highestToLowestValueRatio; + private final int numberOfSignificantValueDigits; + private final long maxAgeSeconds; + private final int ageBuckets; - Summary(Builder b) { + private Summary(Builder b) { super(b); - quantiles = Collections.unmodifiableList(new ArrayList(b.quantiles)); + this.quantiles = Collections.unmodifiableList(new ArrayList(b.quantiles)); + this.highestToLowestValueRatio = b.highestToLowestValueRatio; + this.numberOfSignificantValueDigits = b.numberOfSignificantValueDigits; this.maxAgeSeconds = b.maxAgeSeconds; this.ageBuckets = b.ageBuckets; initializeNoLabelsChild(); @@ -94,24 +98,46 @@ public class Summary extends SimpleCollector implements Counter.D public static class Builder extends SimpleCollector.Builder { - private final List quantiles = new ArrayList(); + private final List quantiles = new ArrayList(); + private long highestToLowestValueRatio = 1000; + private int numberOfSignificantValueDigits = 2; private long maxAgeSeconds = TimeUnit.MINUTES.toSeconds(10); private int ageBuckets = 5; - public Builder quantile(double quantile, double error) { + public Builder quantile(double quantile) { if (quantile < 0.0 || quantile > 1.0) { throw new IllegalArgumentException("Quantile " + quantile + " invalid: Expected number between 0.0 and 1.0."); } - if (error < 0.0 || error > 1.0) { - throw new IllegalArgumentException("Error " + error + " invalid: Expected number between 0.0 and 1.0."); + quantiles.add(quantile); + return this; + } + + // backwards compatibility + public Builder quantile(double quantile, double error) { + this.quantile(quantile); + this.numberOfSignificantValueDigits(Math.max(this.numberOfSignificantValueDigits, (int)-Math.log10(error))); + return this; + } + + public Builder highestToLowestValueRatio(long highestToLowestValueRatio) { + if (highestToLowestValueRatio < 2) { + throw new IllegalArgumentException("highestToLowestValueRatio cannot be " + highestToLowestValueRatio + " : Expected at least 2."); } - quantiles.add(new Quantile(quantile, error)); + this.highestToLowestValueRatio = highestToLowestValueRatio; + return this; + } + + public Builder numberOfSignificantValueDigits(int numberOfSignificantValueDigits) { + if (numberOfSignificantValueDigits < 0 || numberOfSignificantValueDigits > 5) { + throw new IllegalArgumentException("numberOfSignificantValueDigits cannot be " + numberOfSignificantValueDigits + " : Expected number between 0 and 5."); + } + this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; return this; } public Builder maxAgeSeconds(long maxAgeSeconds) { if (maxAgeSeconds <= 0) { - throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds); + throw new IllegalArgumentException("maxAgeSeconds cannot be " + maxAgeSeconds + " : Expected non negative number."); } this.maxAgeSeconds = maxAgeSeconds; return this; @@ -119,7 +145,7 @@ public Builder maxAgeSeconds(long maxAgeSeconds) { public Builder ageBuckets(int ageBuckets) { if (ageBuckets <= 0) { - throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets); + throw new IllegalArgumentException("ageBuckets cannot be " + ageBuckets + " : Expected non negative number."); } this.ageBuckets = ageBuckets; return this; @@ -135,20 +161,21 @@ public Summary create() { dontInitializeNoLabelsChild = true; return new Summary(this); } + } /** - * Return a Builder to allow configuration of a new Summary. Ensures required fields are provided. + * Return a Builder to allow configuration of a new Summary. Ensures required fields are provided. * - * @param name The name of the metric - * @param help The help string of the metric + * @param name The name of the metric + * @param help The help string of the metric */ public static Builder build(String name, String help) { return new Builder().name(name).help(help); } /** - * Return a Builder to allow configuration of a new Summary. + * Return a Builder to allow configuration of a new Summary. */ public static Builder build() { return new Builder(); @@ -156,26 +183,30 @@ public static Builder build() { @Override protected Child newChild() { - return new Child(quantiles, maxAgeSeconds, ageBuckets); + return new Child(quantiles, highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); } - /** * Represents an event being timed. */ public static class Timer implements Closeable { + private final Child child; private final long start; - private Timer(Child child, long start) { + + private Timer(Child child) { this.child = child; - this.start = start; + this.start = SimpleTimer.defaultTimeProvider.nanoTime(); } + /** * Observe the amount of time in seconds since {@link Child#startTimer} was called. + * * @return Measured duration in seconds since {@link Child#startTimer} was called. */ public double observeDuration() { - double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, SimpleTimer.defaultTimeProvider.nanoTime()); + long end = SimpleTimer.defaultTimeProvider.nanoTime(); + double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, end); child.observe(elapsed); return elapsed; } @@ -187,6 +218,7 @@ public double observeDuration() { public void close() { observeDuration(); } + } /** @@ -197,62 +229,30 @@ public void close() { */ public static class Child { - /** - * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Measured duration in seconds for timeable to complete. - */ - public double time(Runnable timeable) { - Timer timer = startTimer(); - - double elapsed; - try { - timeable.run(); - } finally { - elapsed = timer.observeDuration(); - } - return elapsed; - } - - /** - * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. - * - * @param timeable Code that is being timed - * @return Result returned by callable. - */ - public E time(Callable timeable) { - Timer timer = startTimer(); - - try { - return timeable.call(); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - timer.observeDuration(); - } - } - public static class Value { + public final double count; public final double sum; + public final double min; + public final double max; public final SortedMap quantiles; - private Value(double count, double sum, List quantiles, TimeWindowQuantiles quantileValues) { - this.count = count; - this.sum = sum; + private Value(DoubleAdder count, DoubleAdder sum, List quantiles, TimeWindowQuantiles quantileValues) { + this.count = count.sum(); + this.sum = sum.sum(); + this.min = quantileValues == null ? Double.NaN : quantileValues.getMin(); + this.max = quantileValues == null ? Double.NaN : quantileValues.getMax(); this.quantiles = Collections.unmodifiableSortedMap(snapshot(quantiles, quantileValues)); } - private SortedMap snapshot(List quantiles, TimeWindowQuantiles quantileValues) { + private SortedMap snapshot(List quantiles, TimeWindowQuantiles quantileValues) { SortedMap result = new TreeMap(); - for (Quantile q : quantiles) { - result.put(q.quantile, quantileValues.get(q.quantile)); + for (Double quantile : quantiles) { + result.put(quantile, quantileValues.get(quantile)); } return result; } + } // Having these separate leaves us open to races, @@ -261,53 +261,97 @@ private SortedMap snapshot(List quantiles, TimeWindowQ // This should be reevaluated in the future. private final DoubleAdder count = new DoubleAdder(); private final DoubleAdder sum = new DoubleAdder(); - private final List quantiles; + private final List quantiles; private final TimeWindowQuantiles quantileValues; - private Child(List quantiles, long maxAgeSeconds, int ageBuckets) { + private Child(List quantiles, long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { this.quantiles = quantiles; - if (quantiles.size() > 0) { - quantileValues = new TimeWindowQuantiles(quantiles.toArray(new Quantile[]{}), maxAgeSeconds, ageBuckets); - } else { - quantileValues = null; - } + this.quantileValues = quantiles.isEmpty() ? null : new TimeWindowQuantiles(highestToLowestValueRatio, numberOfSignificantValueDigits, maxAgeSeconds, ageBuckets); } /** * Observe the given amount. + * + * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { + if (amt < 0.0) { + // See DoubleHistogram#autoAdjustRangeForValueSlowPath + throw new IllegalArgumentException("Value " + amt + " invalid: Negative values are not supported by HdrHistogram."); + } + count.add(1); sum.add(amt); if (quantileValues != null) { quantileValues.insert(amt); } } + /** * Start a timer to track a duration. *

    * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. */ public Timer startTimer() { - return new Timer(this, SimpleTimer.defaultTimeProvider.nanoTime()); + return new Timer(this); } + + /** + * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Measured duration in seconds for timeable to complete. + */ + public double time(Runnable timeable) { + Timer timer = startTimer(); + double elapsed; + try { + timeable.run(); + } finally { + elapsed = timer.observeDuration(); + } + return elapsed; + } + + /** + * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. + * + * @param timeable Code that is being timed + * @return Result returned by callable. + */ + public E time(Callable timeable) { + Timer timer = startTimer(); + try { + return timeable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + timer.observeDuration(); + } + } + /** * Get the value of the Summary. *

    * Warning: The definition of {@link Value} is subject to change. */ public Value get() { - return new Value(count.sum(), sum.sum(), quantiles, quantileValues); + return new Value(count, sum, quantiles, quantileValues); } + } // Convenience methods. + /** * Observe the given amount on the summary with no labels. + * + * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { noLabelsChild.observe(amt); } + /** * Start a timer to track a duration on the summary with no labels. *

    @@ -323,7 +367,7 @@ public Timer startTimer() { * @param timeable Code that is being timed * @return Measured duration in seconds for timeable to complete. */ - public double time(Runnable timeable){ + public double time(Runnable timeable) { return noLabelsChild.time(timeable); } @@ -333,7 +377,7 @@ public double time(Runnable timeable){ * @param timeable Code that is being timed * @return Result returned by callable. */ - public E time(Callable timeable){ + public E time(Callable timeable) { return noLabelsChild.time(timeable); } @@ -349,19 +393,22 @@ public Child.Value get() { @Override public List collect() { List samples = new ArrayList(); - for(Map.Entry, Child> c: children.entrySet()) { - Child.Value v = c.getValue().get(); + for (Map.Entry, Child> child : children.entrySet()) { + Child.Value value = child.getValue().get(); List labelNamesWithQuantile = new ArrayList(labelNames); labelNamesWithQuantile.add("quantile"); - for(Map.Entry q : v.quantiles.entrySet()) { - List labelValuesWithQuantile = new ArrayList(c.getKey()); - labelValuesWithQuantile.add(doubleToGoString(q.getKey())); - samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithQuantile, labelValuesWithQuantile, q.getValue())); + for (Map.Entry quantile : value.quantiles.entrySet()) { + List labelValuesWithQuantile = new ArrayList(child.getKey()); + labelValuesWithQuantile.add(doubleToGoString(quantile.getKey())); + samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithQuantile, labelValuesWithQuantile, quantile.getValue())); + } + if (!value.quantiles.isEmpty()) { + samples.add(new MetricFamilySamples.Sample(fullname + "_min", labelNames, child.getKey(), value.min)); + samples.add(new MetricFamilySamples.Sample(fullname + "_max", labelNames, child.getKey(), value.max)); } - samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.count)); - samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); + samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, child.getKey(), value.count)); + samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, child.getKey(), value.sum)); } - return familySamplesList(Type.SUMMARY, samples); } diff --git a/simpleclient/src/main/java/io/prometheus/client/TimeWindowQuantiles.java b/simpleclient/src/main/java/io/prometheus/client/TimeWindowQuantiles.java index cc60bc39b..a2a32e214 100644 --- a/simpleclient/src/main/java/io/prometheus/client/TimeWindowQuantiles.java +++ b/simpleclient/src/main/java/io/prometheus/client/TimeWindowQuantiles.java @@ -1,54 +1,99 @@ package io.prometheus.client; -import io.prometheus.client.CKMSQuantiles.Quantile; +import org.HdrHistogram.ConcurrentDoubleHistogram; + import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; /** - * Wrapper around CKMSQuantiles. - * - * Maintains a ring buffer of CKMSQuantiles to provide quantiles over a sliding windows of time. + * Wrapper around HdrHistogram. + *

    + * Maintains a ring buffer of HdrHistogram to provide quantiles over a sliding windows of time. */ class TimeWindowQuantiles { - private final Quantile[] quantiles; - private final CKMSQuantiles[] ringBuffer; - private int currentBucket; - private long lastRotateTimestampMillis; - private final long durationBetweenRotatesMillis; + private final long highestToLowestValueRatio; + private final int numberOfSignificantValueDigits; + private final AtomicReference buckets; + private final AtomicLong lastRotateTimestampNanos; + private final long durationBetweenRotatesNanos; - public TimeWindowQuantiles(Quantile[] quantiles, long maxAgeSeconds, int ageBuckets) { - this.quantiles = quantiles; - this.ringBuffer = new CKMSQuantiles[ageBuckets]; + public TimeWindowQuantiles(long highestToLowestValueRatio, int numberOfSignificantValueDigits, long maxAgeSeconds, int ageBuckets) { + this.highestToLowestValueRatio = highestToLowestValueRatio; + this.numberOfSignificantValueDigits = numberOfSignificantValueDigits; + ConcurrentDoubleHistogram[] emptyBuckets = new ConcurrentDoubleHistogram[ageBuckets]; for (int i = 0; i < ageBuckets; i++) { - this.ringBuffer[i] = new CKMSQuantiles(quantiles); + emptyBuckets[i] = createBucket(); } - this.currentBucket = 0; - this.lastRotateTimestampMillis = System.currentTimeMillis(); - this.durationBetweenRotatesMillis = TimeUnit.SECONDS.toMillis(maxAgeSeconds) / ageBuckets; + this.buckets = new AtomicReference(emptyBuckets); + this.lastRotateTimestampNanos = new AtomicLong(System.nanoTime()); + this.durationBetweenRotatesNanos = TimeUnit.SECONDS.toNanos(maxAgeSeconds) / ageBuckets; + } + + private ConcurrentDoubleHistogram createBucket() { + ConcurrentDoubleHistogram bucket = new ConcurrentDoubleHistogram(highestToLowestValueRatio, numberOfSignificantValueDigits); + bucket.setAutoResize(true); + + return bucket; + } + + public double get(double quantile) { + // On concurrent `get` and `rotate`, it is acceptable to `get` the sample from an outdated `bucket`. + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getValueAtPercentile(quantile * 100.0); + } + + public double getMin() { + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMinValue(); } - public synchronized double get(double q) { - CKMSQuantiles currentBucket = rotate(); - return currentBucket.get(q); + public double getMax() { + ConcurrentDoubleHistogram currentBucket = getCurrentBucket(); + return currentBucket.getTotalCount() == 0 ? Double.NaN : currentBucket.getMaxValue(); } - public synchronized void insert(double value) { + public void insert(double value) { + // On concurrent `insert` and `rotate`, it should be acceptable to lose the measurement in the newest `bucket`. rotate(); - for (CKMSQuantiles ckmsQuantiles : ringBuffer) { - ckmsQuantiles.insert(value); + + for (ConcurrentDoubleHistogram bucket : buckets.get()) { + bucket.recordValue(value); } } - private CKMSQuantiles rotate() { - long timeSinceLastRotateMillis = System.currentTimeMillis() - lastRotateTimestampMillis; - while (timeSinceLastRotateMillis > durationBetweenRotatesMillis) { - ringBuffer[currentBucket] = new CKMSQuantiles(quantiles); - if (++currentBucket >= ringBuffer.length) { - currentBucket = 0; + private ConcurrentDoubleHistogram getCurrentBucket() { + rotate(); + + return buckets.get()[0]; // oldest bucket + } + + private void rotate() { + // On concurrent `rotate` and `rotate`: + // - `currentTime` is cached to reduce thread contention. + // - `lastRotate` is used to ensure the correct number of rotations. + + // Correctness is guaranteed by `volatile` memory access ordering and visibility semantics. + // Note that it is not possible for other threads to read partially initialized `buckets`. + // In other words the `volatile` write to `buckets` propagates preceding `plain` writes to `buckets[i]`. + long currentTime = System.nanoTime(); + long lastRotate = lastRotateTimestampNanos.get(); + while (currentTime - lastRotate > durationBetweenRotatesNanos) { + if (lastRotateTimestampNanos.compareAndSet(lastRotate, lastRotate + durationBetweenRotatesNanos)) { + // rotate buckets (atomic) + ConcurrentDoubleHistogram[] oldBuckets = buckets.get(); + int ageBuckets = oldBuckets.length; + ConcurrentDoubleHistogram[] newBuckets = new ConcurrentDoubleHistogram[ageBuckets]; + newBuckets[ageBuckets - 1] = createBucket(); // newest bucket + System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets + while (!buckets.compareAndSet(oldBuckets, newBuckets)) { + oldBuckets = buckets.get(); + System.arraycopy(oldBuckets, 1, newBuckets, 0, ageBuckets - 1); // older buckets + } } - timeSinceLastRotateMillis -= durationBetweenRotatesMillis; - lastRotateTimestampMillis += durationBetweenRotatesMillis; + lastRotate = lastRotateTimestampNanos.get(); } - return ringBuffer[currentBucket]; } + } diff --git a/simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java b/simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java deleted file mode 100644 index 5914fd9a3..000000000 --- a/simpleclient/src/test/java/io/prometheus/client/HdrSummaryTest.java +++ /dev/null @@ -1,326 +0,0 @@ -package io.prometheus.client; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; - -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class HdrSummaryTest { - - private CollectorRegistry registry; - - private HdrSummary noLabels, labels, noLabelsAndQuantiles, labelsAndQuantiles; - - @Before - public void setUp() { - registry = new CollectorRegistry(); - - noLabels = HdrSummary.build() - .name("no_labels").help("help") - .register(registry); - labels = HdrSummary.build() - .name("labels").help("help") - .labelNames("l") - .register(registry); - noLabelsAndQuantiles = HdrSummary.build() - .name("no_labels_and_quantiles").help("help") - .quantile(0.5).quantile(0.9).quantile(0.99) - .register(registry); - labelsAndQuantiles = HdrSummary.build() - .name("labels_and_quantiles").help("help") - .labelNames("l") - .quantile(0.5).quantile(0.9).quantile(0.99) - .register(registry); - } - - @After - public void tearDown() { - SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider(); - } - - private Double getCount() { - return registry.getSampleValue("no_labels_count"); - } - - private Double getSum() { - return registry.getSampleValue("no_labels_sum"); - } - - private Double getMin() { - return registry.getSampleValue("no_labels_min"); - } - - private Double getMax() { - return registry.getSampleValue("no_labels_max"); - } - - private Double getCount(String labelValue) { - return registry.getSampleValue("labels_count", new String[]{"l"}, new String[]{labelValue}); - } - - private Double getSum(String labelValue) { - return registry.getSampleValue("labels_sum", new String[]{"l"}, new String[]{labelValue}); - } - - private Double getMin(String labelValue) { - return registry.getSampleValue("labels_min", new String[]{"l"}, new String[]{labelValue}); - } - - private Double getMax(String labelValue) { - return registry.getSampleValue("labels_max", new String[]{"l"}, new String[]{labelValue}); - } - - private Double getNoLabelsQuantile(double q) { - return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}); - } - - private Double getLabelsQuantile(double q) { - return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{"a", Collector.doubleToGoString(q)}); - } - - @Test - public void testObserve() { - noLabels.observe(2.0); - assertEquals(1.0, getCount(), .001); - assertEquals(2.0, getSum(), .001); - assertEquals(null, getMin()); - assertEquals(null, getMax()); - - noLabels.labels().observe(4.0); - assertEquals(2.0, getCount(), .001); - assertEquals(6.0, getSum(), .001); - assertEquals(null, getMin()); - assertEquals(null, getMax()); - } - - @Test - public void testQuantiles() { - int nSamples = 1000000; // simulate one million samples - double error = .01; // default `numberOfSignificantValueDigits` is `2` - - for (int i = 1; i <= nSamples; i++) { - // In this test, we observe the numbers from 1 to nSamples, - // because that makes it easy to verify if the quantiles are correct. - noLabelsAndQuantiles.observe(i); - labelsAndQuantiles.labels("a").observe(i); - } - - assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_count"), .001); - assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("no_labels_and_quantiles_sum"), .001); - assertEquals(1.0, registry.getSampleValue("no_labels_and_quantiles_min"), error * 1.0); - assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_max"), error * nSamples); - assertEquals(0.5 * nSamples, getNoLabelsQuantile(0.5), error * nSamples); - assertEquals(0.9 * nSamples, getNoLabelsQuantile(0.9), error * nSamples); - assertEquals(0.99 * nSamples, getNoLabelsQuantile(0.99), error * nSamples); - - assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_count", new String[]{"l"}, new String[]{"a"}), .001); - assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("labels_and_quantiles_sum", new String[]{"l"}, new String[]{"a"}), .001); - assertEquals(1.0, registry.getSampleValue("labels_and_quantiles_min", new String[]{"l"}, new String[]{"a"}), error * 1.0); - assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_max", new String[]{"l"}, new String[]{"a"}), error * nSamples); - assertEquals(0.5 * nSamples, getLabelsQuantile(0.5), error * nSamples); - assertEquals(0.9 * nSamples, getLabelsQuantile(0.9), error * nSamples); - assertEquals(0.99 * nSamples, getLabelsQuantile(0.99), error * nSamples); - } - - @Test(expected = IllegalArgumentException.class) - public void testObserveNegative() { - noLabels.observe(-2.0); - } - - @Test(expected = IllegalArgumentException.class) - public void testObserveNegativeQuantiles() { - noLabelsAndQuantiles.observe(-2.0); - } - - @Test - public void testError() { - for (int n = 1; n <= 5; ++n) { - double error = Math.pow(10, -n); - - HdrSummary summary = HdrSummary.build() - .name("test_precision_" + n).help("help") - .quantile(0.99) - .numberOfSignificantValueDigits(n) - .register(registry); - - summary.observe(1.0); - double val1 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(1.0, val1, error * 1.0); - - summary.observe(1000.0); - double val2 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(1000.0, val2, error * 1000.0); - - summary.observe(1000000.0); - double val3 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(1000000.0, val3, error * 1000000.0); - - summary.observe(1000000000.0); - double val4 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(1000000000.0, val4, error * 1000000000.0); - } - } - - @Test - public void testMaxAge() throws InterruptedException { - HdrSummary summary = HdrSummary.build() - .name("short_attention_span").help("help") - .quantile(0.99) - .maxAgeSeconds(1) // After 1s, all observations will be discarded. - .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. - .register(registry); - - summary.observe(8.0); - double val1 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(8.0, val1, .001); // From bucket 1. - - Thread.sleep(600); - double val2 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(8.0, val2, .001); // From bucket 2. - - Thread.sleep(600); - double val3 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); - assertEquals(Double.NaN, val3, .001); // From bucket 1 again, but now it is empty. - } - - @Test - public void testTimer() { - SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider() { - long value = (long) (30 * 1e9); - long nanoTime() { - value += (long) (10 * 1e9); - return value; - } - }; - - double elapsed1 = noLabels.time(new Runnable() { - @Override - public void run() { - // no op - } - }); - assertEquals(10, elapsed1, .001); - - int result = noLabels.time(new Callable() { - @Override - public Integer call() { - return 123; - } - }); - assertEquals(123, result); - - HdrSummary.Timer timer = noLabels.startTimer(); - double elapsed2 = timer.observeDuration(); - assertEquals(10, elapsed2, .001); - - assertEquals(3, getCount(), .001); - assertEquals(30, getSum(), .001); - } - - @Test - public void testNoLabels() { - assertEquals(0.0, getCount(), .001); - assertEquals(0.0, getSum(), .001); - assertEquals(null, getMin()); - assertEquals(null, getMax()); - - noLabels.observe(2.0); - assertEquals(1.0, getCount(), .001); - assertEquals(2.0, getSum(), .001); - assertEquals(null, getMin()); - assertEquals(null, getMax()); - } - - @Test - public void testLabels() { - assertEquals(null, getCount("a")); - assertEquals(null, getSum("a")); - assertEquals(null, getMin("a")); - assertEquals(null, getMax("a")); - assertEquals(null, getCount("b")); - assertEquals(null, getSum("b")); - assertEquals(null, getMin("b")); - assertEquals(null, getMax("b")); - - labels.labels("a").observe(2.0); - assertEquals(1.0, getCount("a"), .001); - assertEquals(2.0, getSum("a"), .001); - assertEquals(null, getMin("a")); - assertEquals(null, getMax("a")); - assertEquals(null, getCount("b")); - assertEquals(null, getSum("b")); - assertEquals(null, getMin("b")); - assertEquals(null, getMax("b")); - - - labels.labels("b").observe(3.0); - assertEquals(1.0, getCount("a"), .001); - assertEquals(2.0, getSum("a"), .001); - assertEquals(null, getMin("a")); - assertEquals(null, getMax("a")); - assertEquals(1.0, getCount("b"), .001); - assertEquals(3.0, getSum("b"), .001); - assertEquals(null, getMin("b")); - assertEquals(null, getMax("b")); - - } - - @Test - public void testCollect() { - labels.labels("a").observe(2.0); - List mfs = labels.collect(); - - ArrayList samples = new ArrayList(); - samples.add(new Collector.MetricFamilySamples.Sample("labels_count", asList("l"), asList("a"), 1.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", asList("l"), asList("a"), 2.0)); - Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.SUMMARY, "help", samples); - - assertEquals(1, mfs.size()); - assertEquals(mfsFixture, mfs.get(0)); - } - - @Test - public void testCollectWithQuantiles() { - labelsAndQuantiles.labels("a").observe(2); - List mfs = labelsAndQuantiles.collect(); - - ArrayList samples = new ArrayList(); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.5"), 2.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.9"), 2.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.99"), 2.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_min", asList("l"), asList("a"), 2.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_max", asList("l"), asList("a"), 2.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_count", asList("l"), asList("a"), 1.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_sum", asList("l"), asList("a"), 2.0)); - Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_and_quantiles", Collector.Type.SUMMARY, "help", samples); - - assertEquals(1, mfs.size()); - assertEquals(mfsFixture, mfs.get(0)); - } - - @Test - public void testChildAndValuePublicApi() throws Exception { - assertTrue(Modifier.isPublic(HdrSummary.Child.class.getModifiers())); - - final Method getMethod = HdrSummary.Child.class.getMethod("get"); - assertTrue(Modifier.isPublic(getMethod.getModifiers())); - assertEquals(HdrSummary.Child.Value.class, getMethod.getReturnType()); - - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getModifiers())); - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("min").getModifiers())); - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("max").getModifiers())); - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("count").getModifiers())); - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("sum").getModifiers())); - assertTrue(Modifier.isPublic(HdrSummary.Child.Value.class.getField("quantiles").getModifiers())); - } - -} diff --git a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java index 06e1f9d9b..e16e4a0ba 100644 --- a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java @@ -14,28 +14,32 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - public class SummaryTest { - CollectorRegistry registry; - Summary noLabels, labels, labelsAndQuantiles, noLabelsAndQuantiles; + private CollectorRegistry registry; + + private Summary noLabels, labels, noLabelsAndQuantiles, labelsAndQuantiles; @Before public void setUp() { registry = new CollectorRegistry(); - noLabels = Summary.build().name("nolabels").help("help").register(registry); - labels = Summary.build().name("labels").help("help").labelNames("l").register(registry); + + noLabels = Summary.build() + .name("no_labels").help("help") + .register(registry); + labels = Summary.build() + .name("labels").help("help") + .labelNames("l") + .register(registry); noLabelsAndQuantiles = Summary.build() - .quantile(0.5, 0.05) - .quantile(0.9, 0.01) - .quantile(0.99, 0.001) - .name("no_labels_and_quantiles").help("help").register(registry); + .name("no_labels_and_quantiles").help("help") + .quantile(0.5).quantile(0.9).quantile(0.99) + .register(registry); labelsAndQuantiles = Summary.build() - .quantile(0.5, 0.05) - .quantile(0.9, 0.01) - .quantile(0.99, 0.001) - .labelNames("l") - .name("labels_and_quantiles").help("help").register(registry); + .name("labels_and_quantiles").help("help") + .labelNames("l") + .quantile(0.5).quantile(0.9).quantile(0.99) + .register(registry); } @After @@ -43,87 +47,168 @@ public void tearDown() { SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider(); } - private double getCount() { - return registry.getSampleValue("nolabels_count").doubleValue(); + private Double getCount() { + return registry.getSampleValue("no_labels_count"); + } + + private Double getSum() { + return registry.getSampleValue("no_labels_sum"); + } + + private Double getMin() { + return registry.getSampleValue("no_labels_min"); + } + + private Double getMax() { + return registry.getSampleValue("no_labels_max"); + } + + private Double getCount(String labelValue) { + return registry.getSampleValue("labels_count", new String[]{"l"}, new String[]{labelValue}); + } + + private Double getSum(String labelValue) { + return registry.getSampleValue("labels_sum", new String[]{"l"}, new String[]{labelValue}); } - private double getSum() { - return registry.getSampleValue("nolabels_sum").doubleValue(); + + private Double getMin(String labelValue) { + return registry.getSampleValue("labels_min", new String[]{"l"}, new String[]{labelValue}); } - private double getNoLabelQuantile(double q) { - return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}).doubleValue(); + + private Double getMax(String labelValue) { + return registry.getSampleValue("labels_max", new String[]{"l"}, new String[]{labelValue}); } - private double getLabeledQuantile(String labelValue, double q) { - return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{labelValue, Collector.doubleToGoString(q)}).doubleValue(); + + private Double getNoLabelsQuantile(double q) { + return registry.getSampleValue("no_labels_and_quantiles", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(q)}); + } + + private Double getLabelsQuantile(double q) { + return registry.getSampleValue("labels_and_quantiles", new String[]{"l", "quantile"}, new String[]{"a", Collector.doubleToGoString(q)}); } @Test public void testObserve() { - noLabels.observe(2); + noLabels.observe(2.0); assertEquals(1.0, getCount(), .001); assertEquals(2.0, getSum(), .001); - assertEquals(1.0, noLabels.get().count, .001); - assertEquals(2.0, noLabels.get().sum, .001); - noLabels.labels().observe(4); + assertEquals(null, getMin()); + assertEquals(null, getMax()); + + noLabels.labels().observe(4.0); assertEquals(2.0, getCount(), .001); assertEquals(6.0, getSum(), .001); - assertEquals(2.0, noLabels.get().count, .001); - assertEquals(6.0, noLabels.get().sum, .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); } @Test public void testQuantiles() { int nSamples = 1000000; // simulate one million samples + double error = .01; // default `numberOfSignificantValueDigits` is `2` - for (int i=1; i<=nSamples; i++) { + for (int i = 1; i <= nSamples; i++) { // In this test, we observe the numbers from 1 to nSamples, // because that makes it easy to verify if the quantiles are correct. - labelsAndQuantiles.labels("a").observe(i); noLabelsAndQuantiles.observe(i); + labelsAndQuantiles.labels("a").observe(i); } - assertEquals(getNoLabelQuantile(0.5), 0.5 * nSamples, 0.05 * nSamples); - assertEquals(getNoLabelQuantile(0.9), 0.9 * nSamples, 0.01 * nSamples); - assertEquals(getNoLabelQuantile(0.99), 0.99 * nSamples, 0.001 * nSamples); - assertEquals(getLabeledQuantile("a", 0.5), 0.5 * nSamples, 0.05 * nSamples); - assertEquals(getLabeledQuantile("a", 0.9), 0.9 * nSamples, 0.01 * nSamples); - assertEquals(getLabeledQuantile("a", 0.99), 0.99 * nSamples, 0.001 * nSamples); + assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_count"), .001); + assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("no_labels_and_quantiles_sum"), .001); + assertEquals(1.0, registry.getSampleValue("no_labels_and_quantiles_min"), error * 1.0); + assertEquals((double) nSamples, registry.getSampleValue("no_labels_and_quantiles_max"), error * nSamples); + assertEquals(0.5 * nSamples, getNoLabelsQuantile(0.5), error * nSamples); + assertEquals(0.9 * nSamples, getNoLabelsQuantile(0.9), error * nSamples); + assertEquals(0.99 * nSamples, getNoLabelsQuantile(0.99), error * nSamples); + + assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_count", new String[]{"l"}, new String[]{"a"}), .001); + assertEquals((1.0 + nSamples) * nSamples / 2.0, registry.getSampleValue("labels_and_quantiles_sum", new String[]{"l"}, new String[]{"a"}), .001); + assertEquals(1.0, registry.getSampleValue("labels_and_quantiles_min", new String[]{"l"}, new String[]{"a"}), error * 1.0); + assertEquals((double) nSamples, registry.getSampleValue("labels_and_quantiles_max", new String[]{"l"}, new String[]{"a"}), error * nSamples); + assertEquals(0.5 * nSamples, getLabelsQuantile(0.5), error * nSamples); + assertEquals(0.9 * nSamples, getLabelsQuantile(0.9), error * nSamples); + assertEquals(0.99 * nSamples, getLabelsQuantile(0.99), error * nSamples); + } + + @Test(expected = IllegalArgumentException.class) + public void testObserveNegative() { + noLabels.observe(-2.0); + } + + @Test(expected = IllegalArgumentException.class) + public void testObserveNegativeQuantiles() { + noLabelsAndQuantiles.observe(-2.0); + } + + @Test + public void testError() { + for (int n = 1; n <= 5; ++n) { + double error = Math.pow(10, -n); + + Summary summary = Summary.build() + .name("test_precision_" + n).help("help") + .quantile(0.99) + .numberOfSignificantValueDigits(n) + .register(registry); + + summary.observe(1.0); + double val1 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1.0, val1, error * 1.0); + + summary.observe(1000.0); + double val2 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000.0, val2, error * 1000.0); + + summary.observe(1000000.0); + double val3 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000000.0, val3, error * 1000000.0); + + summary.observe(1000000000.0); + double val4 = registry.getSampleValue("test_precision_" + n, new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(1000000000.0, val4, error * 1000000000.0); + } } @Test public void testMaxAge() throws InterruptedException { Summary summary = Summary.build() - .quantile(0.99, 0.001) - .maxAgeSeconds(1) // After 1s, all observations will be discarded. - .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. - .name("short_attention_span").help("help").register(registry); + .name("short_attention_span").help("help") + .quantile(0.99) + .maxAgeSeconds(1) // After 1s, all observations will be discarded. + .ageBuckets(2) // We got 2 buckets, so we discard one bucket every 500ms. + .register(registry); + summary.observe(8.0); - double val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}).doubleValue(); - assertEquals(8.0, val, 0.0); // From bucket 1. + double val1 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val1, .001); // From bucket 1. + Thread.sleep(600); - val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}).doubleValue(); - assertEquals(8.0, val, 0.0); // From bucket 2. + double val2 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(8.0, val2, .001); // From bucket 2. + Thread.sleep(600); - val = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}).doubleValue(); - assertEquals(Double.NaN, val, 0.0); // Bucket 1 again, now it is empty. + double val3 = registry.getSampleValue("short_attention_span", new String[]{"quantile"}, new String[]{Collector.doubleToGoString(0.99)}); + assertEquals(Double.NaN, val3, .001); // From bucket 1 again, but now it is empty. } @Test public void testTimer() { SimpleTimer.defaultTimeProvider = new SimpleTimer.TimeProvider() { - long value = (long)(30 * 1e9); + long value = (long) (30 * 1e9); long nanoTime() { - value += (long)(10 * 1e9); + value += (long) (10 * 1e9); return value; } }; - double elapsed = noLabels.time(new Runnable() { + double elapsed1 = noLabels.time(new Runnable() { @Override public void run() { - //no op + // no op } }); - assertEquals(10, elapsed, .001); + assertEquals(10, elapsed1, .001); int result = noLabels.time(new Callable() { @Override @@ -134,55 +219,69 @@ public Integer call() { assertEquals(123, result); Summary.Timer timer = noLabels.startTimer(); - elapsed = timer.observeDuration(); + double elapsed2 = timer.observeDuration(); + assertEquals(10, elapsed2, .001); + assertEquals(3, getCount(), .001); assertEquals(30, getSum(), .001); - assertEquals(10, elapsed, .001); } @Test - public void noLabelsDefaultZeroValue() { + public void testNoLabels() { assertEquals(0.0, getCount(), .001); assertEquals(0.0, getSum(), .001); - } + assertEquals(null, getMin()); + assertEquals(null, getMax()); - private Double getLabelsCount(String labelValue) { - return registry.getSampleValue("labels_count", new String[]{"l"}, new String[]{labelValue}); - } - private Double getLabelsSum(String labelValue) { - return registry.getSampleValue("labels_sum", new String[]{"l"}, new String[]{labelValue}); + noLabels.observe(2.0); + assertEquals(1.0, getCount(), .001); + assertEquals(2.0, getSum(), .001); + assertEquals(null, getMin()); + assertEquals(null, getMax()); } @Test public void testLabels() { - assertEquals(null, getLabelsCount("a")); - assertEquals(null, getLabelsSum("a")); - assertEquals(null, getLabelsCount("b")); - assertEquals(null, getLabelsSum("b")); - labels.labels("a").observe(2); - assertEquals(1.0, getLabelsCount("a").doubleValue(), .001); - assertEquals(2.0, getLabelsSum("a").doubleValue(), .001); - assertEquals(null, getLabelsCount("b")); - assertEquals(null, getLabelsSum("b")); - labels.labels("b").observe(3); - assertEquals(1.0, getLabelsCount("a").doubleValue(), .001); - assertEquals(2.0, getLabelsSum("a").doubleValue(), .001); - assertEquals(1.0, getLabelsCount("b").doubleValue(), .001); - assertEquals(3.0, getLabelsSum("b").doubleValue(), .001); + assertEquals(null, getCount("a")); + assertEquals(null, getSum("a")); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(null, getCount("b")); + assertEquals(null, getSum("b")); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); + + labels.labels("a").observe(2.0); + assertEquals(1.0, getCount("a"), .001); + assertEquals(2.0, getSum("a"), .001); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(null, getCount("b")); + assertEquals(null, getSum("b")); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); + + + labels.labels("b").observe(3.0); + assertEquals(1.0, getCount("a"), .001); + assertEquals(2.0, getSum("a"), .001); + assertEquals(null, getMin("a")); + assertEquals(null, getMax("a")); + assertEquals(1.0, getCount("b"), .001); + assertEquals(3.0, getSum("b"), .001); + assertEquals(null, getMin("b")); + assertEquals(null, getMax("b")); + } @Test public void testCollect() { - labels.labels("a").observe(2); + labels.labels("a").observe(2.0); List mfs = labels.collect(); ArrayList samples = new ArrayList(); - ArrayList labelNames = new ArrayList(); - labelNames.add("l"); - ArrayList labelValues = new ArrayList(); - labelValues.add("a"); - samples.add(new Collector.MetricFamilySamples.Sample("labels_count", labelNames, labelValues, 1.0)); - samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", labelNames, labelValues, 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_count", asList("l"), asList("a"), 1.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", asList("l"), asList("a"), 2.0)); Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.SUMMARY, "help", samples); assertEquals(1, mfs.size()); @@ -198,6 +297,8 @@ public void testCollectWithQuantiles() { samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.5"), 2.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.9"), 2.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.99"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_min", asList("l"), asList("a"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_max", asList("l"), asList("a"), 2.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_count", asList("l"), asList("a"), 1.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_sum", asList("l"), asList("a"), 2.0)); Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_and_quantiles", Collector.Type.SUMMARY, "help", samples); @@ -215,8 +316,11 @@ public void testChildAndValuePublicApi() throws Exception { assertEquals(Summary.Child.Value.class, getMethod.getReturnType()); assertTrue(Modifier.isPublic(Summary.Child.Value.class.getModifiers())); + assertTrue(Modifier.isPublic(Summary.Child.Value.class.getField("min").getModifiers())); + assertTrue(Modifier.isPublic(Summary.Child.Value.class.getField("max").getModifiers())); assertTrue(Modifier.isPublic(Summary.Child.Value.class.getField("count").getModifiers())); assertTrue(Modifier.isPublic(Summary.Child.Value.class.getField("sum").getModifiers())); assertTrue(Modifier.isPublic(Summary.Child.Value.class.getField("quantiles").getModifiers())); } + } From 2f097b15b29402f1bce572f5638b49fdfa4e0054 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Wed, 16 Oct 2019 18:32:59 +0200 Subject: [PATCH 12/19] Fix unit tests of `common` module Signed-off-by: Rudolf Rakos --- .../io/prometheus/client/exporter/common/TextFormatTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java index 67a01b38e..5c1527ba6 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java @@ -104,6 +104,8 @@ public void testSummaryOutputWithQuantiles() throws IOException { + "labelsAndQuantiles{l=\"a\",quantile=\"0.5\",} 2.0\n" + "labelsAndQuantiles{l=\"a\",quantile=\"0.9\",} 2.0\n" + "labelsAndQuantiles{l=\"a\",quantile=\"0.99\",} 2.0\n" + + "labelsAndQuantiles_min{l=\"a\",} 2.0\n" + + "labelsAndQuantiles_max{l=\"a\",} 2.0\n" + "labelsAndQuantiles_count{l=\"a\",} 1.0\n" + "labelsAndQuantiles_sum{l=\"a\",} 2.0\n", writer.toString()); } From e9faa2a84a9f6d4fdb568301ba94ba248fec6e01 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Fri, 18 Oct 2019 12:55:38 +0200 Subject: [PATCH 13/19] Shade `HdrHistogram` dependency into `simpleclient` artifact Signed-off-by: Rudolf Rakos --- simpleclient/pom.xml | 46 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/simpleclient/pom.xml b/simpleclient/pom.xml index 77840335f..15c0bce2d 100644 --- a/simpleclient/pom.xml +++ b/simpleclient/pom.xml @@ -39,11 +39,55 @@ + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + + + org.hdrhistogram:HdrHistogram + + + + + org.HdrHistogram + io.prometheus.shaded.hdrhistogram + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + to worry about conflicts. See maven-shade-plugin above. --> org.hdrhistogram HdrHistogram From c211c9089767ea4eb75da80e1d7d661ab54147a0 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 31 Oct 2019 21:16:10 +0100 Subject: [PATCH 14/19] Ignore negative measurements in Counter and Summary Handle possible rare exceptions from HdrHistogram in Summary Signed-off-by: Rudolf Rakos --- .../main/java/io/prometheus/client/Counter.java | 4 +--- .../main/java/io/prometheus/client/Summary.java | 17 ++++++++++------- .../java/io/prometheus/client/CounterTest.java | 13 +++++++------ .../java/io/prometheus/client/SummaryTest.java | 4 ++-- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/Counter.java b/simpleclient/src/main/java/io/prometheus/client/Counter.java index 180a8d5c9..e44a3540f 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Counter.java +++ b/simpleclient/src/main/java/io/prometheus/client/Counter.java @@ -116,11 +116,10 @@ public void inc() { } /** * Increment the counter by the given amount. - * @throws IllegalArgumentException If amt is negative. */ public void inc(double amt) { if (amt < 0) { - throw new IllegalArgumentException("Amount to increment must be non-negative."); + return; // ignore negative measurements } value.add(amt); } @@ -141,7 +140,6 @@ public void inc() { } /** * Increment the counter with no labels by the given amount. - * @throws IllegalArgumentException If amt is negative. */ public void inc(double amt) { noLabelsChild.inc(amt); diff --git a/simpleclient/src/main/java/io/prometheus/client/Summary.java b/simpleclient/src/main/java/io/prometheus/client/Summary.java index d90a8c023..5afcb12c9 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Summary.java +++ b/simpleclient/src/main/java/io/prometheus/client/Summary.java @@ -4,6 +4,8 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Summary metric using HdrHistogram, to track the size of events. @@ -271,19 +273,22 @@ private Child(List quantiles, long highestToLowestValueRatio, int number /** * Observe the given amount. - * - * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { if (amt < 0.0) { - // See DoubleHistogram#autoAdjustRangeForValueSlowPath - throw new IllegalArgumentException("Value " + amt + " invalid: Negative values are not supported by HdrHistogram."); + return; // ignore negative measurements } count.add(1); sum.add(amt); if (quantileValues != null) { - quantileValues.insert(amt); + try { + quantileValues.insert(amt); + } catch (Exception e) { + // handle possible rare exceptions from HdrHistogram + Logger.getLogger(Summary.class.getName()) + .log(Level.WARNING, "Failed to record value: " + amt, e); + } } } @@ -345,8 +350,6 @@ public Value get() { /** * Observe the given amount on the summary with no labels. - * - * @throws IllegalArgumentException If amt is negative. */ public void observe(double amt) { noLabelsChild.observe(amt); diff --git a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java index 16d025f73..6c2805e6a 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java @@ -1,11 +1,12 @@ package io.prometheus.client; -import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; import java.util.ArrayList; import java.util.List; -import org.junit.Test; -import org.junit.Before; + +import static org.junit.Assert.assertEquals; public class CounterTest { @@ -38,9 +39,9 @@ public void testIncrement() { assertEquals(8.0, getValue(), .001); assertEquals(8.0, noLabels.get(), .001); } - - @Test(expected=IllegalArgumentException.class) - public void testNegativeIncrementFails() { + + @Test + public void testNegativeIncrementSucceeds() { noLabels.inc(-1); } diff --git a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java index e16e4a0ba..e8f2f85be 100644 --- a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java @@ -131,12 +131,12 @@ public void testQuantiles() { assertEquals(0.99 * nSamples, getLabelsQuantile(0.99), error * nSamples); } - @Test(expected = IllegalArgumentException.class) + @Test public void testObserveNegative() { noLabels.observe(-2.0); } - @Test(expected = IllegalArgumentException.class) + @Test public void testObserveNegativeQuantiles() { noLabelsAndQuantiles.observe(-2.0); } From ffded370191b8ac71b6b8594fd085b877966a5d2 Mon Sep 17 00:00:00 2001 From: Rudolf Rakos Date: Thu, 31 Oct 2019 21:43:06 +0100 Subject: [PATCH 15/19] Version `0.8.2-evo1` Signed-off-by: Rudolf Rakos --- benchmark/pom.xml | 4 ++-- pom.xml | 2 +- simpleclient/pom.xml | 2 +- simpleclient_caffeine/pom.xml | 4 ++-- simpleclient_common/pom.xml | 4 ++-- simpleclient_dropwizard/pom.xml | 4 ++-- simpleclient_graphite_bridge/pom.xml | 4 ++-- simpleclient_guava/pom.xml | 4 ++-- simpleclient_hibernate/pom.xml | 4 ++-- simpleclient_hotspot/pom.xml | 6 +++--- simpleclient_httpserver/pom.xml | 6 +++--- simpleclient_jetty/pom.xml | 4 ++-- simpleclient_jetty_jdk8/pom.xml | 4 ++-- simpleclient_log4j/pom.xml | 4 ++-- simpleclient_log4j2/pom.xml | 4 ++-- simpleclient_logback/pom.xml | 4 ++-- simpleclient_pushgateway/pom.xml | 6 +++--- simpleclient_servlet/pom.xml | 6 +++--- simpleclient_spring_boot/pom.xml | 8 ++++---- simpleclient_spring_web/pom.xml | 6 +++--- simpleclient_vertx/pom.xml | 6 +++--- 21 files changed, 48 insertions(+), 48 deletions(-) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index adbda13af..4f749e9b2 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -49,7 +49,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 com.codahale.metrics diff --git a/pom.xml b/pom.xml index d3f19a302..8866adc6a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.sonatype.oss diff --git a/simpleclient/pom.xml b/simpleclient/pom.xml index 15c0bce2d..cf7640a17 100644 --- a/simpleclient/pom.xml +++ b/simpleclient/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus diff --git a/simpleclient_caffeine/pom.xml b/simpleclient_caffeine/pom.xml index 6b03d5c03..6dfcc4e4b 100644 --- a/simpleclient_caffeine/pom.xml +++ b/simpleclient_caffeine/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 com.github.ben-manes.caffeine diff --git a/simpleclient_common/pom.xml b/simpleclient_common/pom.xml index a6dd61562..ef053da56 100644 --- a/simpleclient_common/pom.xml +++ b/simpleclient_common/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 diff --git a/simpleclient_dropwizard/pom.xml b/simpleclient_dropwizard/pom.xml index d8b815063..425d8b5c5 100644 --- a/simpleclient_dropwizard/pom.xml +++ b/simpleclient_dropwizard/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.dropwizard.metrics diff --git a/simpleclient_graphite_bridge/pom.xml b/simpleclient_graphite_bridge/pom.xml index c796f5ce2..0633ad29c 100644 --- a/simpleclient_graphite_bridge/pom.xml +++ b/simpleclient_graphite_bridge/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 diff --git a/simpleclient_guava/pom.xml b/simpleclient_guava/pom.xml index 0637bc4e1..b8d5bd3c1 100644 --- a/simpleclient_guava/pom.xml +++ b/simpleclient_guava/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 com.google.guava diff --git a/simpleclient_hibernate/pom.xml b/simpleclient_hibernate/pom.xml index b4a82d6e6..ad225fe9a 100644 --- a/simpleclient_hibernate/pom.xml +++ b/simpleclient_hibernate/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 diff --git a/simpleclient_hotspot/pom.xml b/simpleclient_hotspot/pom.xml index 19041684c..3d9c83b80 100644 --- a/simpleclient_hotspot/pom.xml +++ b/simpleclient_hotspot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,14 +37,14 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_servlet - 0.8.2-SNAPSHOT + 0.8.2-evo1 test diff --git a/simpleclient_httpserver/pom.xml b/simpleclient_httpserver/pom.xml index 6f3d7ef37..863262ac2 100644 --- a/simpleclient_httpserver/pom.xml +++ b/simpleclient_httpserver/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,12 +37,12 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 diff --git a/simpleclient_jetty/pom.xml b/simpleclient_jetty/pom.xml index 65bdb3399..14f3fdf42 100644 --- a/simpleclient_jetty/pom.xml +++ b/simpleclient_jetty/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.eclipse.jetty diff --git a/simpleclient_jetty_jdk8/pom.xml b/simpleclient_jetty_jdk8/pom.xml index 3d98ceef8..669579bdb 100644 --- a/simpleclient_jetty_jdk8/pom.xml +++ b/simpleclient_jetty_jdk8/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -48,7 +48,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.eclipse.jetty diff --git a/simpleclient_log4j/pom.xml b/simpleclient_log4j/pom.xml index 61606b344..7f4f86d98 100644 --- a/simpleclient_log4j/pom.xml +++ b/simpleclient_log4j/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 log4j diff --git a/simpleclient_log4j2/pom.xml b/simpleclient_log4j2/pom.xml index fb089d7c4..4e70d60df 100644 --- a/simpleclient_log4j2/pom.xml +++ b/simpleclient_log4j2/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.apache.logging.log4j diff --git a/simpleclient_logback/pom.xml b/simpleclient_logback/pom.xml index 277b57eaa..68eda5e08 100644 --- a/simpleclient_logback/pom.xml +++ b/simpleclient_logback/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 ch.qos.logback diff --git a/simpleclient_pushgateway/pom.xml b/simpleclient_pushgateway/pom.xml index 97d61c4d1..98dbcfe9c 100644 --- a/simpleclient_pushgateway/pom.xml +++ b/simpleclient_pushgateway/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -38,12 +38,12 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 javax.xml.bind diff --git a/simpleclient_servlet/pom.xml b/simpleclient_servlet/pom.xml index 85de8f241..baa3de21f 100644 --- a/simpleclient_servlet/pom.xml +++ b/simpleclient_servlet/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -41,12 +41,12 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 javax.servlet diff --git a/simpleclient_spring_boot/pom.xml b/simpleclient_spring_boot/pom.xml index 9a8c9710d..9c4b42dc9 100644 --- a/simpleclient_spring_boot/pom.xml +++ b/simpleclient_spring_boot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -52,17 +52,17 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_spring_web - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.springframework.boot diff --git a/simpleclient_spring_web/pom.xml b/simpleclient_spring_web/pom.xml index 914be8247..b131d7400 100644 --- a/simpleclient_spring_web/pom.xml +++ b/simpleclient_spring_web/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 simpleclient_spring_web @@ -51,12 +51,12 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 org.springframework diff --git a/simpleclient_vertx/pom.xml b/simpleclient_vertx/pom.xml index b50df92e4..7d68ee74b 100644 --- a/simpleclient_vertx/pom.xml +++ b/simpleclient_vertx/pom.xml @@ -17,7 +17,7 @@ io.prometheus parent - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus @@ -53,12 +53,12 @@ io.prometheus simpleclient - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.prometheus simpleclient_common - 0.8.2-SNAPSHOT + 0.8.2-evo1 io.vertx From f32b0c42b6f6671bf80706c502a833dc36a12c7f Mon Sep 17 00:00:00 2001 From: Vitaly Lavrov Date: Tue, 10 Nov 2020 16:34:59 +0100 Subject: [PATCH 16/19] Add marker class so that runtime validation is possible --- .../src/main/java/io/prometheus/client/EvoVersion.java | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 simpleclient/src/main/java/io/prometheus/client/EvoVersion.java diff --git a/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java b/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java new file mode 100644 index 000000000..4308bffed --- /dev/null +++ b/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java @@ -0,0 +1,3 @@ +package io.prometheus.client; + +public class EvoVersion {} From bf0d1a84c999a239f1b3b123495aa973141cf388 Mon Sep 17 00:00:00 2001 From: Vitaly Lavrov Date: Tue, 10 Nov 2020 16:58:58 +0100 Subject: [PATCH 17/19] Version 0.9.0-evo1 --- benchmark/pom.xml | 4 ++-- pom.xml | 4 ++-- simpleclient/pom.xml | 2 +- simpleclient_caffeine/pom.xml | 4 ++-- simpleclient_common/pom.xml | 4 ++-- simpleclient_dropwizard/pom.xml | 4 ++-- simpleclient_graphite_bridge/pom.xml | 4 ++-- simpleclient_guava/pom.xml | 4 ++-- simpleclient_hibernate/pom.xml | 4 ++-- simpleclient_hotspot/pom.xml | 6 +++--- simpleclient_httpserver/pom.xml | 6 +++--- simpleclient_jetty/pom.xml | 4 ++-- simpleclient_jetty_jdk8/pom.xml | 4 ++-- simpleclient_log4j/pom.xml | 4 ++-- simpleclient_log4j2/pom.xml | 4 ++-- simpleclient_logback/pom.xml | 4 ++-- simpleclient_pushgateway/pom.xml | 6 +++--- simpleclient_servlet/pom.xml | 6 +++--- simpleclient_spring_boot/pom.xml | 8 ++++---- simpleclient_spring_web/pom.xml | 6 +++--- simpleclient_vertx/pom.xml | 6 +++--- 21 files changed, 49 insertions(+), 49 deletions(-) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index 92226557c..65ab6a66d 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -49,7 +49,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 com.codahale.metrics diff --git a/pom.xml b/pom.xml index 17d94f523..d80013618 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 org.sonatype.oss @@ -31,7 +31,7 @@ scm:git:git@github.com:prometheus/client_java.git scm:git:git@github.com:prometheus/client_java.git git@github.com:prometheus/client_java.git - parent-0.9.0 + parent-0.9.0-evo1 diff --git a/simpleclient/pom.xml b/simpleclient/pom.xml index 6658372b4..7e4c65ee8 100644 --- a/simpleclient/pom.xml +++ b/simpleclient/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus diff --git a/simpleclient_caffeine/pom.xml b/simpleclient_caffeine/pom.xml index b1c1fe15c..249ab3a08 100644 --- a/simpleclient_caffeine/pom.xml +++ b/simpleclient_caffeine/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 com.github.ben-manes.caffeine diff --git a/simpleclient_common/pom.xml b/simpleclient_common/pom.xml index 24ec812c6..33ae5315d 100644 --- a/simpleclient_common/pom.xml +++ b/simpleclient_common/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 diff --git a/simpleclient_dropwizard/pom.xml b/simpleclient_dropwizard/pom.xml index dad5c826d..147d027d0 100644 --- a/simpleclient_dropwizard/pom.xml +++ b/simpleclient_dropwizard/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.dropwizard.metrics diff --git a/simpleclient_graphite_bridge/pom.xml b/simpleclient_graphite_bridge/pom.xml index 86054441e..7a65fe372 100644 --- a/simpleclient_graphite_bridge/pom.xml +++ b/simpleclient_graphite_bridge/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 diff --git a/simpleclient_guava/pom.xml b/simpleclient_guava/pom.xml index 26b52d7f9..1aeae3bf7 100644 --- a/simpleclient_guava/pom.xml +++ b/simpleclient_guava/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 com.google.guava diff --git a/simpleclient_hibernate/pom.xml b/simpleclient_hibernate/pom.xml index f203a7ec9..2e1395364 100644 --- a/simpleclient_hibernate/pom.xml +++ b/simpleclient_hibernate/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 diff --git a/simpleclient_hotspot/pom.xml b/simpleclient_hotspot/pom.xml index 542886714..613cad50d 100644 --- a/simpleclient_hotspot/pom.xml +++ b/simpleclient_hotspot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,14 +37,14 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_servlet - 0.9.0 + 0.9.0-evo1 test diff --git a/simpleclient_httpserver/pom.xml b/simpleclient_httpserver/pom.xml index 09c769571..4e61dec1e 100644 --- a/simpleclient_httpserver/pom.xml +++ b/simpleclient_httpserver/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,12 +37,12 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 diff --git a/simpleclient_jetty/pom.xml b/simpleclient_jetty/pom.xml index 46e5205f8..763c71c45 100644 --- a/simpleclient_jetty/pom.xml +++ b/simpleclient_jetty/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 org.eclipse.jetty diff --git a/simpleclient_jetty_jdk8/pom.xml b/simpleclient_jetty_jdk8/pom.xml index 9a812b0b4..c419a5a28 100644 --- a/simpleclient_jetty_jdk8/pom.xml +++ b/simpleclient_jetty_jdk8/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -48,7 +48,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 org.eclipse.jetty diff --git a/simpleclient_log4j/pom.xml b/simpleclient_log4j/pom.xml index 39a8409cb..1949758f5 100644 --- a/simpleclient_log4j/pom.xml +++ b/simpleclient_log4j/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 log4j diff --git a/simpleclient_log4j2/pom.xml b/simpleclient_log4j2/pom.xml index 3fb05cf02..f5a602893 100644 --- a/simpleclient_log4j2/pom.xml +++ b/simpleclient_log4j2/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 org.apache.logging.log4j diff --git a/simpleclient_logback/pom.xml b/simpleclient_logback/pom.xml index d451cd14b..735ab7bee 100644 --- a/simpleclient_logback/pom.xml +++ b/simpleclient_logback/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 ch.qos.logback diff --git a/simpleclient_pushgateway/pom.xml b/simpleclient_pushgateway/pom.xml index 1d3b98d7f..85115d31d 100644 --- a/simpleclient_pushgateway/pom.xml +++ b/simpleclient_pushgateway/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -38,12 +38,12 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 javax.xml.bind diff --git a/simpleclient_servlet/pom.xml b/simpleclient_servlet/pom.xml index d3d5c869e..6580d3a67 100644 --- a/simpleclient_servlet/pom.xml +++ b/simpleclient_servlet/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -41,12 +41,12 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 javax.servlet diff --git a/simpleclient_spring_boot/pom.xml b/simpleclient_spring_boot/pom.xml index f2ae28981..39baff44c 100644 --- a/simpleclient_spring_boot/pom.xml +++ b/simpleclient_spring_boot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -52,17 +52,17 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_spring_web - 0.9.0 + 0.9.0-evo1 org.springframework.boot diff --git a/simpleclient_spring_web/pom.xml b/simpleclient_spring_web/pom.xml index 3d7f43f5a..4eb8a5469 100644 --- a/simpleclient_spring_web/pom.xml +++ b/simpleclient_spring_web/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 simpleclient_spring_web @@ -51,12 +51,12 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 org.springframework diff --git a/simpleclient_vertx/pom.xml b/simpleclient_vertx/pom.xml index 4dcae53a2..e18ae5108 100644 --- a/simpleclient_vertx/pom.xml +++ b/simpleclient_vertx/pom.xml @@ -17,7 +17,7 @@ io.prometheus parent - 0.9.0 + 0.9.0-evo1 io.prometheus @@ -53,12 +53,12 @@ io.prometheus simpleclient - 0.9.0 + 0.9.0-evo1 io.prometheus simpleclient_common - 0.9.0 + 0.9.0-evo1 io.vertx From 92b78dcc127d98ff582f13efa2cd0c35c9e627c5 Mon Sep 17 00:00:00 2001 From: Vitaly Lavrov Date: Wed, 11 Nov 2020 14:41:36 +0100 Subject: [PATCH 18/19] Version 0.9.999-evo1 which should evict whole 0.9.* series --- benchmark/pom.xml | 4 ++-- pom.xml | 4 ++-- simpleclient/pom.xml | 2 +- .../src/main/java/io/prometheus/client/EvoVersion.java | 4 +++- simpleclient_caffeine/pom.xml | 4 ++-- simpleclient_common/pom.xml | 4 ++-- simpleclient_dropwizard/pom.xml | 4 ++-- simpleclient_graphite_bridge/pom.xml | 4 ++-- simpleclient_guava/pom.xml | 4 ++-- simpleclient_hibernate/pom.xml | 4 ++-- simpleclient_hotspot/pom.xml | 6 +++--- simpleclient_httpserver/pom.xml | 6 +++--- simpleclient_jetty/pom.xml | 4 ++-- simpleclient_jetty_jdk8/pom.xml | 4 ++-- simpleclient_log4j/pom.xml | 4 ++-- simpleclient_log4j2/pom.xml | 4 ++-- simpleclient_logback/pom.xml | 4 ++-- simpleclient_pushgateway/pom.xml | 6 +++--- simpleclient_servlet/pom.xml | 6 +++--- simpleclient_spring_boot/pom.xml | 8 ++++---- simpleclient_spring_web/pom.xml | 6 +++--- simpleclient_vertx/pom.xml | 6 +++--- 22 files changed, 52 insertions(+), 50 deletions(-) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index 65ab6a66d..a2e431af3 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -49,7 +49,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 com.codahale.metrics diff --git a/pom.xml b/pom.xml index d80013618..2e63436f4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 org.sonatype.oss @@ -31,7 +31,7 @@ scm:git:git@github.com:prometheus/client_java.git scm:git:git@github.com:prometheus/client_java.git git@github.com:prometheus/client_java.git - parent-0.9.0-evo1 + parent-0.9.999-evo1 diff --git a/simpleclient/pom.xml b/simpleclient/pom.xml index 7e4c65ee8..a0ef1a6a0 100644 --- a/simpleclient/pom.xml +++ b/simpleclient/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus diff --git a/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java b/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java index 4308bffed..8010628b0 100644 --- a/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java +++ b/simpleclient/src/main/java/io/prometheus/client/EvoVersion.java @@ -1,3 +1,5 @@ package io.prometheus.client; -public class EvoVersion {} +public class EvoVersion { + public static String version = "0.9.999-evo1"; +} diff --git a/simpleclient_caffeine/pom.xml b/simpleclient_caffeine/pom.xml index 249ab3a08..839905cbe 100644 --- a/simpleclient_caffeine/pom.xml +++ b/simpleclient_caffeine/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 com.github.ben-manes.caffeine diff --git a/simpleclient_common/pom.xml b/simpleclient_common/pom.xml index 33ae5315d..78abc76b8 100644 --- a/simpleclient_common/pom.xml +++ b/simpleclient_common/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 diff --git a/simpleclient_dropwizard/pom.xml b/simpleclient_dropwizard/pom.xml index 147d027d0..0ee0bbf3a 100644 --- a/simpleclient_dropwizard/pom.xml +++ b/simpleclient_dropwizard/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.dropwizard.metrics diff --git a/simpleclient_graphite_bridge/pom.xml b/simpleclient_graphite_bridge/pom.xml index 7a65fe372..355c45a27 100644 --- a/simpleclient_graphite_bridge/pom.xml +++ b/simpleclient_graphite_bridge/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 diff --git a/simpleclient_guava/pom.xml b/simpleclient_guava/pom.xml index 1aeae3bf7..297fde57b 100644 --- a/simpleclient_guava/pom.xml +++ b/simpleclient_guava/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 com.google.guava diff --git a/simpleclient_hibernate/pom.xml b/simpleclient_hibernate/pom.xml index 2e1395364..226a142bc 100644 --- a/simpleclient_hibernate/pom.xml +++ b/simpleclient_hibernate/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -38,7 +38,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 diff --git a/simpleclient_hotspot/pom.xml b/simpleclient_hotspot/pom.xml index 613cad50d..d1df00fc1 100644 --- a/simpleclient_hotspot/pom.xml +++ b/simpleclient_hotspot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,14 +37,14 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_servlet - 0.9.0-evo1 + 0.9.999-evo1 test diff --git a/simpleclient_httpserver/pom.xml b/simpleclient_httpserver/pom.xml index 4e61dec1e..adc56c801 100644 --- a/simpleclient_httpserver/pom.xml +++ b/simpleclient_httpserver/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,12 +37,12 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 diff --git a/simpleclient_jetty/pom.xml b/simpleclient_jetty/pom.xml index 763c71c45..adaaa4caa 100644 --- a/simpleclient_jetty/pom.xml +++ b/simpleclient_jetty/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -35,7 +35,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 org.eclipse.jetty diff --git a/simpleclient_jetty_jdk8/pom.xml b/simpleclient_jetty_jdk8/pom.xml index c419a5a28..126d52759 100644 --- a/simpleclient_jetty_jdk8/pom.xml +++ b/simpleclient_jetty_jdk8/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -48,7 +48,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 org.eclipse.jetty diff --git a/simpleclient_log4j/pom.xml b/simpleclient_log4j/pom.xml index 1949758f5..7376c6ca4 100644 --- a/simpleclient_log4j/pom.xml +++ b/simpleclient_log4j/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 log4j diff --git a/simpleclient_log4j2/pom.xml b/simpleclient_log4j2/pom.xml index f5a602893..559dbdc80 100644 --- a/simpleclient_log4j2/pom.xml +++ b/simpleclient_log4j2/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 org.apache.logging.log4j diff --git a/simpleclient_logback/pom.xml b/simpleclient_logback/pom.xml index 735ab7bee..4272523af 100644 --- a/simpleclient_logback/pom.xml +++ b/simpleclient_logback/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -37,7 +37,7 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 ch.qos.logback diff --git a/simpleclient_pushgateway/pom.xml b/simpleclient_pushgateway/pom.xml index 85115d31d..f67522acb 100644 --- a/simpleclient_pushgateway/pom.xml +++ b/simpleclient_pushgateway/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -38,12 +38,12 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 javax.xml.bind diff --git a/simpleclient_servlet/pom.xml b/simpleclient_servlet/pom.xml index 6580d3a67..f8f31bd22 100644 --- a/simpleclient_servlet/pom.xml +++ b/simpleclient_servlet/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -41,12 +41,12 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 javax.servlet diff --git a/simpleclient_spring_boot/pom.xml b/simpleclient_spring_boot/pom.xml index 39baff44c..e40c2c22f 100644 --- a/simpleclient_spring_boot/pom.xml +++ b/simpleclient_spring_boot/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -52,17 +52,17 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_spring_web - 0.9.0-evo1 + 0.9.999-evo1 org.springframework.boot diff --git a/simpleclient_spring_web/pom.xml b/simpleclient_spring_web/pom.xml index 4eb8a5469..2fee5daa2 100644 --- a/simpleclient_spring_web/pom.xml +++ b/simpleclient_spring_web/pom.xml @@ -5,7 +5,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 simpleclient_spring_web @@ -51,12 +51,12 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 org.springframework diff --git a/simpleclient_vertx/pom.xml b/simpleclient_vertx/pom.xml index e18ae5108..b40d6c88a 100644 --- a/simpleclient_vertx/pom.xml +++ b/simpleclient_vertx/pom.xml @@ -17,7 +17,7 @@ io.prometheus parent - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus @@ -53,12 +53,12 @@ io.prometheus simpleclient - 0.9.0-evo1 + 0.9.999-evo1 io.prometheus simpleclient_common - 0.9.0-evo1 + 0.9.999-evo1 io.vertx From 024a729a281552f7ea78afcd6617218c76cea0a7 Mon Sep 17 00:00:00 2001 From: Vitaly Lavrov Date: Wed, 11 Nov 2020 16:52:01 +0100 Subject: [PATCH 19/19] Update release repository --- pom.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 2e63436f4..e85380709 100644 --- a/pom.xml +++ b/pom.xml @@ -70,13 +70,9 @@ - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + evo-bintray + https://api.bintray.com/maven/evolutiongaming/maven/simpleclient/;publish=1