Skip to content

Commit f971724

Browse files
authored
Builder.withoutExemplars() setting cannot be overridden via properties (#1477)
The PR will fix the behaviour that exemplar support can be switched on via the properties, although it was disabled explicitly in the code definition by `withoutExemplars`. Currently this would enable exemplars: ```` PrometheusProperties properties = PrometheusProperties.builder() .putMetricProperty("count", MetricsProperties.builder() .exemplarsEnabled(true) .build()) .build(); Counter counter = Counter.builder(properties) .name("count_total") .withoutExemplars() .build(); ```` In TDD fashion I will add failing tests first. Signed-off-by: Jens Wilke <signed-off@cruftex.net>
1 parent 0dc8492 commit f971724

File tree

10 files changed

+224
-44
lines changed

10 files changed

+224
-44
lines changed

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public static PrometheusProperties get() throws PrometheusPropertiesException {
3636
return instance;
3737
}
3838

39+
public static Builder builder() {
40+
return new Builder();
41+
}
42+
3943
public PrometheusProperties(
4044
MetricsProperties defaultMetricsProperties,
4145
Map<String, MetricsProperties> metricProperties,
@@ -95,4 +99,77 @@ public ExporterPushgatewayProperties getExporterPushgatewayProperties() {
9599
public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() {
96100
return exporterOpenTelemetryProperties;
97101
}
102+
103+
public static class Builder {
104+
private MetricsProperties defaultMetricsProperties;
105+
private Map<String, MetricsProperties> metricProperties = new HashMap<>();
106+
private ExemplarsProperties exemplarProperties;
107+
private ExporterProperties exporterProperties;
108+
private ExporterFilterProperties exporterFilterProperties;
109+
private ExporterHttpServerProperties exporterHttpServerProperties;
110+
private ExporterPushgatewayProperties pushgatewayProperties;
111+
private ExporterOpenTelemetryProperties otelConfig;
112+
113+
private Builder() {}
114+
115+
public Builder defaultMetricsProperties(MetricsProperties defaultMetricsProperties) {
116+
this.defaultMetricsProperties = defaultMetricsProperties;
117+
return this;
118+
}
119+
120+
public Builder metricProperties(Map<String, MetricsProperties> metricProperties) {
121+
this.metricProperties = metricProperties;
122+
return this;
123+
}
124+
125+
/** Convenience for adding a single named MetricsProperties */
126+
public Builder putMetricProperty(String name, MetricsProperties props) {
127+
this.metricProperties.put(name, props);
128+
return this;
129+
}
130+
131+
public Builder exemplarProperties(ExemplarsProperties exemplarProperties) {
132+
this.exemplarProperties = exemplarProperties;
133+
return this;
134+
}
135+
136+
public Builder exporterProperties(ExporterProperties exporterProperties) {
137+
this.exporterProperties = exporterProperties;
138+
return this;
139+
}
140+
141+
public Builder exporterFilterProperties(ExporterFilterProperties exporterFilterProperties) {
142+
this.exporterFilterProperties = exporterFilterProperties;
143+
return this;
144+
}
145+
146+
public Builder exporterHttpServerProperties(
147+
ExporterHttpServerProperties exporterHttpServerProperties) {
148+
this.exporterHttpServerProperties = exporterHttpServerProperties;
149+
return this;
150+
}
151+
152+
public Builder pushgatewayProperties(ExporterPushgatewayProperties pushgatewayProperties) {
153+
this.pushgatewayProperties = pushgatewayProperties;
154+
return this;
155+
}
156+
157+
public Builder exporterOpenTelemetryProperties(
158+
ExporterOpenTelemetryProperties exporterOpenTelemetryProperties) {
159+
this.otelConfig = exporterOpenTelemetryProperties;
160+
return this;
161+
}
162+
163+
public PrometheusProperties build() {
164+
return new PrometheusProperties(
165+
defaultMetricsProperties,
166+
metricProperties,
167+
exemplarProperties,
168+
exporterProperties,
169+
exporterFilterProperties,
170+
exporterHttpServerProperties,
171+
pushgatewayProperties,
172+
otelConfig);
173+
}
174+
}
98175
}

prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.io.IOException;
66
import java.io.InputStream;
7+
import java.util.HashMap;
78
import java.util.Properties;
89
import org.junit.jupiter.api.Test;
910

@@ -30,4 +31,29 @@ public void testEmptyUpperBounds() throws IOException {
3031
MetricsProperties.load("io.prometheus.metrics", properties);
3132
assertThat(properties).isEmpty();
3233
}
34+
35+
@Test
36+
public void testBuilder() {
37+
PrometheusProperties defaults = PrometheusPropertiesLoader.load(new HashMap<>());
38+
PrometheusProperties.Builder builder = PrometheusProperties.builder();
39+
builder.defaultMetricsProperties(defaults.getDefaultMetricProperties());
40+
builder.exemplarProperties(defaults.getExemplarProperties());
41+
builder.defaultMetricsProperties(defaults.getDefaultMetricProperties());
42+
builder.exporterFilterProperties(defaults.getExporterFilterProperties());
43+
builder.exporterHttpServerProperties(defaults.getExporterHttpServerProperties());
44+
builder.exporterOpenTelemetryProperties(defaults.getExporterOpenTelemetryProperties());
45+
builder.pushgatewayProperties(defaults.getExporterPushgatewayProperties());
46+
PrometheusProperties result = builder.build();
47+
assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties());
48+
assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties());
49+
assertThat(result.getExemplarProperties()).isSameAs(defaults.getExemplarProperties());
50+
assertThat(result.getExporterFilterProperties())
51+
.isSameAs(defaults.getExporterFilterProperties());
52+
assertThat(result.getExporterHttpServerProperties())
53+
.isSameAs(defaults.getExporterHttpServerProperties());
54+
assertThat(result.getExporterOpenTelemetryProperties())
55+
.isSameAs(defaults.getExporterOpenTelemetryProperties());
56+
assertThat(result.getExporterPushgatewayProperties())
57+
.isSameAs(defaults.getExporterPushgatewayProperties());
58+
}
3359
}

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
public class Counter extends StatefulMetric<CounterDataPoint, Counter.DataPoint>
3333
implements CounterDataPoint {
3434

35-
private final boolean exemplarsEnabled;
3635
private final ExemplarSamplerConfig exemplarSamplerConfig;
3736

3837
private Counter(Builder builder, PrometheusProperties prometheusProperties) {
3938
super(builder);
4039
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
41-
exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
40+
boolean exemplarsEnabled =
41+
getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
4242
if (exemplarsEnabled) {
4343
exemplarSamplerConfig =
4444
new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1);
@@ -93,7 +93,7 @@ protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricDat
9393

9494
@Override
9595
protected boolean isExemplarsEnabled() {
96-
return exemplarsEnabled;
96+
return exemplarSamplerConfig != null;
9797
}
9898

9999
@Override
@@ -112,7 +112,7 @@ static String stripTotalSuffix(String name) {
112112
return name;
113113
}
114114

115-
class DataPoint implements CounterDataPoint {
115+
static class DataPoint implements CounterDataPoint {
116116

117117
private final DoubleAdder doubleValue = new DoubleAdder();
118118
// 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) {
168168
}
169169
}
170170

171+
private boolean isExemplarsEnabled() {
172+
return exemplarSampler != null;
173+
}
174+
171175
private void validateAndAdd(long amount) {
172176
if (amount < 0) {
173177
throw new IllegalArgumentException(

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
public class Gauge extends StatefulMetric<GaugeDataPoint, Gauge.DataPoint>
4040
implements GaugeDataPoint {
4141

42-
private final boolean exemplarsEnabled;
4342
private final ExemplarSamplerConfig exemplarSamplerConfig;
4443

4544
private Gauge(Builder builder, PrometheusProperties prometheusProperties) {
4645
super(builder);
4746
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
48-
exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
47+
boolean exemplarsEnabled =
48+
getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
4949
if (exemplarsEnabled) {
5050
exemplarSamplerConfig =
5151
new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1);
@@ -104,10 +104,10 @@ protected DataPoint newDataPoint() {
104104

105105
@Override
106106
protected boolean isExemplarsEnabled() {
107-
return exemplarsEnabled;
107+
return exemplarSamplerConfig != null;
108108
}
109109

110-
class DataPoint implements GaugeDataPoint {
110+
static class DataPoint implements GaugeDataPoint {
111111

112112
private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false
113113

@@ -171,6 +171,10 @@ private GaugeSnapshot.GaugeDataPointSnapshot collect(Labels labels) {
171171
}
172172
return new GaugeSnapshot.GaugeDataPointSnapshot(get(), labels, oldest);
173173
}
174+
175+
private boolean isExemplarsEnabled() {
176+
return exemplarSampler != null;
177+
}
174178
}
175179

176180
public static Builder builder() {

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ public class Histogram extends StatefulMetric<DistributionDataPoint, Histogram.D
6868
// NATIVE_BOUNDS is used to look up the native bucket index depending on the current schema.
6969
private static final double[][] NATIVE_BOUNDS;
7070

71-
private final boolean exemplarsEnabled;
7271
private final ExemplarSamplerConfig exemplarSamplerConfig;
7372

7473
// Upper bounds for the classic histogram buckets. Contains at least +Inf.
@@ -116,7 +115,6 @@ public class Histogram extends StatefulMetric<DistributionDataPoint, Histogram.D
116115
private Histogram(Histogram.Builder builder, PrometheusProperties prometheusProperties) {
117116
super(builder);
118117
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
119-
exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
120118
nativeInitialSchema =
121119
getConfigProperty(
122120
properties,
@@ -158,11 +156,17 @@ private Histogram(Histogram.Builder builder, PrometheusProperties prometheusProp
158156
getConfigProperty(properties, MetricsProperties::getHistogramNativeMaxNumberOfBuckets);
159157
nativeResetDurationSeconds =
160158
getConfigProperty(properties, MetricsProperties::getHistogramNativeResetDurationSeconds);
159+
boolean exemplarsEnabled =
160+
getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
161161
ExemplarsProperties exemplarsProperties = prometheusProperties.getExemplarProperties();
162-
exemplarSamplerConfig =
163-
classicUpperBounds.length == 0
164-
? new ExemplarSamplerConfig(exemplarsProperties, 4)
165-
: new ExemplarSamplerConfig(exemplarsProperties, classicUpperBounds);
162+
if (exemplarsEnabled) {
163+
exemplarSamplerConfig =
164+
classicUpperBounds.length == 0
165+
? new ExemplarSamplerConfig(exemplarsProperties, 4)
166+
: new ExemplarSamplerConfig(exemplarsProperties, classicUpperBounds);
167+
} else {
168+
exemplarSamplerConfig = null;
169+
}
166170
}
167171

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

178182
@Override
179183
protected boolean isExemplarsEnabled() {
180-
return exemplarsEnabled;
184+
return exemplarSamplerConfig != null;
181185
}
182186

183187
public class DataPoint implements DistributionDataPoint {
@@ -198,7 +202,7 @@ public class DataPoint implements DistributionDataPoint {
198202
private final ExemplarSampler exemplarSampler;
199203

200204
private DataPoint() {
201-
if (exemplarsEnabled) {
205+
if (isExemplarsEnabled()) {
202206
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
203207
} else {
204208
exemplarSampler = null;

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Collections;
1111
import java.util.List;
1212
import java.util.Map;
13+
import java.util.Objects;
1314
import java.util.concurrent.ConcurrentHashMap;
1415
import java.util.function.Function;
1516

@@ -157,23 +158,26 @@ protected T getNoLabels() {
157158
return noLabels;
158159
}
159160

161+
/**
162+
* Metric properties in effect by order of precedence with the highest precedence first. If a
163+
* {@code MetricProperties} is configured for the metric name it has higher precedence than the
164+
* builder configuration. A special case is the setting {@link Builder#withoutExemplars()} via the
165+
* builder, which cannot be overridden by any configuration.
166+
*/
160167
protected MetricsProperties[] getMetricProperties(
161168
Builder<?, ?> builder, PrometheusProperties prometheusProperties) {
169+
List<MetricsProperties> properties = new ArrayList<>();
170+
if (Objects.equals(builder.exemplarsEnabled, false)) {
171+
properties.add(MetricsProperties.builder().exemplarsEnabled(false).build());
172+
}
162173
String metricName = getMetadata().getName();
163174
if (prometheusProperties.getMetricProperties(metricName) != null) {
164-
return new MetricsProperties[] {
165-
prometheusProperties.getMetricProperties(metricName), // highest precedence
166-
builder.toProperties(), // second-highest precedence
167-
prometheusProperties.getDefaultMetricProperties(), // third-highest precedence
168-
builder.getDefaultProperties() // fallback
169-
};
170-
} else {
171-
return new MetricsProperties[] {
172-
builder.toProperties(), // highest precedence
173-
prometheusProperties.getDefaultMetricProperties(), // second-highest precedence
174-
builder.getDefaultProperties() // fallback
175-
};
175+
properties.add(prometheusProperties.getMetricProperties(metricName));
176176
}
177+
properties.add(builder.toProperties());
178+
properties.add(prometheusProperties.getDefaultMetricProperties());
179+
properties.add(builder.getDefaultProperties()); // fallback
180+
return properties.toArray(new MetricsProperties[0]);
177181
}
178182

179183
protected <P> P getConfigProperty(

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,22 @@ public class Summary extends StatefulMetric<DistributionDataPoint, Summary.DataP
4444
private final List<CKMSQuantiles.Quantile> quantiles; // May be empty, but cannot be null.
4545
private final long maxAgeSeconds;
4646
private final int ageBuckets;
47-
private final boolean exemplarsEnabled;
4847
private final ExemplarSamplerConfig exemplarSamplerConfig;
4948

5049
private Summary(Builder builder, PrometheusProperties prometheusProperties) {
5150
super(builder);
5251
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
53-
this.exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
54-
this.quantiles = Collections.unmodifiableList(makeQuantiles(properties));
55-
this.maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds);
56-
this.ageBuckets =
57-
getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets);
58-
this.exemplarSamplerConfig =
59-
new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4);
52+
quantiles = Collections.unmodifiableList(makeQuantiles(properties));
53+
maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds);
54+
ageBuckets = getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets);
55+
boolean exemplarsEnabled =
56+
getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
57+
if (exemplarsEnabled) {
58+
exemplarSamplerConfig =
59+
new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4);
60+
} else {
61+
exemplarSamplerConfig = null;
62+
}
6063
}
6164

6265
private List<CKMSQuantiles.Quantile> makeQuantiles(MetricsProperties[] properties) {
@@ -79,7 +82,7 @@ private List<CKMSQuantiles.Quantile> makeQuantiles(MetricsProperties[] propertie
7982

8083
@Override
8184
protected boolean isExemplarsEnabled() {
82-
return exemplarsEnabled;
85+
return exemplarSamplerConfig != null;
8386
}
8487

8588
@Override
@@ -134,7 +137,7 @@ private DataPoint() {
134137
} else {
135138
quantileValues = null;
136139
}
137-
if (exemplarsEnabled) {
140+
if (isExemplarsEnabled()) {
138141
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
139142
} else {
140143
exemplarSampler = null;

0 commit comments

Comments
 (0)