All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.prometheus.client.Histogram Maven / Gradle / Ivy

package io.prometheus.client;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Histogram metric, to track distributions of events.
 * 

* Example of uses for Histograms include: *

    *
  • Response latency
  • *
  • Request size
  • *
*

* Note: Each bucket is one timeseries. Many buckets and/or many dimensions with labels * can produce large amount of time series, that may cause performance problems. * *

* The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. *

* Example Histograms: *

 * {@code
 *   class YourClass {
 *     static final Histogram requestLatency = Histogram.build()
 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
 *
 *     void processRequest(Request req) {
 *        Histogram.Timer requestTimer = requestLatency.startTimer();
 *        try {
 *          // Your code here.
 *        } finally {
 *          requestTimer.observeDuration();
 *        }
 *     }
 *
 *     // Or if using Java 8 lambdas.
 *     void processRequestLambda(Request req) {
 *        requestLatency.time(() -> {
 *          // Your code here.
 *        });
 *     }
 *   }
 * }
 * 
*

* You can choose your own buckets: *

 * {@code
 *     static final Histogram requestLatency = Histogram.build()
 *         .buckets(.01, .02, .03, .04)
 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
 * }
 * 
* {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets} * offer easy ways to set common bucket patterns. */ public class Histogram extends SimpleCollector implements Collector.Describable { private final double[] buckets; Histogram(Builder b) { super(b); buckets = b.buckets; initializeNoLabelsChild(); } public static class Builder extends SimpleCollector.Builder { private double[] buckets = new double[]{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}; @Override public Histogram create() { for (int i = 0; i < buckets.length - 1; i++) { if (buckets[i] >= buckets[i + 1]) { throw new IllegalStateException("Histogram buckets must be in increasing order: " + buckets[i] + " >= " + buckets[i + 1]); } } if (buckets.length == 0) { throw new IllegalStateException("Histogram must have at least one bucket."); } for (String label: labelNames) { if (label.equals("le")) { throw new IllegalStateException("Histogram cannot have a label named 'le'."); } } // Append infinity bucket if it's not already there. if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) { double[] tmp = new double[buckets.length + 1]; System.arraycopy(buckets, 0, tmp, 0, buckets.length); tmp[buckets.length] = Double.POSITIVE_INFINITY; buckets = tmp; } dontInitializeNoLabelsChild = true; return new Histogram(this); } /** * Set the upper bounds of buckets for the histogram. */ public Builder buckets(double... buckets) { this.buckets = buckets; return this; } /** * Set the upper bounds of buckets for the histogram with a linear sequence. */ public Builder linearBuckets(double start, double width, int count) { buckets = new double[count]; for (int i = 0; i < count; i++){ buckets[i] = start + i*width; } return this; } /** * Set the upper bounds of buckets for the histogram with an exponential sequence. */ public Builder exponentialBuckets(double start, double factor, int count) { buckets = new double[count]; for (int i = 0; i < count; i++) { buckets[i] = start * Math.pow(factor, i); } return this; } } /** * Return a Builder to allow configuration of a new Histogram. 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 Histogram. */ public static Builder build() { return new Builder(); } @Override protected Child newChild() { return new Child(buckets); } /** * 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) { this.child = child; this.start = start; } /** * 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()); child.observe(elapsed); return elapsed; } /** * Equivalent to calling {@link #observeDuration()}. * @throws IOException */ @Override public void close() throws IOException { observeDuration(); } } /** * The value of a single Histogram. *

* Warning: References to a Child become invalid after using * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. */ public static class Child { /** * Executes runnable code (i.e. 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; } public static class Value { public final double sum; public final double[] buckets; public Value(double sum, double[] buckets) { this.sum = sum; this.buckets = buckets; } } private Child(double[] buckets) { upperBounds = buckets; cumulativeCounts = new DoubleAdder[buckets.length]; for (int i = 0; i < buckets.length; ++i) { cumulativeCounts[i] = new DoubleAdder(); } } private final double[] upperBounds; private final DoubleAdder[] cumulativeCounts; private final DoubleAdder sum = new DoubleAdder(); /** * Observe the given amount. */ public void observe(double amt) { for (int i = 0; i < upperBounds.length; ++i) { // The last bucket is +Inf, so we always increment. if (amt <= upperBounds[i]) { cumulativeCounts[i].add(1); break; } } sum.add(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()); } /** * Get the value of the Histogram. *

* Warning: The definition of {@link Value} is subject to change. */ public Value get() { double[] buckets = new double[cumulativeCounts.length]; double acc = 0; for (int i = 0; i < cumulativeCounts.length; ++i) { acc += cumulativeCounts[i].sum(); buckets[i] = acc; } return new Value(sum.sum(), buckets); } } // Convenience methods. /** * Observe the given amount on the histogram with no labels. */ public void observe(double amt) { noLabelsChild.observe(amt); } /** * Start a timer to track a duration on the histogram 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 (i.e. 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); } @Override public List collect() { List samples = new ArrayList(); for(Map.Entry, Child> c: children.entrySet()) { Child.Value v = c.getValue().get(); List labelNamesWithLe = new ArrayList(labelNames); labelNamesWithLe.add("le"); for (int i = 0; i < v.buckets.length; ++i) { List labelValuesWithLe = new ArrayList(c.getKey()); labelValuesWithLe.add(doubleToGoString(buckets[i])); samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i])); } samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1])); samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); } return familySamplesList(Type.HISTOGRAM, samples); } @Override public List describe() { return Collections.singletonList( new MetricFamilySamples(fullname, Type.HISTOGRAM, help, Collections.emptyList())); } double[] getBuckets() { return buckets; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy