Skip to content

Builder.withoutExemplars() setting cannot be overridden via properties #1477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public static PrometheusProperties get() throws PrometheusPropertiesException {
return instance;
}

public static Builder builder() {
return new Builder();
}

public PrometheusProperties(
MetricsProperties defaultMetricsProperties,
Map<String, MetricsProperties> metricProperties,
Expand Down Expand Up @@ -95,4 +99,77 @@ public ExporterPushgatewayProperties getExporterPushgatewayProperties() {
public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() {
return exporterOpenTelemetryProperties;
}

public static class Builder {
private MetricsProperties defaultMetricsProperties;
private Map<String, MetricsProperties> 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<String, MetricsProperties> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@
public class Counter extends StatefulMetric<CounterDataPoint, Counter.DataPoint>
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);
Expand Down Expand Up @@ -93,7 +93,7 @@ protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricDat

@Override
protected boolean isExemplarsEnabled() {
return exemplarsEnabled;
return exemplarSamplerConfig != null;
}

@Override
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
public class Gauge extends StatefulMetric<GaugeDataPoint, Gauge.DataPoint>
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);
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public class Histogram extends StatefulMetric<DistributionDataPoint, Histogram.D
// NATIVE_BOUNDS is used to look up the native bucket index depending on the current schema.
private static final double[][] NATIVE_BOUNDS;

private final boolean exemplarsEnabled;
private final ExemplarSamplerConfig exemplarSamplerConfig;

// Upper bounds for the classic histogram buckets. Contains at least +Inf.
Expand Down Expand Up @@ -116,7 +115,6 @@ public class Histogram extends StatefulMetric<DistributionDataPoint, Histogram.D
private Histogram(Histogram.Builder builder, PrometheusProperties prometheusProperties) {
super(builder);
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
nativeInitialSchema =
getConfigProperty(
properties,
Expand Down Expand Up @@ -158,11 +156,17 @@ private Histogram(Histogram.Builder builder, PrometheusProperties prometheusProp
getConfigProperty(properties, MetricsProperties::getHistogramNativeMaxNumberOfBuckets);
nativeResetDurationSeconds =
getConfigProperty(properties, MetricsProperties::getHistogramNativeResetDurationSeconds);
boolean exemplarsEnabled =
getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
ExemplarsProperties exemplarsProperties = prometheusProperties.getExemplarProperties();
exemplarSamplerConfig =
classicUpperBounds.length == 0
? new ExemplarSamplerConfig(exemplarsProperties, 4)
: new ExemplarSamplerConfig(exemplarsProperties, classicUpperBounds);
if (exemplarsEnabled) {
exemplarSamplerConfig =
classicUpperBounds.length == 0
? new ExemplarSamplerConfig(exemplarsProperties, 4)
: new ExemplarSamplerConfig(exemplarsProperties, classicUpperBounds);
} else {
exemplarSamplerConfig = null;
}
}

@Override
Expand All @@ -177,7 +181,7 @@ public void observeWithExemplar(double amount, Labels labels) {

@Override
protected boolean isExemplarsEnabled() {
return exemplarsEnabled;
return exemplarSamplerConfig != null;
}

public class DataPoint implements DistributionDataPoint {
Expand All @@ -198,7 +202,7 @@ public class DataPoint implements DistributionDataPoint {
private final ExemplarSampler exemplarSampler;

private DataPoint() {
if (exemplarsEnabled) {
if (isExemplarsEnabled()) {
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
} else {
exemplarSampler = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

Expand Down Expand Up @@ -157,23 +158,26 @@ protected T getNoLabels() {
return noLabels;
}

/**
* Metric properties in effect by order of precedence with the highest precedence first. If a
* {@code MetricProperties} is configured for the metric name it has higher precedence than the
* builder configuration. A special case is the setting {@link Builder#withoutExemplars()} via the
* builder, which cannot be overridden by any configuration.
*/
protected MetricsProperties[] getMetricProperties(
Builder<?, ?> builder, PrometheusProperties prometheusProperties) {
List<MetricsProperties> 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> P getConfigProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,22 @@ public class Summary extends StatefulMetric<DistributionDataPoint, Summary.DataP
private final List<CKMSQuantiles.Quantile> 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<CKMSQuantiles.Quantile> makeQuantiles(MetricsProperties[] properties) {
Expand All @@ -79,7 +82,7 @@ private List<CKMSQuantiles.Quantile> makeQuantiles(MetricsProperties[] propertie

@Override
protected boolean isExemplarsEnabled() {
return exemplarsEnabled;
return exemplarSamplerConfig != null;
}

@Override
Expand Down Expand Up @@ -134,7 +137,7 @@ private DataPoint() {
} else {
quantileValues = null;
}
if (exemplarsEnabled) {
if (isExemplarsEnabled()) {
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
} else {
exemplarSampler = null;
Expand Down
Loading