001package io.prometheus.client;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.Map;
007
008/**
009 * Counter metric, to track counts of events or running totals.
010 * <p>
011 * Example of Counters include:
012 * <ul>
013 *  <li>Number of requests processed</li>
014 *  <li>Number of items that were inserted into a queue</li>
015 *  <li>Total amount of data a system has processed</li>
016 * </ul>
017 *
018 * Counters can only go up (and be reset), if your use case can go down you should use a {@link Gauge} instead.
019 * Use the <code>rate()</code> function in Prometheus to calculate the rate of increase of a Counter.
020 * By convention, the names of Counters are suffixed by <code>_total</code>.
021 *
022 * <p>
023 * An example Counter:
024 * <pre>
025 * {@code
026 *   class YourClass {
027 *     static final Counter requests = Counter.build()
028 *         .name("requests_total").help("Total requests.").register();
029 *     static final Counter failedRequests = Counter.build()
030 *         .name("requests_failed_total").help("Total failed requests.").register();
031 *
032 *     void processRequest() {
033 *        requests.inc();
034 *        try {
035 *          // Your code here.
036 *        } catch (Exception e) {
037 *          failedRequests.inc();
038 *          throw e;
039 *        }
040 *     }
041 *   }
042 * }
043 * </pre>
044 *
045 * <p>
046 * You can also use labels to track different types of metric:
047 * <pre>
048 * {@code
049 *   class YourClass {
050 *     static final Counter requests = Counter.build()
051 *         .name("requests_total").help("Total requests.")
052 *         .labelNames("method").register();
053 *
054 *     void processGetRequest() {
055 *        requests.labels("get").inc();
056 *        // Your code here.
057 *     }
058 *     void processPostRequest() {
059 *        requests.labels("post").inc();
060 *        // Your code here.
061 *     }
062 *   }
063 * }
064 * </pre>
065 * These can be aggregated and processed together much more easily in the Prometheus
066 * server than individual metrics for each labelset.
067 *
068 * If there is a suffix of <code>_total</code> on the metric name, it will be
069 * removed. When exposing the time series for counter value, a
070 * <code>_total</code> suffix will be added. This is for compatibility between
071 * OpenMetrics and the Prometheus text format, as OpenMetrics requires the
072 * <code>_total</code> suffix.
073 */
074public class Counter extends SimpleCollector<Counter.Child> implements Collector.Describable {
075
076  Counter(Builder b) {
077    super(b);
078  }
079
080  public static class Builder extends SimpleCollector.Builder<Builder, Counter> {
081    @Override
082    public Counter create() {
083      // Gracefully handle pre-OpenMetrics counters.
084      if (name.endsWith("_total")) {
085        name = name.substring(0, name.length() - 6);
086      }
087      return new Counter(this);
088    }
089  }
090
091  /**
092   *  Return a Builder to allow configuration of a new Counter. Ensures required fields are provided.
093   *
094   *  @param name The name of the metric
095   *  @param help The help string of the metric
096   */
097  public static Builder build(String name, String help) {
098    return new Builder().name(name).help(help);
099  }
100
101  /**
102   *  Return a Builder to allow configuration of a new Counter.
103   */
104  public static Builder build() {
105    return new Builder();
106  }
107
108  @Override
109  protected Child newChild() {
110    return new Child();
111  }
112
113  /**
114   * The value of a single Counter.
115   * <p>
116   * <em>Warning:</em> References to a Child become invalid after using
117   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear},
118   */
119  public static class Child {
120    private final DoubleAdder value = new DoubleAdder();
121    private final long created = System.currentTimeMillis();
122    /**
123     * Increment the counter by 1.
124     */
125    public void inc() {
126      inc(1);
127    }
128    /**
129     * Increment the counter by the given amount.
130     * @throws IllegalArgumentException If amt is negative.
131     */
132    public void inc(double amt) {
133      if (amt < 0) {
134        throw new IllegalArgumentException("Amount to increment must be non-negative.");
135      }
136      value.add(amt);
137    }
138    /**
139     * Get the value of the counter.
140     */
141    public double get() {
142      return value.sum();
143    }
144    /**
145     * Get the created time of the counter in milliseconds.
146     */
147    public long created() {
148      return created;
149    }
150  }
151
152  // Convenience methods.
153  /**
154   * Increment the counter with no labels by 1.
155   */
156  public void inc() {
157    inc(1);
158  }
159  /**
160   * Increment the counter with no labels by the given amount.
161   * @throws IllegalArgumentException If amt is negative.
162   */
163  public void inc(double amt) {
164    noLabelsChild.inc(amt);
165  }
166  
167  /**
168   * Get the value of the counter.
169   */
170  public double get() {
171    return noLabelsChild.get();
172  }
173
174  @Override
175  public List<MetricFamilySamples> collect() {
176    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size());
177    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
178      samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get()));
179      samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0));
180    }
181    return familySamplesList(Type.COUNTER, samples);
182  }
183
184  @Override
185  public List<MetricFamilySamples> describe() {
186    return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames));
187  }
188}