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, Histogram> {
054  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> {
063    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        for (int i = 0; i < buckets.length; i++) {
086          tmp[i] = buckets[i];
087        }
088        tmp[buckets.length] = Double.POSITIVE_INFINITY;
089        buckets = tmp;
090      }
091      dontInitializeNoLabelsChild = true;
092      return new Histogram(this);
093    }
094   
095    /**
096      * Set the upper bounds of buckets for the histogram.
097      */
098    public Builder buckets(double... buckets) {
099      this.buckets = buckets;
100      return this;
101    }
102
103    /**
104      * Set the upper bounds of buckets for the histogram with a linear sequence.
105      */
106    public Builder linearBuckets(double start, double width, int count) {
107      buckets = new double[count];
108      for (int i = 0; i < count; i++){
109        buckets[i] = start + i*width;
110      }
111      return this;
112    }
113    /**
114      * Set the upper bounds of buckets for the histogram with an exponential sequence.
115      */
116    public Builder exponentialBuckets(double start, double factor, int count) {
117      buckets = new double[count];
118      for (int i = 0; i < count; i++) {
119        buckets[i] = start * Math.pow(factor, i);
120      }
121      return this;
122    }
123    
124  }
125
126  /**
127   *  Return a Builder to allow configuration of a new Histogram.
128   */
129  public static Builder build() {
130    return new Builder();
131  }
132
133  @Override
134  protected Child newChild() {
135    return new Child(buckets);
136  }
137
138  /**
139   * Represents an event being timed.
140   */
141  public static class Timer {
142    Child child;
143    long start;
144    private Timer(Child child) {
145      this.child = child;
146      start = Child.timeProvider.nanoTime();
147    }
148    /**
149     * Observe the amount of time in seconds since {@link Child#startTimer} was called.
150     */
151    public void observeDuration() {
152      child.observe((Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND);
153    }
154  }
155
156  /**
157   * The value of a single Histogram.
158   * <p>
159   * <em>Warning:</em> References to a Child become invalid after using
160   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
161   */
162  public static class Child {
163    public static class Value {
164      private double sum;  
165      private double[] buckets;
166    }
167
168    private Child(double[] buckets) {
169      upperBounds = buckets;
170      cumulativeCounts = new DoubleAdder[buckets.length];
171      for (int i = 0; i < buckets.length; ++i) {
172        cumulativeCounts[i] = new DoubleAdder();
173      }
174    }
175    private double[] upperBounds;
176    private DoubleAdder[] cumulativeCounts;
177    private DoubleAdder sum = new DoubleAdder();
178
179    static TimeProvider timeProvider = new TimeProvider();
180    /**
181     * Observe the given amount.
182     */
183    public void observe(double amt) {
184      for (int i = 0; i < upperBounds.length; ++i) {
185        // The last bucket is +Inf, so we always increment.
186        if (amt <= upperBounds[i]) {
187          cumulativeCounts[i].add(1);
188          break;
189        }
190      }
191      sum.add(amt);
192    }
193    /**
194     * Start a timer to track a duration.
195     * <p>
196     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
197     */
198    public Timer startTimer() {
199      return new Timer(this);
200    }
201    /**
202     * Get the value of the Histogram.
203     * <p>
204     * <em>Warning:</em> The definition of {@link Value} is subject to change.
205     */
206    public Value get() {
207      Value v = new Value();
208      v.buckets = new double[cumulativeCounts.length];
209      double acc = 0;
210      for (int i = 0; i < cumulativeCounts.length; ++i) {
211        acc += cumulativeCounts[i].sum();
212        v.buckets[i] = acc;
213      }
214      v.sum = sum.sum();
215      return v;
216    }
217  }
218
219  // Convenience methods.
220  /**
221   * Observe the given amount on the histogram with no labels.
222   */
223  public void observe(double amt) {
224    noLabelsChild.observe(amt);
225  }
226  /**
227   * Start a timer to track a duration on the histogram with no labels.
228   * <p>
229   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
230   */
231  public Timer startTimer() {
232    return noLabelsChild.startTimer();
233  }
234
235  @Override
236  public List<MetricFamilySamples> collect() {
237    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
238    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
239      Child.Value v = c.getValue().get();
240      List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
241      labelNamesWithLe.add("le");
242      for (int i = 0; i < v.buckets.length; ++i) {
243        List<String> labelValuesWithLe = new ArrayList<String>(c.getKey());
244        labelValuesWithLe.add(doubleToGoString(buckets[i]));
245        samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i]));
246      }
247      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1]));
248      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
249    }
250
251    MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.HISTOGRAM, help, samples);
252    List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
253    mfsList.add(mfs);
254    return mfsList;
255  }
256
257  static class TimeProvider {
258    long nanoTime() {
259      return System.nanoTime();
260    }
261  }
262}