001package io.prometheus.client;
002
003import io.prometheus.client.exemplars.CounterExemplarSampler;
004import io.prometheus.client.exemplars.Exemplar;
005import io.prometheus.client.exemplars.ExemplarConfig;
006
007import java.util.ArrayList;
008import java.util.Collections;
009import java.util.List;
010import java.util.Map;
011import java.util.concurrent.atomic.AtomicReference;
012
013import static java.lang.Boolean.FALSE;
014import static java.lang.Boolean.TRUE;
015
016/**
017 * Counter metric, to track counts of events or running totals.
018 * <p>
019 * Example of Counters include:
020 * <ul>
021 *  <li>Number of requests processed</li>
022 *  <li>Number of items that were inserted into a queue</li>
023 *  <li>Total amount of data a system has processed</li>
024 * </ul>
025 *
026 * Counters can only go up (and be reset), if your use case can go down you should use a {@link Gauge} instead.
027 * Use the <code>rate()</code> function in Prometheus to calculate the rate of increase of a Counter.
028 * By convention, the names of Counters are suffixed by <code>_total</code>.
029 *
030 * <p>
031 * An example Counter:
032 * <pre>
033 * {@code
034 *   class YourClass {
035 *     static final Counter requests = Counter.build()
036 *         .name("requests_total").help("Total requests.").register();
037 *     static final Counter failedRequests = Counter.build()
038 *         .name("requests_failed_total").help("Total failed requests.").register();
039 *
040 *     void processRequest() {
041 *        requests.inc();
042 *        try {
043 *          // Your code here.
044 *        } catch (Exception e) {
045 *          failedRequests.inc();
046 *          throw e;
047 *        }
048 *     }
049 *   }
050 * }
051 * </pre>
052 *
053 * <p>
054 * You can also use labels to track different types of metric:
055 * <pre>
056 * {@code
057 *   class YourClass {
058 *     static final Counter requests = Counter.build()
059 *         .name("requests_total").help("Total requests.")
060 *         .labelNames("method").register();
061 *
062 *     void processGetRequest() {
063 *        requests.labels("get").inc();
064 *        // Your code here.
065 *     }
066 *     void processPostRequest() {
067 *        requests.labels("post").inc();
068 *        // Your code here.
069 *     }
070 *   }
071 * }
072 * </pre>
073 * These can be aggregated and processed together much more easily in the Prometheus
074 * server than individual metrics for each labelset.
075 *
076 * If there is a suffix of <code>_total</code> on the metric name, it will be
077 * removed. When exposing the time series for counter value, a
078 * <code>_total</code> suffix will be added. This is for compatibility between
079 * OpenMetrics and the Prometheus text format, as OpenMetrics requires the
080 * <code>_total</code> suffix.
081 */
082public class Counter extends SimpleCollector<Counter.Child> implements Collector.Describable {
083
084  private final Boolean exemplarsEnabled; // null means default from ExemplarConfig applies
085  private final CounterExemplarSampler exemplarSampler;
086
087  Counter(Builder b) {
088    super(b);
089    this.exemplarsEnabled = b.exemplarsEnabled;
090    this.exemplarSampler = b.exemplarSampler;
091    initializeNoLabelsChild();
092  }
093
094  public static class Builder extends SimpleCollector.Builder<Builder, Counter> {
095
096    private Boolean exemplarsEnabled = null;
097    private CounterExemplarSampler exemplarSampler = null;
098
099    @Override
100    public Counter create() {
101      // Gracefully handle pre-OpenMetrics counters.
102      if (name.endsWith("_total")) {
103        name = name.substring(0, name.length() - 6);
104      }
105      dontInitializeNoLabelsChild = true;
106      return new Counter(this);
107    }
108
109    /**
110     * Enable exemplars and provide a custom {@link CounterExemplarSampler}.
111     */
112    public Builder withExemplarSampler(CounterExemplarSampler exemplarSampler) {
113      if (exemplarSampler == null) {
114        throw new NullPointerException();
115      }
116      this.exemplarSampler = exemplarSampler;
117      return withExemplars();
118    }
119
120    /**
121     * Allow this counter to load exemplars from a {@link CounterExemplarSampler}.
122     * <p>
123     * If a specific exemplar sampler is configured for this counter that exemplar sampler is used
124     * (see {@link #withExemplarSampler(CounterExemplarSampler)}).
125     * Otherwise the default from {@link ExemplarConfig} is used.
126     */
127    public Builder withExemplars() {
128      this.exemplarsEnabled = TRUE;
129      return this;
130    }
131
132    /**
133     * Prevent this counter from loading exemplars from a {@link CounterExemplarSampler}.
134     * <p>
135     * You can still provide exemplars for explicitly individual observations, e.g. using
136     * {@link #incWithExemplar(double, String...)}.
137     */
138    public Builder withoutExemplars() {
139      this.exemplarsEnabled = FALSE;
140      return this;
141    }
142  }
143
144  /**
145   * Return a Builder to allow configuration of a new Counter. Ensures required fields are provided.
146   *
147   * @param name The name of the metric
148   * @param help The help string of the metric
149   */
150  public static Builder build(String name, String help) {
151    return new Builder().name(name).help(help);
152  }
153
154  /**
155   * Return a Builder to allow configuration of a new Counter.
156   */
157  public static Builder build() {
158    return new Builder();
159  }
160
161  @Override
162  protected Child newChild() {
163    return new Child(exemplarsEnabled, exemplarSampler);
164  }
165
166  /**
167   * The value of a single Counter.
168   * <p>
169   * <em>Warning:</em> References to a Child become invalid after using
170   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear},
171   */
172  public static class Child {
173    private final DoubleAdder value = new DoubleAdder();
174    private final long created = System.currentTimeMillis();
175    private final Boolean exemplarsEnabled;
176    private final CounterExemplarSampler exemplarSampler;
177    private final AtomicReference<Exemplar> exemplar = new AtomicReference<Exemplar>();
178
179    public Child() {
180      this(null, null);
181    }
182
183    public Child(Boolean exemplarsEnabled, CounterExemplarSampler exemplarSampler) {
184      this.exemplarsEnabled = exemplarsEnabled;
185      this.exemplarSampler = exemplarSampler;
186    }
187
188    /**
189     * Increment the counter by 1.
190     */
191    public void inc() {
192      inc(1);
193    }
194
195    /**
196     * Same as {@link #incWithExemplar(double, String...) incWithExemplar(1, exemplarLabels)}.
197     */
198    public void incWithExemplar(String... exemplarLabels) {
199      incWithExemplar(1, exemplarLabels);
200    }
201
202    /**
203     * Same as {@link #incWithExemplar(double, Map) incWithExemplar(1, exemplarLabels)}.
204     */
205    public void incWithExemplar(Map<String, String> exemplarLabels) {
206      incWithExemplar(1, exemplarLabels);
207    }
208
209    /**
210     * Increment the counter by the given amount.
211     *
212     * @throws IllegalArgumentException If amt is negative.
213     */
214    public void inc(double amt) {
215      incWithExemplar(amt, (String[]) null);
216    }
217
218    /**
219     * Like {@link #inc(double)}, but additionally creates an exemplar.
220     * <p>
221     * This exemplar takes precedence over any exemplar returned by the {@link CounterExemplarSampler} configured
222     * in {@link ExemplarConfig}.
223     * <p>
224     * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp,
225     * and the specified labels.
226     *
227     * @param amt            same as in {@link #inc(double)}
228     * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}.
229     *                       A commonly used name is {@code "trace_id"}.
230     *                       Calling {@code incWithExemplar(amt)} means that an exemplar without labels will be created.
231     *                       Calling {@code incWithExemplar(amt, (String[]) null)} is equivalent
232     *                       to calling {@code inc(amt)}.
233     */
234    public void incWithExemplar(double amt, String... exemplarLabels) {
235      Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels);
236      if (amt < 0) {
237        throw new IllegalArgumentException("Amount to increment must be non-negative.");
238      }
239      value.add(amt);
240      updateExemplar(amt, exemplar);
241    }
242
243    /**
244     * Same as {@link #incWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}.
245     */
246    public void incWithExemplar(double amt, Map<String, String> exemplarLabels) {
247      incWithExemplar(amt, Exemplar.mapToArray(exemplarLabels));
248    }
249
250    private void updateExemplar(double amt, Exemplar userProvidedExemplar) {
251      Exemplar prev, next;
252      do {
253        prev = exemplar.get();
254        if (userProvidedExemplar == null) {
255          next = sampleNextExemplar(amt, prev);
256        } else {
257          next = userProvidedExemplar;
258        }
259        if (next == null || next == prev) {
260          return;
261        }
262      } while (!exemplar.compareAndSet(prev, next));
263    }
264
265    private Exemplar sampleNextExemplar(double amt, Exemplar prev) {
266      if (FALSE.equals(exemplarsEnabled)) {
267        return null;
268      }
269      if (exemplarSampler != null) {
270        return exemplarSampler.sample(amt, prev);
271      }
272      if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) {
273        CounterExemplarSampler exemplarSampler = ExemplarConfig.getCounterExemplarSampler();
274        if (exemplarSampler != null) {
275          return exemplarSampler.sample(amt, prev);
276        }
277      }
278      return null;
279    }
280
281    /**
282     * Get the value of the counter.
283     */
284    public double get() {
285      return value.sum();
286    }
287
288    private Exemplar getExemplar() {
289      return exemplar.get();
290    }
291
292    /**
293     * Get the created time of the counter in milliseconds.
294     */
295    public long created() {
296      return created;
297    }
298  }
299
300  // Convenience methods.
301
302  /**
303   * Increment the counter with no labels by 1.
304   */
305  public void inc() {
306    inc(1);
307  }
308
309  /**
310   * Like {@link Child#incWithExemplar(String...)}, but for the counter without labels.
311   */
312  public void incWithExemplar(String... exemplarLabels) {
313    incWithExemplar(1, exemplarLabels);
314  }
315
316  /**
317   * Like {@link Child#incWithExemplar(Map)}, but for the counter without labels.
318   */
319  public void incWithExemplar(Map<String, String> exemplarLabels) {
320    incWithExemplar(1, exemplarLabels);
321  }
322
323  /**
324   * Increment the counter with no labels by the given amount.
325   *
326   * @throws IllegalArgumentException If amt is negative.
327   */
328  public void inc(double amt) {
329    noLabelsChild.inc(amt);
330  }
331
332  /**
333   * Like {@link Child#incWithExemplar(double, String...)}, but for the counter without labels.
334   */
335  public void incWithExemplar(double amt, String... exemplarLabels) {
336    noLabelsChild.incWithExemplar(amt, exemplarLabels);
337  }
338
339  /**
340   * Like {@link Child#incWithExemplar(double, Map)}, but for the counter without labels.
341   */
342  public void incWithExemplar(double amt, Map<String, String> exemplarLabels) {
343    noLabelsChild.incWithExemplar(amt, exemplarLabels);
344  }
345
346  /**
347   * Get the value of the counter.
348   */
349  public double get() {
350    return noLabelsChild.get();
351  }
352
353  @Override
354  public List<MetricFamilySamples> collect() {
355    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size());
356    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
357      samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get(), c.getValue().getExemplar()));
358      samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0));
359    }
360    return familySamplesList(Type.COUNTER, samples);
361  }
362
363  @Override
364  public List<MetricFamilySamples> describe() {
365    return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames));
366  }
367}