001package io.prometheus.client;
002
003import java.io.Closeable;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007import java.util.Map;
008import java.util.concurrent.Callable;
009
010/**
011 * Histogram metric, to track distributions of events.
012 * <p>
013 * Example of uses for Histograms include:
014 * <ul>
015 *  <li>Response latency</li>
016 *  <li>Request size</li>
017 * </ul>
018 * <p>
019 * <em>Note:</em> Each bucket is one timeseries. Many buckets and/or many dimensions with labels
020 * can produce large amount of time series, that may cause performance problems.
021 *
022 * <p>
023 * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
024 * <p>
025 * Example Histograms:
026 * <pre>
027 * {@code
028 *   class YourClass {
029 *     static final Histogram requestLatency = Histogram.build()
030 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
031 *
032 *     void processRequest(Request req) {
033 *        Histogram.Timer requestTimer = requestLatency.startTimer();
034 *        try {
035 *          // Your code here.
036 *        } finally {
037 *          requestTimer.observeDuration();
038 *        }
039 *     }
040 *
041 *     // Or if using Java 8 lambdas.
042 *     void processRequestLambda(Request req) {
043 *        requestLatency.time(() -> {
044 *          // Your code here.
045 *        });
046 *     }
047 *   }
048 * }
049 * </pre>
050 * <p>
051 * You can choose your own buckets:
052 * <pre>
053 * {@code
054 *     static final Histogram requestLatency = Histogram.build()
055 *         .buckets(.01, .02, .03, .04)
056 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
057 * }
058 * </pre>
059 * {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and
060 * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets}
061 * offer easy ways to set common bucket patterns.
062 */
063public class Histogram extends SimpleCollector<Histogram.Child> implements Collector.Describable {
064  private final double[] buckets;
065
066  Histogram(Builder b) {
067    super(b);
068    buckets = b.buckets;
069    initializeNoLabelsChild();
070  }
071
072  public static class Builder extends SimpleCollector.Builder<Builder, Histogram> {
073    private double[] buckets = new double[]{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10};
074
075    @Override
076    public Histogram create() {
077      for (int i = 0; i < buckets.length - 1; i++) {
078        if (buckets[i] >= buckets[i + 1]) {
079          throw new IllegalStateException("Histogram buckets must be in increasing order: "
080              + buckets[i] + " >= " + buckets[i + 1]);
081        }
082      }
083      if (buckets.length == 0) {
084          throw new IllegalStateException("Histogram must have at least one bucket.");
085      }
086      for (String label: labelNames) {
087        if (label.equals("le")) {
088            throw new IllegalStateException("Histogram cannot have a label named 'le'.");
089        }
090      }
091
092      // Append infinity bucket if it's not already there.
093      if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) {
094        double[] tmp = new double[buckets.length + 1];
095        System.arraycopy(buckets, 0, tmp, 0, buckets.length);
096        tmp[buckets.length] = Double.POSITIVE_INFINITY;
097        buckets = tmp;
098      }
099      dontInitializeNoLabelsChild = true;
100      return new Histogram(this);
101    }
102
103    /**
104      * Set the upper bounds of buckets for the histogram.
105      */
106    public Builder buckets(double... buckets) {
107      this.buckets = buckets;
108      return this;
109    }
110
111    /**
112      * Set the upper bounds of buckets for the histogram with a linear sequence.
113      */
114    public Builder linearBuckets(double start, double width, int count) {
115      buckets = new double[count];
116      for (int i = 0; i < count; i++){
117        buckets[i] = start + i*width;
118      }
119      return this;
120    }
121    /**
122      * Set the upper bounds of buckets for the histogram with an exponential sequence.
123      */
124    public Builder exponentialBuckets(double start, double factor, int count) {
125      buckets = new double[count];
126      for (int i = 0; i < count; i++) {
127        buckets[i] = start * Math.pow(factor, i);
128      }
129      return this;
130    }
131
132  }
133
134  /**
135   *  Return a Builder to allow configuration of a new Histogram. Ensures required fields are provided.
136   *
137   *  @param name The name of the metric
138   *  @param help The help string of the metric
139   */
140  public static Builder build(String name, String help) {
141    return new Builder().name(name).help(help);
142  }
143
144  /**
145   *  Return a Builder to allow configuration of a new Histogram.
146   */
147  public static Builder build() {
148    return new Builder();
149  }
150
151  @Override
152  protected Child newChild() {
153    return new Child(buckets);
154  }
155
156  /**
157   * Represents an event being timed.
158   */
159  public static class Timer implements Closeable {
160    private final Child child;
161    private final long start;
162    private Timer(Child child, long start) {
163      this.child = child;
164      this.start = start;
165    }
166    /**
167     * Observe the amount of time in seconds since {@link Child#startTimer} was called.
168     * @return Measured duration in seconds since {@link Child#startTimer} was called.
169     */
170    public double observeDuration() {
171        double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, SimpleTimer.defaultTimeProvider.nanoTime());
172        child.observe(elapsed);
173        return elapsed;
174    }
175
176    /**
177     * Equivalent to calling {@link #observeDuration()}.
178     */
179    @Override
180    public void close() {
181      observeDuration();
182    }
183  }
184
185  /**
186   * The value of a single Histogram.
187   * <p>
188   * <em>Warning:</em> References to a Child become invalid after using
189   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
190   */
191  public static class Child {
192
193    /**
194     * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
195     *
196     * @param timeable Code that is being timed
197     * @return Measured duration in seconds for timeable to complete.
198     */
199    public double time(Runnable timeable) {
200      Timer timer = startTimer();
201
202      double elapsed;
203      try {
204        timeable.run();
205      } finally {
206        elapsed = timer.observeDuration();
207      }
208      return elapsed;
209    }
210
211    /**
212     * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
213     *
214     * @param timeable Code that is being timed
215     * @return Result returned by callable.
216     */
217    public <E> E time(Callable<E> timeable) {
218      Timer timer = startTimer();
219
220      try {
221        return timeable.call();
222      } catch (RuntimeException e) {
223        throw e;
224      } catch (Exception e) {
225        throw new RuntimeException(e);
226      } finally {
227        timer.observeDuration();
228      }
229    }
230
231    public static class Value {
232      public final double sum;
233      public final double[] buckets;
234      public final long created;
235
236      public Value(double sum, double[] buckets, long created) {
237        this.sum = sum;
238        this.buckets = buckets;
239        this.created = created;
240      }
241    }
242
243    private Child(double[] buckets) {
244      upperBounds = buckets;
245      cumulativeCounts = new DoubleAdder[buckets.length];
246      for (int i = 0; i < buckets.length; ++i) {
247        cumulativeCounts[i] = new DoubleAdder();
248      }
249    }
250    private final double[] upperBounds;
251    private final DoubleAdder[] cumulativeCounts;
252    private final DoubleAdder sum = new DoubleAdder();
253    private final long created = System.currentTimeMillis();
254
255
256    /**
257     * Observe the given amount.
258     */
259    public void observe(double amt) {
260      for (int i = 0; i < upperBounds.length; ++i) {
261        // The last bucket is +Inf, so we always increment.
262        if (amt <= upperBounds[i]) {
263          cumulativeCounts[i].add(1);
264          break;
265        }
266      }
267      sum.add(amt);
268    }
269    /**
270     * Start a timer to track a duration.
271     * <p>
272     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
273     */
274    public Timer startTimer() {
275      return new Timer(this, SimpleTimer.defaultTimeProvider.nanoTime());
276    }
277    /**
278     * Get the value of the Histogram.
279     * <p>
280     * <em>Warning:</em> The definition of {@link Value} is subject to change.
281     */
282    public Value get() {
283      double[] buckets = new double[cumulativeCounts.length];
284      double acc = 0;
285      for (int i = 0; i < cumulativeCounts.length; ++i) {
286        acc += cumulativeCounts[i].sum();
287        buckets[i] = acc;
288      }
289      return new Value(sum.sum(), buckets, created);
290    }
291  }
292
293  // Convenience methods.
294  /**
295   * Observe the given amount on the histogram with no labels.
296   */
297  public void observe(double amt) {
298    noLabelsChild.observe(amt);
299  }
300  /**
301   * Start a timer to track a duration on the histogram with no labels.
302   * <p>
303   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
304   */
305  public Timer startTimer() {
306    return noLabelsChild.startTimer();
307  }
308
309  /**
310   * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
311   *
312   * @param timeable Code that is being timed
313   * @return Measured duration in seconds for timeable to complete.
314   */
315  public double time(Runnable timeable){
316    return noLabelsChild.time(timeable);
317  }
318
319  /**
320   * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run.
321   *
322   * @param timeable Code that is being timed
323   * @return Result returned by callable.
324   */
325  public <E> E time(Callable<E> timeable){
326    return noLabelsChild.time(timeable);
327  }
328
329  @Override
330  public List<MetricFamilySamples> collect() {
331    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
332    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
333      Child.Value v = c.getValue().get();
334      List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
335      labelNamesWithLe.add("le");
336      for (int i = 0; i < v.buckets.length; ++i) {
337        List<String> labelValuesWithLe = new ArrayList<String>(c.getKey());
338        labelValuesWithLe.add(doubleToGoString(buckets[i]));
339        samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i]));
340      }
341      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1]));
342      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
343      samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), v.created / 1000.0));
344    }
345
346    return familySamplesList(Type.HISTOGRAM, samples);
347  }
348
349  @Override
350  public List<MetricFamilySamples> describe() {
351    return Collections.singletonList(
352            new MetricFamilySamples(fullname, Type.HISTOGRAM, help, Collections.<MetricFamilySamples.Sample>emptyList()));
353  }
354
355  double[] getBuckets() {
356    return buckets;
357  }
358
359
360}