001package io.prometheus.metrics.core.metrics;
002
003import io.prometheus.metrics.config.MetricsProperties;
004import io.prometheus.metrics.config.PrometheusProperties;
005import io.prometheus.metrics.core.datapoints.CounterDataPoint;
006import io.prometheus.metrics.core.exemplars.ExemplarSampler;
007import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig;
008import io.prometheus.metrics.model.snapshots.CounterSnapshot;
009import io.prometheus.metrics.model.snapshots.Exemplar;
010import io.prometheus.metrics.model.snapshots.Labels;
011import java.util.ArrayList;
012import java.util.Collections;
013import java.util.List;
014import java.util.concurrent.atomic.DoubleAdder;
015import java.util.concurrent.atomic.LongAdder;
016
017/**
018 * Counter metric.
019 *
020 * <p>Example usage:
021 *
022 * <pre>{@code
023 * Counter requestCount = Counter.builder()
024 *     .name("requests_total")
025 *     .help("Total number of requests")
026 *     .labelNames("path", "status")
027 *     .register();
028 * requestCount.labelValues("/hello-world", "200").inc();
029 * requestCount.labelValues("/hello-world", "500").inc();
030 * }</pre>
031 */
032public class Counter extends StatefulMetric<CounterDataPoint, Counter.DataPoint>
033    implements CounterDataPoint {
034
035  private final boolean exemplarsEnabled;
036  private final ExemplarSamplerConfig exemplarSamplerConfig;
037
038  private Counter(Builder builder, PrometheusProperties prometheusProperties) {
039    super(builder);
040    MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
041    exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
042    if (exemplarsEnabled) {
043      exemplarSamplerConfig =
044          new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1);
045    } else {
046      exemplarSamplerConfig = null;
047    }
048  }
049
050  @Override
051  public void inc(long amount) {
052    getNoLabels().inc(amount);
053  }
054
055  @Override
056  public void inc(double amount) {
057    getNoLabels().inc(amount);
058  }
059
060  @Override
061  public void incWithExemplar(long amount, Labels labels) {
062    getNoLabels().incWithExemplar(amount, labels);
063  }
064
065  @Override
066  public void incWithExemplar(double amount, Labels labels) {
067    getNoLabels().incWithExemplar(amount, labels);
068  }
069
070  @Override
071  public double get() {
072    return getNoLabels().get();
073  }
074
075  @Override
076  public long getLongValue() {
077    return getNoLabels().getLongValue();
078  }
079
080  @Override
081  public CounterSnapshot collect() {
082    return (CounterSnapshot) super.collect();
083  }
084
085  @Override
086  protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
087    List<CounterSnapshot.CounterDataPointSnapshot> data = new ArrayList<>(labels.size());
088    for (int i = 0; i < labels.size(); i++) {
089      data.add(metricData.get(i).collect(labels.get(i)));
090    }
091    return new CounterSnapshot(getMetadata(), data);
092  }
093
094  @Override
095  protected boolean isExemplarsEnabled() {
096    return exemplarsEnabled;
097  }
098
099  @Override
100  protected DataPoint newDataPoint() {
101    if (isExemplarsEnabled()) {
102      return new DataPoint(new ExemplarSampler(exemplarSamplerConfig));
103    } else {
104      return new DataPoint(null);
105    }
106  }
107
108  static String stripTotalSuffix(String name) {
109    if (name != null && (name.endsWith("_total") || name.endsWith(".total"))) {
110      name = name.substring(0, name.length() - 6);
111    }
112    return name;
113  }
114
115  class DataPoint implements CounterDataPoint {
116
117    private final DoubleAdder doubleValue = new DoubleAdder();
118    // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations,
119    // and DoubleAdder for double observations. If the user doesn't observe any double at all,
120    // we will be using the LongAdder and get the best performance.
121    private final LongAdder longValue = new LongAdder();
122    private final long createdTimeMillis = System.currentTimeMillis();
123    private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false
124
125    private DataPoint(ExemplarSampler exemplarSampler) {
126      this.exemplarSampler = exemplarSampler;
127    }
128
129    @Override
130    public double get() {
131      return longValue.sum() + doubleValue.sum();
132    }
133
134    @Override
135    public long getLongValue() {
136      return longValue.sum() + (long) doubleValue.sum();
137    }
138
139    @Override
140    public void inc(long amount) {
141      validateAndAdd(amount);
142      if (isExemplarsEnabled()) {
143        exemplarSampler.observe((double) amount);
144      }
145    }
146
147    @Override
148    public void inc(double amount) {
149      validateAndAdd(amount);
150      if (isExemplarsEnabled()) {
151        exemplarSampler.observe(amount);
152      }
153    }
154
155    @Override
156    public void incWithExemplar(long amount, Labels labels) {
157      validateAndAdd(amount);
158      if (isExemplarsEnabled()) {
159        exemplarSampler.observeWithExemplar((double) amount, labels);
160      }
161    }
162
163    @Override
164    public void incWithExemplar(double amount, Labels labels) {
165      validateAndAdd(amount);
166      if (isExemplarsEnabled()) {
167        exemplarSampler.observeWithExemplar(amount, labels);
168      }
169    }
170
171    private void validateAndAdd(long amount) {
172      if (amount < 0) {
173        throw new IllegalArgumentException(
174            "Negative increment " + amount + " is illegal for Counter metrics.");
175      }
176      longValue.add(amount);
177    }
178
179    private void validateAndAdd(double amount) {
180      if (amount < 0) {
181        throw new IllegalArgumentException(
182            "Negative increment " + amount + " is illegal for Counter metrics.");
183      }
184      doubleValue.add(amount);
185    }
186
187    private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) {
188      // Read the exemplar first. Otherwise, there is a race condition where you might
189      // see an Exemplar for a value that's not counted yet.
190      // If there are multiple Exemplars (by default it's just one), use the newest.
191      Exemplar latestExemplar = null;
192      if (exemplarSampler != null) {
193        for (Exemplar exemplar : exemplarSampler.collect()) {
194          if (latestExemplar == null
195              || exemplar.getTimestampMillis() > latestExemplar.getTimestampMillis()) {
196            latestExemplar = exemplar;
197          }
198        }
199      }
200      return new CounterSnapshot.CounterDataPointSnapshot(
201          get(), labels, latestExemplar, createdTimeMillis);
202    }
203  }
204
205  public static Builder builder() {
206    return new Builder(PrometheusProperties.get());
207  }
208
209  public static Builder builder(PrometheusProperties config) {
210    return new Builder(config);
211  }
212
213  public static class Builder extends StatefulMetric.Builder<Builder, Counter> {
214
215    private Builder(PrometheusProperties properties) {
216      super(Collections.emptyList(), properties);
217    }
218
219    /**
220     * The {@code _total} suffix will automatically be appended if it's missing.
221     *
222     * <pre>{@code
223     * Counter c1 = Counter.builder()
224     *     .name("events_total")
225     *     .build();
226     * Counter c2 = Counter.builder()
227     *     .name("events")
228     *     .build();
229     * }</pre>
230     *
231     * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in
232     * Prometheus.
233     *
234     * <p>Throws an {@link IllegalArgumentException} if {@link
235     * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String)
236     * MetricMetadata.isValidMetricName(name)} is {@code false}.
237     */
238    @Override
239    public Builder name(String name) {
240      return super.name(stripTotalSuffix(name));
241    }
242
243    @Override
244    public Counter build() {
245      return new Counter(this, properties);
246    }
247
248    @Override
249    protected Builder self() {
250      return this;
251    }
252  }
253}