io.prometheus.client.Histogram Maven / Gradle / Ivy
Show all versions of simpleclient Show documentation
package io.prometheus.client;
import java.io.Closeable;
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()}.
*/
@Override
public void close() {
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;
}
}