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