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  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    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     * @return Measured duration in seconds since {@link Child#startTimer} was called.
151     */
152    public double observeDuration() {
153        double elapsed = (Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND;
154        child.observe(elapsed);
155        return elapsed;
156    }
157  }
158
159  /**
160   * The value of a single Histogram.
161   * <p>
162   * <em>Warning:</em> References to a Child become invalid after using
163   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
164   */
165  public static class Child {
166    public static class Value {
167      private double sum;  
168      private double[] buckets;
169    }
170
171    private Child(double[] buckets) {
172      upperBounds = buckets;
173      cumulativeCounts = new DoubleAdder[buckets.length];
174      for (int i = 0; i < buckets.length; ++i) {
175        cumulativeCounts[i] = new DoubleAdder();
176      }
177    }
178    private double[] upperBounds;
179    private DoubleAdder[] cumulativeCounts;
180    private DoubleAdder sum = new DoubleAdder();
181
182    static TimeProvider timeProvider = new TimeProvider();
183    /**
184     * Observe the given amount.
185     */
186    public void observe(double amt) {
187      for (int i = 0; i < upperBounds.length; ++i) {
188        // The last bucket is +Inf, so we always increment.
189        if (amt <= upperBounds[i]) {
190          cumulativeCounts[i].add(1);
191          break;
192        }
193      }
194      sum.add(amt);
195    }
196    /**
197     * Start a timer to track a duration.
198     * <p>
199     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
200     */
201    public Timer startTimer() {
202      return new Timer(this);
203    }
204    /**
205     * Get the value of the Histogram.
206     * <p>
207     * <em>Warning:</em> The definition of {@link Value} is subject to change.
208     */
209    public Value get() {
210      Value v = new Value();
211      v.buckets = new double[cumulativeCounts.length];
212      double acc = 0;
213      for (int i = 0; i < cumulativeCounts.length; ++i) {
214        acc += cumulativeCounts[i].sum();
215        v.buckets[i] = acc;
216      }
217      v.sum = sum.sum();
218      return v;
219    }
220  }
221
222  // Convenience methods.
223  /**
224   * Observe the given amount on the histogram with no labels.
225   */
226  public void observe(double amt) {
227    noLabelsChild.observe(amt);
228  }
229  /**
230   * Start a timer to track a duration on the histogram with no labels.
231   * <p>
232   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
233   */
234  public Timer startTimer() {
235    return noLabelsChild.startTimer();
236  }
237
238  @Override
239  public List<MetricFamilySamples> collect() {
240    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
241    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
242      Child.Value v = c.getValue().get();
243      List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
244      labelNamesWithLe.add("le");
245      for (int i = 0; i < v.buckets.length; ++i) {
246        List<String> labelValuesWithLe = new ArrayList<String>(c.getKey());
247        labelValuesWithLe.add(doubleToGoString(buckets[i]));
248        samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i]));
249      }
250      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1]));
251      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
252    }
253
254    MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.HISTOGRAM, help, samples);
255    List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
256    mfsList.add(mfs);
257    return mfsList;
258  }
259
260  static class TimeProvider {
261    long nanoTime() {
262      return System.nanoTime();
263    }
264  }
265}