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