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