001package io.prometheus.metrics.config;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.Map;
007
008import static java.util.Collections.unmodifiableList;
009
010/**
011 * Properties starting with io.prometheus.metrics
012 */
013public class MetricsProperties {
014
015    private static final String EXEMPLARS_ENABLED = "exemplarsEnabled";
016    private static final String HISTOGRAM_NATIVE_ONLY = "histogramNativeOnly";
017    private static final String HISTOGRAM_CLASSIC_ONLY = "histogramClassicOnly";
018    private static final String HISTOGRAM_CLASSIC_UPPER_BOUNDS = "histogramClassicUpperBounds";
019    private static final String HISTOGRAM_NATIVE_INITIAL_SCHEMA = "histogramNativeInitialSchema";
020    private static final String HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD = "histogramNativeMinZeroThreshold";
021    private static final String HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD = "histogramNativeMaxZeroThreshold";
022    private static final String HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS = "histogramNativeMaxNumberOfBuckets"; // 0 means unlimited number of buckets
023    private static final String HISTOGRAM_NATIVE_RESET_DURATION_SECONDS = "histogramNativeResetDurationSeconds"; // 0 means no reset
024    private static final String SUMMARY_QUANTILES = "summaryQuantiles";
025    private static final String SUMMARY_QUANTILE_ERRORS = "summaryQuantileErrors";
026    private static final String SUMMARY_MAX_AGE_SECONDS = "summaryMaxAgeSeconds";
027    private static final String SUMMARY_NUMBER_OF_AGE_BUCKETS = "summaryNumberOfAgeBuckets";
028
029    private final Boolean exemplarsEnabled;
030    private final Boolean histogramNativeOnly;
031    private final Boolean histogramClassicOnly;
032    private final List<Double> histogramClassicUpperBounds;
033    private final Integer histogramNativeInitialSchema;
034    private final Double histogramNativeMinZeroThreshold;
035    private final Double histogramNativeMaxZeroThreshold;
036    private final Integer histogramNativeMaxNumberOfBuckets;
037    private final Long histogramNativeResetDurationSeconds;
038    private final List<Double> summaryQuantiles;
039    private final List<Double> summaryQuantileErrors;
040    private final Long summaryMaxAgeSeconds;
041    private final Integer summaryNumberOfAgeBuckets;
042
043    public MetricsProperties(
044            Boolean exemplarsEnabled,
045            Boolean histogramNativeOnly,
046            Boolean histogramClassicOnly,
047            List<Double> histogramClassicUpperBounds,
048            Integer histogramNativeInitialSchema,
049            Double histogramNativeMinZeroThreshold,
050            Double histogramNativeMaxZeroThreshold,
051            Integer histogramNativeMaxNumberOfBuckets,
052            Long histogramNativeResetDurationSeconds,
053            List<Double> summaryQuantiles,
054            List<Double> summaryQuantileErrors,
055            Long summaryMaxAgeSeconds,
056            Integer summaryNumberOfAgeBuckets) {
057        this(exemplarsEnabled,
058                histogramNativeOnly,
059                histogramClassicOnly,
060                histogramClassicUpperBounds,
061                histogramNativeInitialSchema,
062                histogramNativeMinZeroThreshold,
063                histogramNativeMaxZeroThreshold,
064                histogramNativeMaxNumberOfBuckets,
065                histogramNativeResetDurationSeconds,
066                summaryQuantiles,
067                summaryQuantileErrors,
068                summaryMaxAgeSeconds,
069                summaryNumberOfAgeBuckets,
070                "");
071    }
072
073    private MetricsProperties(
074            Boolean exemplarsEnabled,
075            Boolean histogramNativeOnly,
076            Boolean histogramClassicOnly,
077            List<Double> histogramClassicUpperBounds,
078            Integer histogramNativeInitialSchema,
079            Double histogramNativeMinZeroThreshold,
080            Double histogramNativeMaxZeroThreshold,
081            Integer histogramNativeMaxNumberOfBuckets,
082            Long histogramNativeResetDurationSeconds,
083            List<Double> summaryQuantiles,
084            List<Double> summaryQuantileErrors,
085            Long summaryMaxAgeSeconds,
086            Integer summaryNumberOfAgeBuckets,
087            String configPropertyPrefix) {
088        this.exemplarsEnabled = exemplarsEnabled;
089        this.histogramNativeOnly = isHistogramNativeOnly(histogramClassicOnly, histogramNativeOnly);
090        this.histogramClassicOnly = isHistogramClassicOnly(histogramClassicOnly, histogramNativeOnly);
091        this.histogramClassicUpperBounds = histogramClassicUpperBounds == null ? null : unmodifiableList(new ArrayList<>(histogramClassicUpperBounds));
092        this.histogramNativeInitialSchema = histogramNativeInitialSchema;
093        this.histogramNativeMinZeroThreshold = histogramNativeMinZeroThreshold;
094        this.histogramNativeMaxZeroThreshold = histogramNativeMaxZeroThreshold;
095        this.histogramNativeMaxNumberOfBuckets = histogramNativeMaxNumberOfBuckets;
096        this.histogramNativeResetDurationSeconds = histogramNativeResetDurationSeconds;
097        this.summaryQuantiles = summaryQuantiles == null ? null : unmodifiableList(new ArrayList<>(summaryQuantiles));
098        this.summaryQuantileErrors = summaryQuantileErrors == null ? null : unmodifiableList(new ArrayList<>(summaryQuantileErrors));
099        this.summaryMaxAgeSeconds = summaryMaxAgeSeconds;
100        this.summaryNumberOfAgeBuckets = summaryNumberOfAgeBuckets;
101        validate(configPropertyPrefix);
102    }
103
104
105    private Boolean isHistogramClassicOnly(Boolean histogramClassicOnly, Boolean histogramNativeOnly) {
106        if (histogramClassicOnly == null && histogramNativeOnly == null) {
107            return null;
108        }
109        if (histogramClassicOnly != null) {
110            return histogramClassicOnly;
111        }
112        return !histogramNativeOnly;
113    }
114
115    private Boolean isHistogramNativeOnly(Boolean histogramClassicOnly, Boolean histogramNativeOnly) {
116        if (histogramClassicOnly == null && histogramNativeOnly == null) {
117            return null;
118        }
119        if (histogramNativeOnly != null) {
120            return histogramNativeOnly;
121        }
122        return !histogramClassicOnly;
123    }
124
125    private void validate(String prefix) throws PrometheusPropertiesException {
126        Util.assertValue(histogramNativeInitialSchema, s -> s >= -4 && s <= 8, "Expecting number between -4 and +8.", prefix, HISTOGRAM_NATIVE_INITIAL_SCHEMA);
127        Util.assertValue(histogramNativeMinZeroThreshold, t -> t >= 0, "Expecting value >= 0.", prefix, HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD);
128        Util.assertValue(histogramNativeMaxZeroThreshold, t -> t >= 0, "Expecting value >= 0.", prefix, HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD);
129        Util.assertValue(histogramNativeMaxNumberOfBuckets, n -> n >= 0, "Expecting value >= 0.", prefix, HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS);
130        Util.assertValue(histogramNativeResetDurationSeconds, t -> t >= 0, "Expecting value >= 0.", prefix, HISTOGRAM_NATIVE_RESET_DURATION_SECONDS);
131        Util.assertValue(summaryMaxAgeSeconds, t -> t > 0, "Expecting value > 0", prefix, SUMMARY_MAX_AGE_SECONDS);
132        Util.assertValue(summaryNumberOfAgeBuckets, t -> t > 0, "Expecting value > 0", prefix, SUMMARY_NUMBER_OF_AGE_BUCKETS);
133
134        if (Boolean.TRUE.equals(histogramNativeOnly) && Boolean.TRUE.equals(histogramClassicOnly)) {
135            throw new PrometheusPropertiesException(prefix + "." + HISTOGRAM_NATIVE_ONLY + " and " + prefix + "." + HISTOGRAM_CLASSIC_ONLY + " cannot both be true");
136        }
137
138        if (histogramNativeMinZeroThreshold != null && histogramNativeMaxZeroThreshold != null) {
139            if (histogramNativeMinZeroThreshold > histogramNativeMaxZeroThreshold) {
140                throw new PrometheusPropertiesException(prefix + "." + HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD + " cannot be greater than " + prefix + "." + HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD);
141            }
142        }
143
144        if (summaryQuantiles != null) {
145            for (double quantile : summaryQuantiles) {
146                if (quantile < 0 || quantile > 1) {
147                    throw new PrometheusPropertiesException(prefix + "." + SUMMARY_QUANTILES + ": Expecting 0.0 <= quantile <= 1.0");
148                }
149            }
150        }
151
152        if (summaryQuantileErrors != null) {
153            if (summaryQuantiles == null) {
154                throw new PrometheusPropertiesException(prefix + "." + SUMMARY_QUANTILE_ERRORS + ": Can't configure " + SUMMARY_QUANTILE_ERRORS + " without configuring " + SUMMARY_QUANTILES);
155            }
156            if (summaryQuantileErrors.size() != summaryQuantiles.size()) {
157                throw new PrometheusPropertiesException(prefix + "." + SUMMARY_QUANTILE_ERRORS + ": must have the same length as " + SUMMARY_QUANTILES);
158            }
159            for (double error : summaryQuantileErrors) {
160                if (error < 0 || error > 1) {
161                    throw new PrometheusPropertiesException(prefix + "." + SUMMARY_QUANTILE_ERRORS + ": Expecting 0.0 <= error <= 1.0");
162                }
163            }
164        }
165    }
166
167    /**
168     * This is the only configuration property that can be applied to all metric types.
169     * You can use it to turn Exemplar support off. Default is {@code true}.
170     */
171    public Boolean getExemplarsEnabled() {
172        return exemplarsEnabled;
173    }
174
175    /**
176     * See {@code Histogram.Builder.nativeOnly()}
177     */
178    public Boolean getHistogramNativeOnly() {
179        return histogramNativeOnly;
180    }
181
182    /**
183     * See {@code Histogram.Builder.classicOnly()}
184     */
185    public Boolean getHistogramClassicOnly() {
186        return histogramClassicOnly;
187    }
188
189    /**
190     * See {@code Histogram.Builder.classicBuckets()}
191     */
192    public List<Double> getHistogramClassicUpperBounds() {
193        return histogramClassicUpperBounds;
194    }
195
196    /**
197     * See {@code Histogram.Builder.nativeInitialSchema()}
198     */
199    public Integer getHistogramNativeInitialSchema() {
200        return histogramNativeInitialSchema;
201    }
202
203    /**
204     * See {@code Histogram.Builder.nativeMinZeroThreshold()}
205     */
206    public Double getHistogramNativeMinZeroThreshold() {
207        return histogramNativeMinZeroThreshold;
208    }
209
210    /**
211     * See {@code Histogram.Builder.nativeMaxZeroThreshold()}
212     */
213    public Double getHistogramNativeMaxZeroThreshold() {
214        return histogramNativeMaxZeroThreshold;
215    }
216
217    /**
218     * See {@code Histogram.Builder.nativeMaxNumberOfBuckets()}
219     */
220    public Integer getHistogramNativeMaxNumberOfBuckets() {
221        return histogramNativeMaxNumberOfBuckets;
222    }
223
224    /**
225     * See {@code Histogram.Builder.nativeResetDuration()}
226     */
227    public Long getHistogramNativeResetDurationSeconds() {
228        return histogramNativeResetDurationSeconds;
229    }
230
231    /**
232     * See {@code Summary.Builder.quantile()}
233     */
234    public List<Double> getSummaryQuantiles() {
235        return summaryQuantiles;
236    }
237
238    /**
239     * See {@code Summary.Builder.quantile()}
240     * <p>
241     * Returns {@code null} only if {@link #getSummaryQuantiles()} is also {@code null}.
242     * Returns an empty list if {@link #getSummaryQuantiles()} are specified without specifying errors.
243     * If the list is not empty, it has the same size as {@link #getSummaryQuantiles()}.
244     */
245    public List<Double> getSummaryQuantileErrors() {
246        if (summaryQuantiles != null) {
247            if (summaryQuantileErrors == null) {
248                return Collections.emptyList();
249            }
250        }
251        return summaryQuantileErrors;
252    }
253
254    /**
255     * See {@code Summary.Builder.maxAgeSeconds()}
256     */
257    public Long getSummaryMaxAgeSeconds() {
258        return summaryMaxAgeSeconds;
259    }
260
261    /**
262     * See {@code Summary.Builder.numberOfAgeBuckets()}
263     */
264    public Integer getSummaryNumberOfAgeBuckets() {
265        return summaryNumberOfAgeBuckets;
266    }
267
268    /**
269     * Note that this will remove entries from {@code properties}.
270     * This is because we want to know if there are unused properties remaining after all properties have been loaded.
271     */
272    static MetricsProperties load(String prefix, Map<Object, Object> properties) throws PrometheusPropertiesException {
273        return new MetricsProperties(
274                Util.loadBoolean(prefix + "." + EXEMPLARS_ENABLED, properties),
275                Util.loadBoolean(prefix + "." + HISTOGRAM_NATIVE_ONLY, properties),
276                Util.loadBoolean(prefix + "." + HISTOGRAM_CLASSIC_ONLY, properties),
277                Util.loadDoubleList(prefix + "." + HISTOGRAM_CLASSIC_UPPER_BOUNDS, properties),
278                Util.loadInteger(prefix + "." + HISTOGRAM_NATIVE_INITIAL_SCHEMA, properties),
279                Util.loadDouble(prefix + "." + HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD, properties),
280                Util.loadDouble(prefix + "." + HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD, properties),
281                Util.loadInteger(prefix + "." + HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS, properties),
282                Util.loadLong(prefix + "." + HISTOGRAM_NATIVE_RESET_DURATION_SECONDS, properties),
283                Util.loadDoubleList(prefix + "." + SUMMARY_QUANTILES, properties),
284                Util.loadDoubleList(prefix + "." + SUMMARY_QUANTILE_ERRORS, properties),
285                Util.loadLong(prefix + "." + SUMMARY_MAX_AGE_SECONDS, properties),
286                Util.loadInteger(prefix + "." + SUMMARY_NUMBER_OF_AGE_BUCKETS, properties),
287                prefix);
288    }
289
290    public static Builder builder() {
291        return new Builder();
292    }
293
294    public static class Builder {
295        private Boolean exemplarsEnabled;
296        private Boolean histogramNativeOnly;
297        private Boolean histogramClassicOnly;
298        private List<Double> histogramClassicUpperBounds;
299        private Integer histogramNativeInitialSchema;
300        private Double histogramNativeMinZeroThreshold;
301        private Double histogramNativeMaxZeroThreshold;
302        private Integer histogramNativeMaxNumberOfBuckets;
303        private Long histogramNativeResetDurationSeconds;
304        private List<Double> summaryQuantiles;
305        private List<Double> summaryQuantileErrors;
306        private Long summaryMaxAgeSeconds;
307        private Integer summaryNumberOfAgeBuckets;
308
309        private Builder() {
310        }
311
312        public MetricsProperties build() {
313            return new MetricsProperties(exemplarsEnabled,
314                    histogramNativeOnly,
315                    histogramClassicOnly,
316                    histogramClassicUpperBounds,
317                    histogramNativeInitialSchema,
318                    histogramNativeMinZeroThreshold,
319                    histogramNativeMaxZeroThreshold,
320                    histogramNativeMaxNumberOfBuckets,
321                    histogramNativeResetDurationSeconds,
322                    summaryQuantiles,
323                    summaryQuantileErrors,
324                    summaryMaxAgeSeconds,
325                    summaryNumberOfAgeBuckets);
326        }
327
328        /**
329         * See {@link MetricsProperties#getExemplarsEnabled()}
330         */
331        public Builder exemplarsEnabled(Boolean exemplarsEnabled) {
332            this.exemplarsEnabled = exemplarsEnabled;
333            return this;
334        }
335
336        /**
337         * See {@link MetricsProperties#getHistogramNativeOnly()}
338         */
339        public Builder histogramNativeOnly(Boolean histogramNativeOnly) {
340            this.histogramNativeOnly = histogramNativeOnly;
341            return this;
342        }
343
344        /**
345         * See {@link MetricsProperties#getHistogramClassicOnly()}
346         */
347        public Builder histogramClassicOnly(Boolean histogramClassicOnly) {
348            this.histogramClassicOnly = histogramClassicOnly;
349            return this;
350        }
351
352        /**
353         * See {@link MetricsProperties#getHistogramClassicUpperBounds()}
354         */
355        public Builder histogramClassicUpperBounds(double... histogramClassicUpperBounds) {
356            this.histogramClassicUpperBounds = Util.toList(histogramClassicUpperBounds);
357            return this;
358        }
359
360        /**
361         * See {@link MetricsProperties#getHistogramNativeInitialSchema()}
362         */
363        public Builder histogramNativeInitialSchema(Integer histogramNativeInitialSchema) {
364            this.histogramNativeInitialSchema = histogramNativeInitialSchema;
365            return this;
366        }
367
368        /**
369         * See {@link MetricsProperties#getHistogramNativeMinZeroThreshold()}
370         */
371        public Builder histogramNativeMinZeroThreshold(Double histogramNativeMinZeroThreshold) {
372            this.histogramNativeMinZeroThreshold = histogramNativeMinZeroThreshold;
373            return this;
374        }
375
376        /**
377         * See {@link MetricsProperties#getHistogramNativeMaxZeroThreshold()}
378         */
379        public Builder histogramNativeMaxZeroThreshold(Double histogramNativeMaxZeroThreshold) {
380            this.histogramNativeMaxZeroThreshold = histogramNativeMaxZeroThreshold;
381            return this;
382        }
383
384        /**
385         * See {@link MetricsProperties#getHistogramNativeMaxNumberOfBuckets()}
386         */
387        public Builder histogramNativeMaxNumberOfBuckets(Integer histogramNativeMaxNumberOfBuckets) {
388            this.histogramNativeMaxNumberOfBuckets = histogramNativeMaxNumberOfBuckets;
389            return this;
390        }
391
392        /**
393         * See {@link MetricsProperties#getHistogramNativeResetDurationSeconds()}
394         */
395        public Builder histogramNativeResetDurationSeconds(Long histogramNativeResetDurationSeconds) {
396            this.histogramNativeResetDurationSeconds = histogramNativeResetDurationSeconds;
397            return this;
398        }
399
400        /**
401         * See {@link MetricsProperties#getSummaryQuantiles()}
402         */
403        public Builder summaryQuantiles(double... summaryQuantiles) {
404            this.summaryQuantiles = Util.toList(summaryQuantiles);
405            return this;
406        }
407
408        /**
409         * See {@link MetricsProperties#getSummaryQuantileErrors()}
410         */
411        public Builder summaryQuantileErrors(double... summaryQuantileErrors) {
412            this.summaryQuantileErrors = Util.toList(summaryQuantileErrors);
413            return this;
414        }
415
416        /**
417         * See {@link MetricsProperties#getSummaryMaxAgeSeconds()}
418         */
419        public Builder summaryMaxAgeSeconds(Long summaryMaxAgeSeconds) {
420            this.summaryMaxAgeSeconds = summaryMaxAgeSeconds;
421            return this;
422        }
423
424        /**
425         * See {@link MetricsProperties#getSummaryNumberOfAgeBuckets()}
426         */
427        public Builder summaryNumberOfAgeBuckets(Integer summaryNumberOfAgeBuckets) {
428            this.summaryNumberOfAgeBuckets = summaryNumberOfAgeBuckets;
429            return this;
430        }
431    }
432}