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, Summary> {
041
042  Summary(Builder b) {
043    super(b);
044  }
045
046  public static class Builder extends SimpleCollector.Builder<Builder> {
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     */
078    public void observeDuration() {
079      child.observe((Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND);
080    }
081  }
082
083  /**
084   * The value of a single Summary.
085   * <p>
086   * <em>Warning:</em> References to a Child become invalid after using
087   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
088   */
089  public static class Child {
090    public static class Value {
091      private double count;  
092      private double sum;
093    }
094
095    // Having these seperate leaves us open to races,
096    // however Prometheus as whole has other races
097    // that mean adding atomicity here wouldn't be useful.
098    // This should be reevaluated in the future.
099    private DoubleAdder count = new DoubleAdder();  
100    private DoubleAdder sum = new DoubleAdder();
101
102    static TimeProvider timeProvider = new TimeProvider();
103    /**
104     * Observe the given amount.
105     */
106    public void observe(double amt) {
107      count.add(1);
108      sum.add(amt);
109    }
110    /**
111     * Start a timer to track a duration.
112     * <p>
113     * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
114     */
115    public Timer startTimer() {
116      return new Timer(this);
117    }
118    /**
119     * Get the value of the Summary.
120     * <p>
121     * <em>Warning:</em> The definition of {@link Value} is subject to change.
122     */
123    public Value get() {
124      Value v = new Value();
125      v.count = count.sum();
126      v.sum = sum.sum();
127      return v;
128    }
129  }
130
131  // Convenience methods.
132  /**
133   * Observe the given amount on the summary with no labels.
134   */
135  public void observe(double amt) {
136    noLabelsChild.observe(amt);
137  }
138  /**
139   * Start a timer to track a duration on the summary with no labels.
140   * <p>
141   * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of.
142   */
143  public Timer startTimer() {
144    return noLabelsChild.startTimer();
145  }
146
147  @Override
148  public List<MetricFamilySamples> collect() {
149    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
150    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
151      Child.Value v = c.getValue().get();
152      samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.count));
153      samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum));
154    }
155
156    MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.SUMMARY, help, samples);
157    List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
158    mfsList.add(mfs);
159    return mfsList;
160  }
161
162  static class TimeProvider {
163    long nanoTime() {
164      return System.nanoTime();
165    }
166  }
167}