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