001package io.prometheus.client;
002
003import io.prometheus.client.exemplars.Exemplar;
004import io.prometheus.client.exemplars.ExemplarConfig;
005import io.prometheus.client.exemplars.HistogramExemplarSampler;
006
007import java.io.Closeable;
008import java.util.ArrayList;
009import java.util.Collections;
010import java.util.List;
011import java.util.Map;
012import java.util.concurrent.Callable;
013import java.util.concurrent.atomic.AtomicReference;
014
015import static java.lang.Boolean.FALSE;
016import static java.lang.Boolean.TRUE;
017
018/**
019 * Histogram metric, to track distributions of events.
020 * <p>
021 * Example of uses for Histograms include:
022 * <ul>
023 *  <li>Response latency</li>
024 *  <li>Request size</li>
025 * </ul>
026 * <p>
027 * <em>Note:</em> Each bucket is one timeseries. Many buckets and/or many dimensions with labels
028 * can produce large amount of time series, that may cause performance problems.
029 *
030 * <p>
031 * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
032 * <p>
033 * Example Histograms:
034 * <pre>
035 * {@code
036 *   class YourClass {
037 *     static final Histogram requestLatency = Histogram.build()
038 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
039 *
040 *     void processRequest(Request req) {
041 *        Histogram.Timer requestTimer = requestLatency.startTimer();
042 *        try {
043 *          // Your code here.
044 *        } finally {
045 *          requestTimer.observeDuration();
046 *        }
047 *     }
048 *
049 *     // Or if using Java 8 lambdas.
050 *     void processRequestLambda(Request req) {
051 *        requestLatency.time(() -> {
052 *          // Your code here.
053 *        });
054 *     }
055 *   }
056 * }
057 * </pre>
058 * <p>
059 * You can choose your own buckets:
060 * <pre>
061 * {@code
062 *     static final Histogram requestLatency = Histogram.build()
063 *         .buckets(.01, .02, .03, .04)
064 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
065 * }
066 * </pre>
067 * {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and
068 * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets}
069 * offer easy ways to set common bucket patterns.
070 */
071public class Histogram extends SimpleCollector<Histogram.Child> implements Collector.Describable {
072  private final double[] buckets;
073  private final Boolean exemplarsEnabled; // null means default from ExemplarConfig applies
074  private final HistogramExemplarSampler exemplarSampler;
075
076  Histogram(Builder b) {
077    super(b);
078    this.exemplarsEnabled = b.exemplarsEnabled;
079    this.exemplarSampler = b.exemplarSampler;
080    buckets = b.buckets;
081    initializeNoLabelsChild();
082  }
083
084  public static class Builder extends SimpleCollector.Builder<Builder, Histogram> {
085
086    private Boolean exemplarsEnabled = null;
087    private HistogramExemplarSampler exemplarSampler = null;
088    private double[] buckets = new double[] { .005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10 };
089
090    @Override
091    public Histogram create() {
092      for (int i = 0; i < buckets.length - 1; i++) {
093        if (buckets[i] >= buckets[i + 1]) {
094          throw new IllegalStateException("Histogram buckets must be in increasing order: "
095              + buckets[i] + " >= " + buckets[i + 1]);
096        }
097      }
098      if (buckets.length == 0) {
099        throw new IllegalStateException("Histogram must have at least one bucket.");
100      }
101      for (String label : labelNames) {
102        if (label.equals("le")) {
103          throw new IllegalStateException("Histogram cannot have a label named 'le'.");
104        }
105      }
106
107      // Append infinity bucket if it's not already there.
108      if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) {
109        double[] tmp = new double[buckets.length + 1];
110        System.arraycopy(buckets, 0, tmp, 0, buckets.length);
111        tmp[buckets.length] = Double.POSITIVE_INFINITY;
112        buckets = tmp;
113      }
114      dontInitializeNoLabelsChild = true;
115      return new Histogram(this);
116    }
117
118    /**
119     * Set the upper bounds of buckets for the histogram.
120     */
121    public Builder buckets(double... buckets) {
122      this.buckets = buckets;
123      return this;
124    }
125
126    /**
127     * Set the upper bounds of buckets for the histogram with a linear sequence.
128     */
129    public Builder linearBuckets(double start, double width, int count) {
130      buckets = new double[count];
131      for (int i = 0; i < count; i++) {
132        buckets[i] = start + i * width;
133      }
134      return this;
135    }
136
137    /**
138     * Set the upper bounds of buckets for the histogram with an exponential sequence.
139     */
140    public Builder exponentialBuckets(double start, double factor, int count) {
141      buckets = new double[count];
142      for (int i = 0; i < count; i++) {
143        buckets[i] = start * Math.pow(factor, i);
144      }
145      return this;
146    }
147
148    /**
149     * Enable exemplars and provide a custom {@link HistogramExemplarSampler}.
150     */
151    public Builder withExemplarSampler(HistogramExemplarSampler exemplarSampler) {
152      if (exemplarSampler == null) {
153        throw new NullPointerException();
154      }
155      this.exemplarSampler = exemplarSampler;
156      return withExemplars();
157    }
158
159    /**
160     * Allow this histogram to load exemplars from a {@link HistogramExemplarSampler}.
161     * <p>
162     * If a specific exemplar sampler is configured for this histogram that exemplar sampler is used
163     * (see {@link #withExemplarSampler(HistogramExemplarSampler)}).
164     * Otherwise the default from {@link ExemplarConfig} is used.
165     */
166    public Builder withExemplars() {
167      this.exemplarsEnabled = TRUE;
168      return this;
169    }
170
171    /**
172     * Prevent this histogram from loading exemplars from a {@link HistogramExemplarSampler}.
173     * <p>
174     * You can still provide exemplars for explicitly individual observations, e.g. using
175     * {@link #observeWithExemplar(double, String...)}.
176     */
177    public Builder withoutExemplars() {
178      this.exemplarsEnabled = FALSE;
179      return this;
180    }
181  }
182
183  /**
184   * Return a Builder to allow configuration of a new Histogram. Ensures required fields are provided.
185   *
186   * @param name The name of the metric
187   * @param help The help string of the metric
188   */
189  public static Builder build(String name, String help) {
190    return new Builder().name(name).help(help);
191  }
192
193  /**
194   * Return a Builder to allow configuration of a new Histogram.
195   */
196  public static Builder build() {
197    return new Builder();
198  }
199
200  @Override
201  protected Child newChild() {
202    return new Child(buckets, exemplarsEnabled, exemplarSampler);
203  }
204
205  /**
206   * Represents an event being timed.
207   */
208  public static class Timer implements Closeable {
209    private final Child child;
210    private final long start;
211
212    private Timer(Child child, long start) {
213      this.child = child;
214      this.start = start;
215    }
216
217    /**
218     * Observe the amount of time in seconds since {@link Child#startTimer} was called.
219     *
220     * @return Measured duration in seconds since {@link Child#startTimer} was called.
221     */
222    public double observeDuration() {
223      return observeDurationWithExemplar((String[]) null);
224    }
225
226    public double observeDurationWithExemplar(String... exemplarLabels) {
227      double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, SimpleTimer.defaultTimeProvider.nanoTime());
228      child.observeWithExemplar(elapsed, exemplarLabels);
229      return elapsed;
230    }
231
232    public double observeDurationWithExemplar(Map<String, String> exemplarLabels) {
233      return observeDurationWithExemplar(Exemplar.mapToArray(exemplarLabels));
234    }
235
236    /**
237     * Equivalent to calling {@link #observeDuration()}.
238     */
239    @Override
240    public void close() {
241      observeDuration();
242    }
243  }
244
245  /**
246   * The value of a single Histogram.
247   * <p>
248   * <em>Warning:</em> References to a Child become invalid after using
249   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
250   */
251  public static class Child {
252
253    /**
254     * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
255     *
256     * @param timeable Code that is being timed
257     * @return Measured duration in seconds for timeable to complete.
258     */
259    public double time(Runnable timeable) {
260      return timeWithExemplar(timeable, (String[]) null);
261    }
262
263    /**
264     * Like {@link #time(Runnable)}, but additionally create an exemplar.
265     * <p>
266     * See {@link #observeWithExemplar(double, String...)}  for documentation on the {@code exemplarLabels} parameter.
267     */
268    public double timeWithExemplar(Runnable timeable, String... exemplarLabels) {
269      Timer timer = startTimer();
270
271      double elapsed;
272      try {
273        timeable.run();
274      } finally {
275        elapsed = timer.observeDurationWithExemplar(exemplarLabels);
276      }
277      return elapsed;
278    }
279
280    /**
281     * Like {@link #time(Runnable)}, but additionally create an exemplar.
282     * <p>
283     * See {@link #observeWithExemplar(double, Map)}  for documentation on the {@code exemplarLabels} parameter.
284     */
285    public double timeWithExemplar(Runnable timeable, Map<String, String> exemplarLabels) {
286      return timeWithExemplar(timeable, Exemplar.mapToArray(exemplarLabels));
287    }
288
289    /**
290     * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
291     *
292     * @param timeable Code that is being timed
293     * @return Result returned by callable.
294     */
295    public <E> E time(Callable<E> timeable) {
296      return timeWithExemplar(timeable, (String[]) null);
297    }
298
299    /**
300     * Like {@link #time(Callable)}, but additionally create an exemplar.
301     * <p>
302     * See {@link #observeWithExemplar(double, String...)}  for documentation on the {@code exemplarLabels} parameter.
303     */
304    public <E> E timeWithExemplar(Callable<E> timeable, String... exemplarLabels) {
305      Timer timer = startTimer();
306
307      try {
308        return timeable.call();
309      } catch (RuntimeException e) {
310        throw e;
311      } catch (Exception e) {
312        throw new RuntimeException(e);
313      } finally {
314        timer.observeDurationWithExemplar(exemplarLabels);
315      }
316    }
317
318    /**
319     * Like {@link #time(Callable)}, but additionally create an exemplar.
320     * <p>
321     * See {@link #observeWithExemplar(double, Map)}  for documentation on the {@code exemplarLabels} parameter.
322     */
323    public <E> E timeWithExemplar(Callable<E> timeable, Map<String, String> exemplarLabels) {
324      return timeWithExemplar(timeable, Exemplar.mapToArray(exemplarLabels));
325    }
326
327    public static class Value {
328      public final double sum;
329      public final double[] buckets;
330      public final Exemplar[] exemplars;
331      public final long created;
332
333      public Value(double sum, double[] buckets, Exemplar[] exemplars, long created) {
334        this.sum = sum;
335        this.buckets = buckets;
336        this.exemplars = exemplars;
337        this.created = created;
338      }
339    }
340
341    private Child(double[] buckets, Boolean exemplarsEnabled, HistogramExemplarSampler exemplarSampler) {
342      upperBounds = buckets;
343      this.exemplarsEnabled = exemplarsEnabled;
344      this.exemplarSampler = exemplarSampler;
345      exemplars = new ArrayList<AtomicReference<Exemplar>>(buckets.length);
346      cumulativeCounts = new DoubleAdder[buckets.length];
347      for (int i = 0; i < buckets.length; ++i) {
348        cumulativeCounts[i] = new DoubleAdder();
349        exemplars.add(new AtomicReference<Exemplar>());
350      }
351    }
352
353    private final ArrayList<AtomicReference<Exemplar>> exemplars;
354    private final Boolean exemplarsEnabled;
355    private final HistogramExemplarSampler exemplarSampler;
356    private final double[] upperBounds;
357    private final DoubleAdder[] cumulativeCounts;
358    private final DoubleAdder sum = new DoubleAdder();
359    private final long created = System.currentTimeMillis();
360
361    /**
362     * Observe the given amount.
363     *
364     * @param amt in most cases amt should be &gt;= 0. Negative values are supported, but you should read
365     *            <a href="https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations">
366     *            https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations</a> for
367     *            implications and alternatives.
368     */
369    public void observe(double amt) {
370      observeWithExemplar(amt, (String[]) null);
371    }
372
373    /**
374     * Like {@link #observe(double)}, but additionally creates an exemplar.
375     * <p>
376     * This exemplar takes precedence over any exemplar returned by the {@link HistogramExemplarSampler} configured
377     * in {@link ExemplarConfig}.
378     * <p>
379     * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp,
380     * and the specified labels.
381     *
382     * @param amt            same as in {@link #observe(double)} (double)}
383     * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}.
384     *                       A commonly used name is {@code "trace_id"}.
385     *                       Calling {@code observeWithExemplar(amt)} means that an exemplar without labels is created.
386     *                       Calling {@code observeWithExemplar(amt, (String[]) null)} is equivalent
387     *                       to calling {@code observe(amt)}.
388     */
389    public void observeWithExemplar(double amt, String... exemplarLabels) {
390      Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels);
391      for (int i = 0; i < upperBounds.length; ++i) {
392        // The last bucket is +Inf, so we always increment.
393        if (amt <= upperBounds[i]) {
394          cumulativeCounts[i].add(1);
395          updateExemplar(amt, i, exemplar);
396          break;
397        }
398      }
399      sum.add(amt);
400    }
401
402    /**
403     * Like {@link #observeWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}.
404     */
405    public void observeWithExemplar(double amt, Map<String, String> exemplarLabels) {
406      observeWithExemplar(amt, Exemplar.mapToArray(exemplarLabels));
407    }
408
409    private void updateExemplar(double amt, int i, Exemplar userProvidedExemplar) {
410      AtomicReference<Exemplar> exemplar = exemplars.get(i);
411      double bucketFrom = i == 0 ? Double.NEGATIVE_INFINITY : upperBounds[i - 1];
412      double bucketTo = upperBounds[i];
413      Exemplar prev, next;
414      do {
415        prev = exemplar.get();
416        if (userProvidedExemplar != null) {
417          next = userProvidedExemplar;
418        } else {
419          next = sampleNextExemplar(amt, bucketFrom, bucketTo, prev);
420        }
421        if (next == null || next == prev) {
422          return;
423        }
424      } while (!exemplar.compareAndSet(prev, next));
425    }
426
427    private Exemplar sampleNextExemplar(double amt, double bucketFrom, double bucketTo, Exemplar prev) {
428      if (FALSE.equals(exemplarsEnabled)) {
429        return null;
430      }
431      if (exemplarSampler != null) {
432        return exemplarSampler.sample(amt, bucketFrom, bucketTo, prev);
433      }
434      if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) {
435        HistogramExemplarSampler exemplarSampler = ExemplarConfig.getHistogramExemplarSampler();
436        if (exemplarSampler != null) {
437          return exemplarSampler.sample(amt, bucketFrom, bucketTo, prev);
438        }
439      }
440      return null;
441    }
442
443    /**
444     * Start a timer to track a duration.
445     * <p>
446     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
447     */
448    public Timer startTimer() {
449      return new Timer(this, SimpleTimer.defaultTimeProvider.nanoTime());
450    }
451
452    /**
453     * Get the value of the Histogram.
454     * <p>
455     * <em>Warning:</em> The definition of {@link Value} is subject to change.
456     */
457    public Value get() {
458      double[] buckets = new double[cumulativeCounts.length];
459      Exemplar[] exemplars = new Exemplar[cumulativeCounts.length];
460      double acc = 0;
461      for (int i = 0; i < cumulativeCounts.length; ++i) {
462        acc += cumulativeCounts[i].sum();
463        buckets[i] = acc;
464        exemplars[i] = this.exemplars.get(i).get();
465      }
466      return new Value(sum.sum(), buckets, exemplars, created);
467    }
468  }
469
470  // Convenience methods.
471
472  /**
473   * Observe the given amount on the histogram with no labels.
474   *
475   * @param amt in most cases amt should be &gt;= 0. Negative values are supported, but you should read
476   *            <a href="https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations">
477   *            https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations</a> for
478   *            implications and alternatives.
479   */
480  public void observe(double amt) {
481    noLabelsChild.observe(amt);
482  }
483
484  /**
485   * Like {@link Child#observeWithExemplar(double, String...)}, but for the histogram without labels.
486   */
487  public void observeWithExemplar(double amt, String... exemplarLabels) {
488    noLabelsChild.observeWithExemplar(amt, exemplarLabels);
489  }
490
491  /**
492   * Like {@link Child#observeWithExemplar(double, Map)}, but for the histogram without labels.
493   */
494  public void observeWithExemplar(double amt, Map<String, String> exemplarLabels) {
495    noLabelsChild.observeWithExemplar(amt, exemplarLabels);
496  }
497
498  /**
499   * Start a timer to track a duration on the histogram with no labels.
500   * <p>
501   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
502   */
503  public Timer startTimer() {
504    return noLabelsChild.startTimer();
505  }
506
507  /**
508   * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
509   *
510   * @param timeable Code that is being timed
511   * @return Measured duration in seconds for timeable to complete.
512   */
513  public double time(Runnable timeable) {
514    return noLabelsChild.time(timeable);
515  }
516
517  /**
518   * Like {@link #time(Runnable)}, but additionally create an exemplar.
519   * <p>
520   * See {@link Child#observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter.
521   */
522  public double timeWithExemplar(Runnable timeable, String... exemplarLabels) {
523    return noLabelsChild.timeWithExemplar(timeable, exemplarLabels);
524  }
525
526  /**
527   * Like {@link #time(Runnable)}, but additionally create an exemplar.
528   * <p>
529   * See {@link Child#observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter.
530   */
531  public double timeWithExemplar(Runnable timeable, Map<String, String> exemplarLabels) {
532    return noLabelsChild.timeWithExemplar(timeable, exemplarLabels);
533  }
534
535  /**
536   * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
537   *
538   * @param timeable Code that is being timed
539   * @return Result returned by callable.
540   */
541  public <E> E time(Callable<E> timeable) {
542    return noLabelsChild.time(timeable);
543  }
544
545  /**
546   * Like {@link #time(Callable)}, but additionally create an exemplar.
547   * <p>
548   * See {@link Child#observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter.
549   */
550  public <E> E timeWithExemplar(Callable<E> timeable, String... exemplarLabels) {
551    return noLabelsChild.timeWithExemplar(timeable, exemplarLabels);
552  }
553
554  /**
555   * Like {@link #time(Callable)}, but additionally create an exemplar.
556   * <p>
557   * See {@link Child#observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter.
558   */
559  public <E> E timeWithExemplar(Callable<E> timeable, Map<String, String> exemplarLabels) {
560    return noLabelsChild.timeWithExemplar(timeable, exemplarLabels);
561  }
562
563  @Override
564  public List<MetricFamilySamples> collect() {
565    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
566    for (Map.Entry<List<String>, Child> c : children.entrySet()) {
567      Child.Value v = c.getValue().get();
568      List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
569      labelNamesWithLe.add("le");
570      for (int i = 0; i < v.buckets.length; ++i) {
571        List<String> labelValuesWithLe = new ArrayList<String>(c.getKey());
572        labelValuesWithLe.add(doubleToGoString(buckets[i]));
573        samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i], v.exemplars[i]));
574      }
575      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1]));
576      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
577      samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), v.created / 1000.0));
578    }
579
580    return familySamplesList(Type.HISTOGRAM, samples);
581  }
582
583  @Override
584  public List<MetricFamilySamples> describe() {
585    return Collections.singletonList(
586        new MetricFamilySamples(fullname, Type.HISTOGRAM, help, Collections.<MetricFamilySamples.Sample>emptyList()));
587  }
588
589  double[] getBuckets() {
590    return buckets;
591  }
592}