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