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(Boolean exemplarsEnabled, CounterExemplarSampler exemplarSampler) {
180      this.exemplarsEnabled = exemplarsEnabled;
181      this.exemplarSampler = exemplarSampler;
182    }
183
184    /**
185     * Increment the counter by 1.
186     */
187    public void inc() {
188      inc(1);
189    }
190
191    /**
192     * Same as {@link #incWithExemplar(double, String...) incWithExemplar(1, exemplarLabels)}.
193     */
194    public void incWithExemplar(String... exemplarLabels) {
195      incWithExemplar(1, exemplarLabels);
196    }
197
198    /**
199     * Same as {@link #incWithExemplar(double, Map) incWithExemplar(1, exemplarLabels)}.
200     */
201    public void incWithExemplar(Map<String, String> exemplarLabels) {
202      incWithExemplar(1, exemplarLabels);
203    }
204
205    /**
206     * Increment the counter by the given amount.
207     *
208     * @throws IllegalArgumentException If amt is negative.
209     */
210    public void inc(double amt) {
211      incWithExemplar(amt, (String[]) null);
212    }
213
214    /**
215     * Like {@link #inc(double)}, but additionally creates an exemplar.
216     * <p>
217     * This exemplar takes precedence over any exemplar returned by the {@link CounterExemplarSampler} configured
218     * in {@link ExemplarConfig}.
219     * <p>
220     * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp,
221     * and the specified labels.
222     *
223     * @param amt            same as in {@link #inc(double)}
224     * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}.
225     *                       A commonly used name is {@code "trace_id"}.
226     *                       Calling {@code incWithExemplar(amt)} means that an exemplar without labels will be created.
227     *                       Calling {@code incWithExemplar(amt, (String[]) null)} is equivalent
228     *                       to calling {@code inc(amt)}.
229     */
230    public void incWithExemplar(double amt, String... exemplarLabels) {
231      Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels);
232      if (amt < 0) {
233        throw new IllegalArgumentException("Amount to increment must be non-negative.");
234      }
235      value.add(amt);
236      updateExemplar(amt, exemplar);
237    }
238
239    /**
240     * Same as {@link #incWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}.
241     */
242    public void incWithExemplar(double amt, Map<String, String> exemplarLabels) {
243      incWithExemplar(amt, Exemplar.mapToArray(exemplarLabels));
244    }
245
246    private void updateExemplar(double amt, Exemplar userProvidedExemplar) {
247      Exemplar prev, next;
248      do {
249        prev = exemplar.get();
250        if (userProvidedExemplar == null) {
251          next = sampleNextExemplar(amt, prev);
252        } else {
253          next = userProvidedExemplar;
254        }
255        if (next == null || next == prev) {
256          return;
257        }
258      } while (!exemplar.compareAndSet(prev, next));
259    }
260
261    private Exemplar sampleNextExemplar(double amt, Exemplar prev) {
262      if (FALSE.equals(exemplarsEnabled)) {
263        return null;
264      }
265      if (exemplarSampler != null) {
266        return exemplarSampler.sample(amt, prev);
267      }
268      if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) {
269        CounterExemplarSampler exemplarSampler = ExemplarConfig.getCounterExemplarSampler();
270        if (exemplarSampler != null) {
271          return exemplarSampler.sample(amt, prev);
272        }
273      }
274      return null;
275    }
276
277    /**
278     * Get the value of the counter.
279     */
280    public double get() {
281      return value.sum();
282    }
283
284    private Exemplar getExemplar() {
285      return exemplar.get();
286    }
287
288    /**
289     * Get the created time of the counter in milliseconds.
290     */
291    public long created() {
292      return created;
293    }
294  }
295
296  // Convenience methods.
297
298  /**
299   * Increment the counter with no labels by 1.
300   */
301  public void inc() {
302    inc(1);
303  }
304
305  /**
306   * Like {@link Child#incWithExemplar(String...)}, but for the counter without labels.
307   */
308  public void incWithExemplar(String... exemplarLabels) {
309    incWithExemplar(1, exemplarLabels);
310  }
311
312  /**
313   * Like {@link Child#incWithExemplar(Map)}, but for the counter without labels.
314   */
315  public void incWithExemplar(Map<String, String> exemplarLabels) {
316    incWithExemplar(1, exemplarLabels);
317  }
318
319  /**
320   * Increment the counter with no labels by the given amount.
321   *
322   * @throws IllegalArgumentException If amt is negative.
323   */
324  public void inc(double amt) {
325    noLabelsChild.inc(amt);
326  }
327
328  /**
329   * Like {@link Child#incWithExemplar(double, String...)}, but for the counter without labels.
330   */
331  public void incWithExemplar(double amt, String... exemplarLabels) {
332    noLabelsChild.incWithExemplar(amt, exemplarLabels);
333  }
334
335  /**
336   * Like {@link Child#incWithExemplar(double, Map)}, but for the counter without labels.
337   */
338  public void incWithExemplar(double amt, Map<String, String> exemplarLabels) {
339    noLabelsChild.incWithExemplar(amt, exemplarLabels);
340  }
341
342  /**
343   * Get the value of the counter.
344   */
345  public double get() {
346    return noLabelsChild.get();
347  }
348
349  @Override
350  public List<MetricFamilySamples> collect() {
351    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size());
352    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
353      samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get(), c.getValue().getExemplar()));
354      samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0));
355    }
356    return familySamplesList(Type.COUNTER, samples);
357  }
358
359  @Override
360  public List<MetricFamilySamples> describe() {
361    return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames));
362  }
363}