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 double get() {
068        return getNoLabels().get();
069    }
070
071    /**
072     * {@inheritDoc}
073     */
074    @Override
075    public void incWithExemplar(double amount, Labels labels) {
076        getNoLabels().incWithExemplar(amount, labels);
077    }
078
079    /**
080     * {@inheritDoc}
081     */
082    @Override
083    public void set(double value) {
084        getNoLabels().set(value);
085    }
086
087    /**
088     * {@inheritDoc}
089     */
090    @Override
091    public void setWithExemplar(double value, Labels labels) {
092        getNoLabels().setWithExemplar(value, labels);
093    }
094
095    /**
096     * {@inheritDoc}
097     */
098    @Override
099    public GaugeSnapshot collect() {
100        return (GaugeSnapshot) super.collect();
101    }
102
103    @Override
104    protected GaugeSnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
105        List<GaugeSnapshot.GaugeDataPointSnapshot> dataPointSnapshots = new ArrayList<>(labels.size());
106        for (int i = 0; i < labels.size(); i++) {
107            dataPointSnapshots.add(metricData.get(i).collect(labels.get(i)));
108        }
109        return new GaugeSnapshot(getMetadata(), dataPointSnapshots);
110    }
111
112    @Override
113    protected DataPoint newDataPoint() {
114        if (isExemplarsEnabled()) {
115            return new DataPoint(new ExemplarSampler(exemplarSamplerConfig));
116        } else {
117            return new DataPoint(null);
118        }
119    }
120
121    @Override
122    protected boolean isExemplarsEnabled() {
123        return exemplarsEnabled;
124    }
125
126    class DataPoint implements GaugeDataPoint {
127
128        private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false
129
130        private DataPoint(ExemplarSampler exemplarSampler) {
131            this.exemplarSampler = exemplarSampler;
132        }
133
134        private final AtomicLong value = new AtomicLong(Double.doubleToRawLongBits(0));
135
136        /**
137         * {@inheritDoc}
138         */
139        @Override
140        public void inc(double amount) {
141            long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount));
142            if (isExemplarsEnabled()) {
143                exemplarSampler.observe(Double.longBitsToDouble(next));
144            }
145        }
146
147        /**
148         * {@inheritDoc}
149         */
150        @Override
151        public void incWithExemplar(double amount, Labels labels) {
152            long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount));
153            if (isExemplarsEnabled()) {
154                exemplarSampler.observeWithExemplar(Double.longBitsToDouble(next), labels);
155            }
156        }
157
158        /**
159         * {@inheritDoc}
160         */
161        @Override
162        public void set(double value) {
163            this.value.set(Double.doubleToRawLongBits(value));
164            if (isExemplarsEnabled()) {
165                exemplarSampler.observe(value);
166            }
167        }
168
169        /**
170         * {@inheritDoc}
171         */
172        @Override
173        public double get() {
174            return Double.longBitsToDouble(value.get());
175        }
176
177        /**
178         * {@inheritDoc}
179         */
180        @Override
181        public void setWithExemplar(double value, Labels labels) {
182            this.value.set(Double.doubleToRawLongBits(value));
183            if (isExemplarsEnabled()) {
184                exemplarSampler.observeWithExemplar(value, labels);
185            }
186        }
187
188        private GaugeSnapshot.GaugeDataPointSnapshot collect(Labels labels) {
189            // Read the exemplar first. Otherwise, there is a race condition where you might
190            // see an Exemplar for a value that's not represented in getValue() yet.
191            // If there are multiple Exemplars (by default it's just one), use the oldest
192            // so that we don't violate min age.
193            Exemplar oldest = null;
194            if (isExemplarsEnabled()) {
195                for (Exemplar exemplar : exemplarSampler.collect()) {
196                    if (oldest == null || exemplar.getTimestampMillis() < oldest.getTimestampMillis()) {
197                        oldest = exemplar;
198                    }
199                }
200            }
201            return new GaugeSnapshot.GaugeDataPointSnapshot(get(), labels, oldest);
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, Gauge> {
214
215        private Builder(PrometheusProperties config) {
216            super(Collections.emptyList(), config);
217        }
218
219        @Override
220        public Gauge build() {
221            return new Gauge(this, properties);
222        }
223
224        @Override
225        protected Builder self() {
226            return this;
227        }
228    }
229}