001package io.prometheus.client;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Map;
006
007/**
008 * Histogram metric, to track distributions of events.
009 * <p>
010 * Example of uses for Histograms include:
011 * <ul>
012 *  <li>Response latency</li>
013 *  <li>Request size</li>
014 * </ul>
015 * <p>
016 * <em>Note:</em> Each bucket is one timeseries. Many buckets and/or many dimensions with labels
017 * can produce large amount of time series, that may cause performance problems.
018 * 
019 * <p>
020 * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
021 * <p>
022 * Example Histograms:
023 * <pre>
024 * {@code
025 *   class YourClass {
026 *     static final Histogram requestLatency = Histogram.build()
027 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
028 *
029 *     void processRequest(Request req) {  
030 *        Histogram.Timer requestTimer = requestLatency.startTimer();
031 *        try {
032 *          // Your code here.
033 *        } finally {
034 *          requestTimer.observeDuration();
035 *        }
036 *     }
037 *   }
038 * }
039 * </pre>
040 * <p>
041 * You can choose your own buckets:
042 * <pre>
043 * {@code
044 *     static final Histogram requestLatency = Histogram.build()
045 *         .buckets(.01, .02, .03, .04)
046 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
047 * }
048 * </pre>
049 * {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and
050 * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets}
051 * offer easy ways to set common bucket patterns.
052 */
053public class Histogram extends SimpleCollector<Histogram.Child> {
054  private final double[] buckets;
055
056  Histogram(Builder b) {
057    super(b);
058    buckets = b.buckets;
059    initializeNoLabelsChild();
060  }
061
062  public static class Builder extends SimpleCollector.Builder<Builder, Histogram> {
063    private double[] buckets = new double[]{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10};
064
065    @Override
066    public Histogram create() {
067      for (int i = 0; i < buckets.length - 1; i++) {
068        if (buckets[i] >= buckets[i + 1]) {
069          throw new IllegalStateException("Histogram buckets must be in increasing order: "
070              + buckets[i] + " >= " + buckets[i + 1]);
071        }
072      }
073      if (buckets.length == 0) {
074          throw new IllegalStateException("Histogram must have at least one bucket.");
075      }
076      for (String label: labelNames) {
077        if (label.equals("le")) {
078            throw new IllegalStateException("Histogram cannot have a label named 'le'.");
079        }
080      }
081
082      // Append infinity bucket if it's not already there.
083      if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) {
084        double[] tmp = new double[buckets.length + 1];
085        System.arraycopy(buckets, 0, tmp, 0, buckets.length);
086        tmp[buckets.length] = Double.POSITIVE_INFINITY;
087        buckets = tmp;
088      }
089      dontInitializeNoLabelsChild = true;
090      return new Histogram(this);
091    }
092   
093    /**
094      * Set the upper bounds of buckets for the histogram.
095      */
096    public Builder buckets(double... buckets) {
097      this.buckets = buckets;
098      return this;
099    }
100
101    /**
102      * Set the upper bounds of buckets for the histogram with a linear sequence.
103      */
104    public Builder linearBuckets(double start, double width, int count) {
105      buckets = new double[count];
106      for (int i = 0; i < count; i++){
107        buckets[i] = start + i*width;
108      }
109      return this;
110    }
111    /**
112      * Set the upper bounds of buckets for the histogram with an exponential sequence.
113      */
114    public Builder exponentialBuckets(double start, double factor, int count) {
115      buckets = new double[count];
116      for (int i = 0; i < count; i++) {
117        buckets[i] = start * Math.pow(factor, i);
118      }
119      return this;
120    }
121    
122  }
123
124  /**
125   *  Return a Builder to allow configuration of a new Histogram.
126   */
127  public static Builder build() {
128    return new Builder();
129  }
130
131  @Override
132  protected Child newChild() {
133    return new Child(buckets);
134  }
135
136  /**
137   * Represents an event being timed.
138   */
139  public static class Timer {
140    private final Child child;
141    private final long start;
142    private Timer(Child child) {
143      this.child = child;
144      start = Child.timeProvider.nanoTime();
145    }
146    /**
147     * Observe the amount of time in seconds since {@link Child#startTimer} was called.
148     * @return Measured duration in seconds since {@link Child#startTimer} was called.
149     */
150    public double observeDuration() {
151        double elapsed = (Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND;
152        child.observe(elapsed);
153        return elapsed;
154    }
155  }
156
157  /**
158   * The value of a single Histogram.
159   * <p>
160   * <em>Warning:</em> References to a Child become invalid after using
161   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
162   */
163  public static class Child {
164    public static class Value {
165      public final double sum;
166      public final double[] buckets;
167
168      public Value(double sum, double[] buckets) {
169        this.sum = sum;
170        this.buckets = buckets;
171      }
172    }
173
174    private Child(double[] buckets) {
175      upperBounds = buckets;
176      cumulativeCounts = new DoubleAdder[buckets.length];
177      for (int i = 0; i < buckets.length; ++i) {
178        cumulativeCounts[i] = new DoubleAdder();
179      }
180    }
181    private final double[] upperBounds;
182    private final DoubleAdder[] cumulativeCounts;
183    private final DoubleAdder sum = new DoubleAdder();
184
185    static TimeProvider timeProvider = new TimeProvider();
186    /**
187     * Observe the given amount.
188     */
189    public void observe(double amt) {
190      for (int i = 0; i < upperBounds.length; ++i) {
191        // The last bucket is +Inf, so we always increment.
192        if (amt <= upperBounds[i]) {
193          cumulativeCounts[i].add(1);
194          break;
195        }
196      }
197      sum.add(amt);
198    }
199    /**
200     * Start a timer to track a duration.
201     * <p>
202     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
203     */
204    public Timer startTimer() {
205      return new Timer(this);
206    }
207    /**
208     * Get the value of the Histogram.
209     * <p>
210     * <em>Warning:</em> The definition of {@link Value} is subject to change.
211     */
212    public Value get() {
213      double[] buckets = new double[cumulativeCounts.length];
214      double acc = 0;
215      for (int i = 0; i < cumulativeCounts.length; ++i) {
216        acc += cumulativeCounts[i].sum();
217        buckets[i] = acc;
218      }
219      return new Value(sum.sum(), buckets);
220    }
221  }
222
223  // Convenience methods.
224  /**
225   * Observe the given amount on the histogram with no labels.
226   */
227  public void observe(double amt) {
228    noLabelsChild.observe(amt);
229  }
230  /**
231   * Start a timer to track a duration on the histogram with no labels.
232   * <p>
233   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
234   */
235  public Timer startTimer() {
236    return noLabelsChild.startTimer();
237  }
238
239  @Override
240  public List<MetricFamilySamples> collect() {
241    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
242    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
243      Child.Value v = c.getValue().get();
244      List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
245      labelNamesWithLe.add("le");
246      for (int i = 0; i < v.buckets.length; ++i) {
247        List<String> labelValuesWithLe = new ArrayList<String>(c.getKey());
248        labelValuesWithLe.add(doubleToGoString(buckets[i]));
249        samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i]));
250      }
251      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1]));
252      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
253    }
254
255    MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.HISTOGRAM, help, samples);
256    List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
257    mfsList.add(mfs);
258    return mfsList;
259  }
260
261  double[] getBuckets() {
262    return buckets;
263  }
264
265  static class TimeProvider {
266    long nanoTime() {
267      return System.nanoTime();
268    }
269  }
270}