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.GaugeDataPoint;
006import io.prometheus.metrics.core.exemplars.ExemplarSampler;
007import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig;
008import io.prometheus.metrics.model.snapshots.Exemplar;
009import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
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.AtomicLong;
016
017/**
018 * Gauge metric.
019 * <p>
020 * Example usage:
021 * <pre>{@code
022 * Gauge currentActiveUsers = Gauge.builder()
023 *     .name("current_active_users")
024 *     .help("Number of users that are currently active")
025 *     .labelNames("region")
026 *     .register();
027 *
028 * public void login(String region) {
029 *     currentActiveUsers.labelValues(region).inc();
030 *     // perform login
031 * }
032 *
033 * public void logout(String region) {
034 *     currentActiveUsers.labelValues(region).dec();
035 *     // perform logout
036 * }
037 * }</pre>
038 */
039public class Gauge extends StatefulMetric<GaugeDataPoint, Gauge.DataPoint> implements GaugeDataPoint {
040
041    private final boolean exemplarsEnabled;
042    private final ExemplarSamplerConfig exemplarSamplerConfig;
043
044    private Gauge(Builder builder, PrometheusProperties prometheusProperties) {
045        super(builder);
046        MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
047        exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
048        if (exemplarsEnabled) {
049            exemplarSamplerConfig = new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1);
050        } else {
051            exemplarSamplerConfig = null;
052        }
053    }
054
055    /**
056     * {@inheritDoc}
057     */
058    @Override
059    public void inc(double amount) {
060        getNoLabels().inc(amount);
061    }
062
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    public void incWithExemplar(double amount, Labels labels) {
068        getNoLabels().incWithExemplar(amount, labels);
069    }
070
071    /**
072     * {@inheritDoc}
073     */
074    @Override
075    public void set(double value) {
076        getNoLabels().set(value);
077    }
078
079    /**
080     * {@inheritDoc}
081     */
082    @Override
083    public void setWithExemplar(double value, Labels labels) {
084        getNoLabels().setWithExemplar(value, labels);
085    }
086
087    /**
088     * {@inheritDoc}
089     */
090    @Override
091    public GaugeSnapshot collect() {
092        return (GaugeSnapshot) super.collect();
093    }
094
095    @Override
096    protected GaugeSnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
097        List<GaugeSnapshot.GaugeDataPointSnapshot> dataPointSnapshots = new ArrayList<>(labels.size());
098        for (int i = 0; i < labels.size(); i++) {
099            dataPointSnapshots.add(metricData.get(i).collect(labels.get(i)));
100        }
101        return new GaugeSnapshot(getMetadata(), dataPointSnapshots);
102    }
103
104    @Override
105    protected DataPoint newDataPoint() {
106        if (isExemplarsEnabled()) {
107            return new DataPoint(new ExemplarSampler(exemplarSamplerConfig));
108        } else {
109            return new DataPoint(null);
110        }
111    }
112
113    @Override
114    protected boolean isExemplarsEnabled() {
115        return exemplarsEnabled;
116    }
117
118    class DataPoint implements GaugeDataPoint {
119
120        private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false
121
122        private DataPoint(ExemplarSampler exemplarSampler) {
123            this.exemplarSampler = exemplarSampler;
124        }
125
126        private final AtomicLong value = new AtomicLong(Double.doubleToRawLongBits(0));
127
128        /**
129         * {@inheritDoc}
130         */
131        @Override
132        public void inc(double amount) {
133            long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount));
134            if (isExemplarsEnabled()) {
135                exemplarSampler.observe(Double.longBitsToDouble(next));
136            }
137        }
138
139        /**
140         * {@inheritDoc}
141         */
142        @Override
143        public void incWithExemplar(double amount, Labels labels) {
144            long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount));
145            if (isExemplarsEnabled()) {
146                exemplarSampler.observeWithExemplar(Double.longBitsToDouble(next), labels);
147            }
148        }
149
150        /**
151         * {@inheritDoc}
152         */
153        @Override
154        public void set(double value) {
155            this.value.set(Double.doubleToRawLongBits(value));
156            if (isExemplarsEnabled()) {
157                exemplarSampler.observe(value);
158            }
159        }
160
161        /**
162         * {@inheritDoc}
163         */
164        @Override
165        public void setWithExemplar(double value, Labels labels) {
166            this.value.set(Double.doubleToRawLongBits(value));
167            if (isExemplarsEnabled()) {
168                exemplarSampler.observeWithExemplar(value, labels);
169            }
170        }
171
172        private GaugeSnapshot.GaugeDataPointSnapshot collect(Labels labels) {
173            // Read the exemplar first. Otherwise, there is a race condition where you might
174            // see an Exemplar for a value that's not represented in getValue() yet.
175            // If there are multiple Exemplars (by default it's just one), use the oldest
176            // so that we don't violate min age.
177            Exemplar oldest = null;
178            if (isExemplarsEnabled()) {
179                for (Exemplar exemplar : exemplarSampler.collect()) {
180                    if (oldest == null || exemplar.getTimestampMillis() < oldest.getTimestampMillis()) {
181                        oldest = exemplar;
182                    }
183                }
184            }
185            return new GaugeSnapshot.GaugeDataPointSnapshot(Double.longBitsToDouble(value.get()), labels, oldest);
186        }
187    }
188
189    public static Builder builder() {
190        return new Builder(PrometheusProperties.get());
191    }
192
193    public static Builder builder(PrometheusProperties config) {
194        return new Builder(config);
195    }
196
197    public static class Builder extends StatefulMetric.Builder<Builder, Gauge> {
198
199        private Builder(PrometheusProperties config) {
200            super(Collections.emptyList(), config);
201        }
202
203        @Override
204        public Gauge build() {
205            return new Gauge(this, properties);
206        }
207
208        @Override
209        protected Builder self() {
210            return this;
211        }
212    }
213}