diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index da31fe8cc..6d9a37594 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -36,6 +36,10 @@ public static PrometheusProperties get() throws PrometheusPropertiesException { return instance; } + public static Builder builder() { + return new Builder(); + } + public PrometheusProperties( MetricsProperties defaultMetricsProperties, Map metricProperties, @@ -95,4 +99,77 @@ public ExporterPushgatewayProperties getExporterPushgatewayProperties() { public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() { return exporterOpenTelemetryProperties; } + + public static class Builder { + private MetricsProperties defaultMetricsProperties; + private Map metricProperties = new HashMap<>(); + private ExemplarsProperties exemplarProperties; + private ExporterProperties exporterProperties; + private ExporterFilterProperties exporterFilterProperties; + private ExporterHttpServerProperties exporterHttpServerProperties; + private ExporterPushgatewayProperties pushgatewayProperties; + private ExporterOpenTelemetryProperties otelConfig; + + private Builder() {} + + public Builder defaultMetricsProperties(MetricsProperties defaultMetricsProperties) { + this.defaultMetricsProperties = defaultMetricsProperties; + return this; + } + + public Builder metricProperties(Map metricProperties) { + this.metricProperties = metricProperties; + return this; + } + + /** Convenience for adding a single named MetricsProperties */ + public Builder putMetricProperty(String name, MetricsProperties props) { + this.metricProperties.put(name, props); + return this; + } + + public Builder exemplarProperties(ExemplarsProperties exemplarProperties) { + this.exemplarProperties = exemplarProperties; + return this; + } + + public Builder exporterProperties(ExporterProperties exporterProperties) { + this.exporterProperties = exporterProperties; + return this; + } + + public Builder exporterFilterProperties(ExporterFilterProperties exporterFilterProperties) { + this.exporterFilterProperties = exporterFilterProperties; + return this; + } + + public Builder exporterHttpServerProperties( + ExporterHttpServerProperties exporterHttpServerProperties) { + this.exporterHttpServerProperties = exporterHttpServerProperties; + return this; + } + + public Builder pushgatewayProperties(ExporterPushgatewayProperties pushgatewayProperties) { + this.pushgatewayProperties = pushgatewayProperties; + return this; + } + + public Builder exporterOpenTelemetryProperties( + ExporterOpenTelemetryProperties exporterOpenTelemetryProperties) { + this.otelConfig = exporterOpenTelemetryProperties; + return this; + } + + public PrometheusProperties build() { + return new PrometheusProperties( + defaultMetricsProperties, + metricProperties, + exemplarProperties, + exporterProperties, + exporterFilterProperties, + exporterHttpServerProperties, + pushgatewayProperties, + otelConfig); + } + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java index cce7213a6..a01e05248 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -30,4 +31,29 @@ public void testEmptyUpperBounds() throws IOException { MetricsProperties.load("io.prometheus.metrics", properties); assertThat(properties).isEmpty(); } + + @Test + public void testBuilder() { + PrometheusProperties defaults = PrometheusPropertiesLoader.load(new HashMap<>()); + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); + builder.exemplarProperties(defaults.getExemplarProperties()); + builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); + builder.exporterFilterProperties(defaults.getExporterFilterProperties()); + builder.exporterHttpServerProperties(defaults.getExporterHttpServerProperties()); + builder.exporterOpenTelemetryProperties(defaults.getExporterOpenTelemetryProperties()); + builder.pushgatewayProperties(defaults.getExporterPushgatewayProperties()); + PrometheusProperties result = builder.build(); + assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); + assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); + assertThat(result.getExemplarProperties()).isSameAs(defaults.getExemplarProperties()); + assertThat(result.getExporterFilterProperties()) + .isSameAs(defaults.getExporterFilterProperties()); + assertThat(result.getExporterHttpServerProperties()) + .isSameAs(defaults.getExporterHttpServerProperties()); + assertThat(result.getExporterOpenTelemetryProperties()) + .isSameAs(defaults.getExporterOpenTelemetryProperties()); + assertThat(result.getExporterPushgatewayProperties()) + .isSameAs(defaults.getExporterPushgatewayProperties()); + } } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java index 8ed9b0001..aceea8fd7 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java @@ -32,13 +32,13 @@ public class Counter extends StatefulMetric implements CounterDataPoint { - private final boolean exemplarsEnabled; private final ExemplarSamplerConfig exemplarSamplerConfig; private Counter(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); if (exemplarsEnabled) { exemplarSamplerConfig = new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1); @@ -93,7 +93,7 @@ protected CounterSnapshot collect(List labels, List metricDat @Override protected boolean isExemplarsEnabled() { - return exemplarsEnabled; + return exemplarSamplerConfig != null; } @Override @@ -112,7 +112,7 @@ static String stripTotalSuffix(String name) { return name; } - class DataPoint implements CounterDataPoint { + static class DataPoint implements CounterDataPoint { private final DoubleAdder doubleValue = new DoubleAdder(); // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations, @@ -168,6 +168,10 @@ public void incWithExemplar(double amount, Labels labels) { } } + private boolean isExemplarsEnabled() { + return exemplarSampler != null; + } + private void validateAndAdd(long amount) { if (amount < 0) { throw new IllegalArgumentException( diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java index 4ba5d5ed3..faff5a058 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java @@ -39,13 +39,13 @@ public class Gauge extends StatefulMetric implements GaugeDataPoint { - private final boolean exemplarsEnabled; private final ExemplarSamplerConfig exemplarSamplerConfig; private Gauge(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); if (exemplarsEnabled) { exemplarSamplerConfig = new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1); @@ -104,10 +104,10 @@ protected DataPoint newDataPoint() { @Override protected boolean isExemplarsEnabled() { - return exemplarsEnabled; + return exemplarSamplerConfig != null; } - class DataPoint implements GaugeDataPoint { + static class DataPoint implements GaugeDataPoint { private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false @@ -171,6 +171,10 @@ private GaugeSnapshot.GaugeDataPointSnapshot collect(Labels labels) { } return new GaugeSnapshot.GaugeDataPointSnapshot(get(), labels, oldest); } + + private boolean isExemplarsEnabled() { + return exemplarSampler != null; + } } public static Builder builder() { diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java index 327867fd0..a465c1970 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java @@ -68,7 +68,6 @@ public class Histogram extends StatefulMetric builder, PrometheusProperties prometheusProperties) { + List properties = new ArrayList<>(); + if (Objects.equals(builder.exemplarsEnabled, false)) { + properties.add(MetricsProperties.builder().exemplarsEnabled(false).build()); + } String metricName = getMetadata().getName(); if (prometheusProperties.getMetricProperties(metricName) != null) { - return new MetricsProperties[] { - prometheusProperties.getMetricProperties(metricName), // highest precedence - builder.toProperties(), // second-highest precedence - prometheusProperties.getDefaultMetricProperties(), // third-highest precedence - builder.getDefaultProperties() // fallback - }; - } else { - return new MetricsProperties[] { - builder.toProperties(), // highest precedence - prometheusProperties.getDefaultMetricProperties(), // second-highest precedence - builder.getDefaultProperties() // fallback - }; + properties.add(prometheusProperties.getMetricProperties(metricName)); } + properties.add(builder.toProperties()); + properties.add(prometheusProperties.getDefaultMetricProperties()); + properties.add(builder.getDefaultProperties()); // fallback + return properties.toArray(new MetricsProperties[0]); } protected

P getConfigProperty( diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java index ddb3eb7f2..d1f3b278a 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java @@ -44,19 +44,22 @@ public class Summary extends StatefulMetric quantiles; // May be empty, but cannot be null. private final long maxAgeSeconds; private final int ageBuckets; - private final boolean exemplarsEnabled; private final ExemplarSamplerConfig exemplarSamplerConfig; private Summary(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - this.exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); - this.quantiles = Collections.unmodifiableList(makeQuantiles(properties)); - this.maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds); - this.ageBuckets = - getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets); - this.exemplarSamplerConfig = - new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4); + quantiles = Collections.unmodifiableList(makeQuantiles(properties)); + maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds); + ageBuckets = getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + if (exemplarsEnabled) { + exemplarSamplerConfig = + new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4); + } else { + exemplarSamplerConfig = null; + } } private List makeQuantiles(MetricsProperties[] properties) { @@ -79,7 +82,7 @@ private List makeQuantiles(MetricsProperties[] propertie @Override protected boolean isExemplarsEnabled() { - return exemplarsEnabled; + return exemplarSamplerConfig != null; } @Override @@ -134,7 +137,7 @@ private DataPoint() { } else { quantileValues = null; } - if (exemplarsEnabled) { + if (isExemplarsEnabled()) { exemplarSampler = new ExemplarSampler(exemplarSamplerConfig); } else { exemplarSampler = null; diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index b5ca3be15..8ad990151 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -5,6 +5,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; @@ -317,13 +319,33 @@ void incWithExemplar2() { @Test public void testExemplarSamplerDisabled() { - Counter counter = - Counter.builder() - // .withExemplarSampler((inc, prev) -> {throw new RuntimeException("unexpected call to - // exemplar sampler");}) - .name("count_total") - .withoutExemplars() + Counter counter = Counter.builder().name("count_total").withoutExemplars().build(); + counter.incWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(counter).getExemplar()).isNull(); + counter.inc(2.0); + assertThat(getData(counter).getExemplar()).isNull(); + } + + @Test + public void testExemplarSamplerDisabled_enabledByDefault() { + PrometheusProperties properties = + PrometheusProperties.builder() + .defaultMetricsProperties(MetricsProperties.builder().exemplarsEnabled(true).build()) + .build(); + Counter counter = Counter.builder(properties).name("count_total").withoutExemplars().build(); + counter.incWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(counter).getExemplar()).isNull(); + counter.inc(2.0); + assertThat(getData(counter).getExemplar()).isNull(); + } + + @Test + public void testExemplarSamplerDisabledInBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("count", MetricsProperties.builder().exemplarsEnabled(true).build()) .build(); + Counter counter = Counter.builder(properties).name("count_total").withoutExemplars().build(); counter.incWithExemplar(3.0, Labels.of("a", "b")); assertThat(getData(counter).getExemplar()).isNull(); counter.inc(2.0); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java index 41d692a80..108fc05ce 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java @@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.Timer; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; import io.prometheus.metrics.model.snapshots.Exemplar; @@ -227,4 +229,17 @@ public void testExemplarSamplerDisabled() { gauge.inc(2.0); assertThat(getData(gauge).getExemplar()).isNull(); } + + @Test + public void testExemplarSamplerDisabledByBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("test", MetricsProperties.builder().exemplarsEnabled(true).build()) + .build(); + Gauge gauge = Gauge.builder(properties).name("test").withoutExemplars().build(); + gauge.setWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(gauge).getExemplar()).isNull(); + gauge.inc(2.0); + assertThat(getData(gauge).getExemplar()).isNull(); + } } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 7cfa1b83e..70419ea8d 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -5,6 +5,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.DistributionDataPoint; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; @@ -1428,6 +1430,25 @@ public void testBoundaryConditions() { assertThat(getBucket(histogram, Double.POSITIVE_INFINITY).getCount()).isOne(); } + @Test + public void testExemplarsDisabledInBuilder() { + Histogram histogram = Histogram.builder().withoutExemplars().name("test").build(); + histogram.observeWithExemplar(2.5, Labels.EMPTY); + assertThat(getData(histogram).getExemplars().size()).isZero(); + } + + @Test + public void testExemplarsDisabledInBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("test", MetricsProperties.builder().exemplarsEnabled(true).build()) + .defaultMetricsProperties(MetricsProperties.builder().build()) + .build(); + Histogram histogram = Histogram.builder(properties).withoutExemplars().name("test").build(); + histogram.observeWithExemplar(2.5, Labels.EMPTY); + assertThat(getData(histogram).getExemplars().size()).isZero(); + } + @Test public void testObserveWithLabels() { Histogram histogram =