001package io.prometheus.client;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Map;
006
007/**
008 * Summary metric, to track the size of events.
009 * <p>
010 * Example of uses for Summaries include:
011 * <ul>
012 *  <li>Response latency</li>
013 *  <li>Request size</li>
014 * </ul>
015 * 
016 * <p>
017 * Example Summaries:
018 * <pre>
019 * {@code
020 *   class YourClass {
021 *     static final Summary receivedBytes = Summary.build()
022 *         .name("requests_size_bytes").help("Request size in bytes.").register();
023 *     static final Summary requestLatency = Summary.build()
024 *         .name("requests_latency_seconds").help("Request latency in seconds.").register();
025 *
026 *     void processRequest(Request req) {  
027 *        Summary.Timer requestTimer = requestLatency.startTimer();
028 *        try {
029 *          // Your code here.
030 *        } finally {
031 *          receivedBytes.observe(req.size());
032 *          requestTimer.observeDuration();
033 *        }
034 *     }
035 *   }
036 * }
037 * </pre>
038 * This would allow you to track request rate, average latency and average request size.
039 */
040public class Summary extends SimpleCollector<Summary.Child> {
041
042  Summary(Builder b) {
043    super(b);
044  }
045
046  public static class Builder extends SimpleCollector.Builder<Builder, Summary> {
047    @Override
048    public Summary create() {
049      return new Summary(this);
050    }
051  }
052
053  /**
054   *  Return a Builder to allow configuration of a new Summary.
055   */
056  public static Builder build() {
057    return new Builder();
058  }
059
060  @Override
061  protected Child newChild() {
062    return new Child();
063  }
064
065  /**
066   * Represents an event being timed.
067   */
068  public static class Timer {
069    Child child;
070    long start;
071    private Timer(Child child) {
072      this.child = child;
073      start = Child.timeProvider.nanoTime();
074    }
075    /**
076     * Observe the amount of time in seconds since {@link Child#startTimer} was called.
077     * @return Measured duration in seconds since {@link Child#startTimer} was called.
078     */
079    public double observeDuration() {
080      double elapsed = (Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND;
081      child.observe(elapsed);
082      return elapsed;
083    }
084  }
085
086  /**
087   * The value of a single Summary.
088   * <p>
089   * <em>Warning:</em> References to a Child become invalid after using
090   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
091   */
092  public static class Child {
093    public static class Value {
094      private double count;  
095      private double sum;
096    }
097
098    // Having these seperate leaves us open to races,
099    // however Prometheus as whole has other races
100    // that mean adding atomicity here wouldn't be useful.
101    // This should be reevaluated in the future.
102    private DoubleAdder count = new DoubleAdder();  
103    private DoubleAdder sum = new DoubleAdder();
104
105    static TimeProvider timeProvider = new TimeProvider();
106    /**
107     * Observe the given amount.
108     */
109    public void observe(double amt) {
110      count.add(1);
111      sum.add(amt);
112    }
113    /**
114     * Start a timer to track a duration.
115     * <p>
116     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
117     */
118    public Timer startTimer() {
119      return new Timer(this);
120    }
121    /**
122     * Get the value of the Summary.
123     * <p>
124     * <em>Warning:</em> The definition of {@link Value} is subject to change.
125     */
126    public Value get() {
127      Value v = new Value();
128      v.count = count.sum();
129      v.sum = sum.sum();
130      return v;
131    }
132  }
133
134  // Convenience methods.
135  /**
136   * Observe the given amount on the summary with no labels.
137   */
138  public void observe(double amt) {
139    noLabelsChild.observe(amt);
140  }
141  /**
142   * Start a timer to track a duration on the summary with no labels.
143   * <p>
144   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
145   */
146  public Timer startTimer() {
147    return noLabelsChild.startTimer();
148  }
149
150  @Override
151  public List<MetricFamilySamples> collect() {
152    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
153    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
154      Child.Value v = c.getValue().get();
155      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.count));
156      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
157    }
158
159    MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.SUMMARY, help, samples);
160    List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
161    mfsList.add(mfs);
162    return mfsList;
163  }
164
165  static class TimeProvider {
166    long nanoTime() {
167      return System.nanoTime();
168    }
169  }
170}