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    @Override
084    public CounterSnapshot collect() {
085        return (CounterSnapshot) super.collect();
086    }
087
088    @Override
089    protected boolean isExemplarsEnabled() {
090        return exemplarsEnabled;
091    }
092
093    @Override
094    protected DataPoint newDataPoint() {
095        if (isExemplarsEnabled()) {
096            return new DataPoint(new ExemplarSampler(exemplarSamplerConfig));
097        } else {
098            return new DataPoint(null);
099        }
100    }
101
102    @Override
103    protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
104        List<CounterSnapshot.CounterDataPointSnapshot> data = new ArrayList<>(labels.size());
105        for (int i = 0; i < labels.size(); i++) {
106            data.add(metricData.get(i).collect(labels.get(i)));
107        }
108        return new CounterSnapshot(getMetadata(), data);
109    }
110
111    static String stripTotalSuffix(String name) {
112        if (name != null && (name.endsWith("_total") || name.endsWith(".total"))) {
113            name = name.substring(0, name.length() - 6);
114        }
115        return name;
116    }
117
118    class DataPoint implements CounterDataPoint {
119
120        private final DoubleAdder doubleValue = new DoubleAdder();
121        // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations,
122        // and DoubleAdder for double observations. If the user doesn't observe any double at all,
123        // we will be using the LongAdder and get the best performance.
124        private final LongAdder longValue = new LongAdder();
125        private final long createdTimeMillis = System.currentTimeMillis();
126        private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false
127
128        private DataPoint(ExemplarSampler exemplarSampler) {
129            this.exemplarSampler = exemplarSampler;
130        }
131
132        /**
133         * {@inheritDoc}
134         */
135        @Override
136        public void inc(long amount) {
137            validateAndAdd(amount);
138            if (isExemplarsEnabled()) {
139                exemplarSampler.observe(amount);
140            }
141        }
142
143        /**
144         * {@inheritDoc}
145         */
146        @Override
147        public void inc(double amount) {
148            validateAndAdd(amount);
149            if (isExemplarsEnabled()) {
150                exemplarSampler.observe(amount);
151            }
152        }
153
154        /**
155         * {@inheritDoc}
156         */
157        @Override
158        public void incWithExemplar(long amount, Labels labels) {
159            validateAndAdd(amount);
160            if (isExemplarsEnabled()) {
161                exemplarSampler.observeWithExemplar(amount, labels);
162            }
163        }
164
165        /**
166         * {@inheritDoc}
167         */
168        @Override
169        public void incWithExemplar(double amount, Labels labels) {
170            validateAndAdd(amount);
171            if (isExemplarsEnabled()) {
172                exemplarSampler.observeWithExemplar(amount, labels);
173            }
174        }
175
176        private void validateAndAdd(long amount) {
177            if (amount < 0) {
178                throw new IllegalArgumentException("Negative increment " + amount + " is illegal for Counter metrics.");
179            }
180            longValue.add(amount);
181        }
182
183        private void validateAndAdd(double amount) {
184            if (amount < 0) {
185                throw new IllegalArgumentException("Negative increment " + amount + " is illegal for Counter metrics.");
186            }
187            doubleValue.add(amount);
188        }
189
190        private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) {
191            // Read the exemplar first. Otherwise, there is a race condition where you might
192            // see an Exemplar for a value that's not counted yet.
193            // If there are multiple Exemplars (by default it's just one), use the newest.
194            Exemplar latestExemplar = null;
195            if (exemplarSampler != null) {
196                for (Exemplar exemplar : exemplarSampler.collect()) {
197                    if (latestExemplar == null || exemplar.getTimestampMillis() > latestExemplar.getTimestampMillis()) {
198                        latestExemplar = exemplar;
199                    }
200                }
201            }
202            return new CounterSnapshot.CounterDataPointSnapshot(longValue.sum() + doubleValue.sum(), labels, latestExemplar, createdTimeMillis);
203        }
204    }
205
206    public static Builder builder() {
207        return new Builder(PrometheusProperties.get());
208    }
209
210    public static Builder builder(PrometheusProperties config) {
211        return new Builder(config);
212    }
213
214    public static class Builder extends StatefulMetric.Builder<Builder, Counter> {
215
216        private Builder(PrometheusProperties properties) {
217            super(Collections.emptyList(), properties);
218        }
219
220        /**
221         * The {@code _total} suffix will automatically be appended if it's missing.
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         * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in Prometheus.
231         * <p>
232         * Throws an {@link IllegalArgumentException} if
233         * {@link io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) MetricMetadata.isValidMetricName(name)}
234         * is {@code false}.
235         */
236        @Override
237        public Builder name(String name) {
238            return super.name(stripTotalSuffix(name));
239        }
240
241        @Override
242        public Counter build() {
243            return new Counter(this, properties);
244        }
245
246        @Override
247        protected Builder self() {
248            return this;
249        }
250    }
251}