Skip to content

Commit ec60b58

Browse files
committed
Add OpenMetrics2 configuration support
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
1 parent e6eb2f9 commit ec60b58

5 files changed

Lines changed: 374 additions & 3 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package io.prometheus.metrics.config;
2+
3+
import javax.annotation.Nullable;
4+
5+
/** Properties starting with io.prometheus.open_metrics2 */
6+
public class OpenMetrics2Properties {
7+
8+
private static final String PREFIX = "io.prometheus.open_metrics2";
9+
private static final String CONTENT_NEGOTIATION = "content_negotiation";
10+
private static final String DISABLE_SUFFIX_APPENDING = "disable_suffix_appending";
11+
private static final String COMPOSITE_VALUES = "composite_values";
12+
private static final String EXEMPLAR_COMPLIANCE = "exemplar_compliance";
13+
private static final String NATIVE_HISTOGRAMS = "native_histograms";
14+
15+
@Nullable private final Boolean contentNegotiation;
16+
@Nullable private final Boolean disableSuffixAppending;
17+
@Nullable private final Boolean compositeValues;
18+
@Nullable private final Boolean exemplarCompliance;
19+
@Nullable private final Boolean nativeHistograms;
20+
21+
private OpenMetrics2Properties(
22+
@Nullable Boolean contentNegotiation,
23+
@Nullable Boolean disableSuffixAppending,
24+
@Nullable Boolean compositeValues,
25+
@Nullable Boolean exemplarCompliance,
26+
@Nullable Boolean nativeHistograms) {
27+
this.contentNegotiation = contentNegotiation;
28+
this.disableSuffixAppending = disableSuffixAppending;
29+
this.compositeValues = compositeValues;
30+
this.exemplarCompliance = exemplarCompliance;
31+
this.nativeHistograms = nativeHistograms;
32+
}
33+
34+
/** Gate OM2 features behind content negotiation. Default is {@code false}. */
35+
public boolean getContentNegotiation() {
36+
return contentNegotiation != null && contentNegotiation;
37+
}
38+
39+
/** Suppress {@code _total} and unit suffixes. Default is {@code false}. */
40+
public boolean getDisableSuffixAppending() {
41+
return disableSuffixAppending != null && disableSuffixAppending;
42+
}
43+
44+
/** Single-line histogram/summary with {@code st@}. Default is {@code false}. */
45+
public boolean getCompositeValues() {
46+
return compositeValues != null && compositeValues;
47+
}
48+
49+
/** Mandatory timestamps, no 128-char limit for exemplars. Default is {@code false}. */
50+
public boolean getExemplarCompliance() {
51+
return exemplarCompliance != null && exemplarCompliance;
52+
}
53+
54+
/** Exponential buckets support for native histograms. Default is {@code false}. */
55+
public boolean getNativeHistograms() {
56+
return nativeHistograms != null && nativeHistograms;
57+
}
58+
59+
/**
60+
* Note that this will remove entries from {@code propertySource}. This is because we want to know
61+
* if there are unused properties remaining after all properties have been loaded.
62+
*/
63+
static OpenMetrics2Properties load(PropertySource propertySource)
64+
throws PrometheusPropertiesException {
65+
Boolean contentNegotiation = Util.loadBoolean(PREFIX, CONTENT_NEGOTIATION, propertySource);
66+
Boolean disableSuffixAppending =
67+
Util.loadBoolean(PREFIX, DISABLE_SUFFIX_APPENDING, propertySource);
68+
Boolean compositeValues = Util.loadBoolean(PREFIX, COMPOSITE_VALUES, propertySource);
69+
Boolean exemplarCompliance = Util.loadBoolean(PREFIX, EXEMPLAR_COMPLIANCE, propertySource);
70+
Boolean nativeHistograms = Util.loadBoolean(PREFIX, NATIVE_HISTOGRAMS, propertySource);
71+
return new OpenMetrics2Properties(
72+
contentNegotiation,
73+
disableSuffixAppending,
74+
compositeValues,
75+
exemplarCompliance,
76+
nativeHistograms);
77+
}
78+
79+
public static Builder builder() {
80+
return new Builder();
81+
}
82+
83+
public static class Builder {
84+
85+
@Nullable private Boolean contentNegotiation;
86+
@Nullable private Boolean disableSuffixAppending;
87+
@Nullable private Boolean compositeValues;
88+
@Nullable private Boolean exemplarCompliance;
89+
@Nullable private Boolean nativeHistograms;
90+
91+
private Builder() {}
92+
93+
/** See {@link #getContentNegotiation()} */
94+
public Builder contentNegotiation(boolean contentNegotiation) {
95+
this.contentNegotiation = contentNegotiation;
96+
return this;
97+
}
98+
99+
/** See {@link #getDisableSuffixAppending()} */
100+
public Builder disableSuffixAppending(boolean disableSuffixAppending) {
101+
this.disableSuffixAppending = disableSuffixAppending;
102+
return this;
103+
}
104+
105+
/** See {@link #getCompositeValues()} */
106+
public Builder compositeValues(boolean compositeValues) {
107+
this.compositeValues = compositeValues;
108+
return this;
109+
}
110+
111+
/** See {@link #getExemplarCompliance()} */
112+
public Builder exemplarCompliance(boolean exemplarCompliance) {
113+
this.exemplarCompliance = exemplarCompliance;
114+
return this;
115+
}
116+
117+
/** See {@link #getNativeHistograms()} */
118+
public Builder nativeHistograms(boolean nativeHistograms) {
119+
this.nativeHistograms = nativeHistograms;
120+
return this;
121+
}
122+
123+
/** Enable all OpenMetrics 2.0 features */
124+
public Builder enableAll() {
125+
this.contentNegotiation = true;
126+
this.disableSuffixAppending = true;
127+
this.compositeValues = true;
128+
this.exemplarCompliance = true;
129+
this.nativeHistograms = true;
130+
return this;
131+
}
132+
133+
public OpenMetrics2Properties build() {
134+
return new OpenMetrics2Properties(
135+
contentNegotiation,
136+
disableSuffixAppending,
137+
compositeValues,
138+
exemplarCompliance,
139+
nativeHistograms);
140+
}
141+
}
142+
}

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.HashMap;
44
import java.util.Map;
5+
import java.util.function.Consumer;
56
import javax.annotation.Nullable;
67

78
/**
@@ -21,6 +22,7 @@ public class PrometheusProperties {
2122
private final ExporterHttpServerProperties exporterHttpServerProperties;
2223
private final ExporterOpenTelemetryProperties exporterOpenTelemetryProperties;
2324
private final ExporterPushgatewayProperties exporterPushgatewayProperties;
25+
private final OpenMetrics2Properties openMetrics2Properties;
2426

2527
/**
2628
* Map that stores metric-specific properties keyed by metric name in exposition format
@@ -111,7 +113,8 @@ public static Builder builder() {
111113
ExporterFilterProperties exporterFilterProperties,
112114
ExporterHttpServerProperties httpServerConfig,
113115
ExporterPushgatewayProperties pushgatewayProperties,
114-
ExporterOpenTelemetryProperties otelConfig) {
116+
ExporterOpenTelemetryProperties otelConfig,
117+
OpenMetrics2Properties openMetrics2Properties) {
115118
this.defaultMetricsProperties = defaultMetricsProperties;
116119
this.metricProperties = metricProperties;
117120
this.exemplarProperties = exemplarProperties;
@@ -120,6 +123,7 @@ public static Builder builder() {
120123
this.exporterHttpServerProperties = httpServerConfig;
121124
this.exporterPushgatewayProperties = pushgatewayProperties;
122125
this.exporterOpenTelemetryProperties = otelConfig;
126+
this.openMetrics2Properties = openMetrics2Properties;
123127
}
124128

125129
/**
@@ -167,6 +171,10 @@ public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() {
167171
return exporterOpenTelemetryProperties;
168172
}
169173

174+
public OpenMetrics2Properties getOpenMetrics2Properties() {
175+
return openMetrics2Properties;
176+
}
177+
170178
public static class Builder {
171179
private MetricsProperties defaultMetricsProperties = MetricsProperties.builder().build();
172180
private final MetricPropertiesMap metricProperties = new MetricPropertiesMap();
@@ -180,6 +188,8 @@ public static class Builder {
180188
ExporterPushgatewayProperties.builder().build();
181189
private ExporterOpenTelemetryProperties otelConfig =
182190
ExporterOpenTelemetryProperties.builder().build();
191+
private OpenMetrics2Properties openMetrics2Properties =
192+
OpenMetrics2Properties.builder().build();
183193

184194
private Builder() {}
185195

@@ -231,6 +241,18 @@ public Builder exporterOpenTelemetryProperties(
231241
return this;
232242
}
233243

244+
public Builder enableOpenMetrics2(Consumer<OpenMetrics2Properties.Builder> configurator) {
245+
OpenMetrics2Properties.Builder openMetrics2Builder = OpenMetrics2Properties.builder();
246+
configurator.accept(openMetrics2Builder);
247+
this.openMetrics2Properties = openMetrics2Builder.build();
248+
return this;
249+
}
250+
251+
public Builder openMetrics2Properties(OpenMetrics2Properties openMetrics2Properties) {
252+
this.openMetrics2Properties = openMetrics2Properties;
253+
return this;
254+
}
255+
234256
public PrometheusProperties build() {
235257
return new PrometheusProperties(
236258
defaultMetricsProperties,
@@ -240,7 +262,8 @@ public PrometheusProperties build() {
240262
exporterFilterProperties,
241263
exporterHttpServerProperties,
242264
pushgatewayProperties,
243-
otelConfig);
265+
otelConfig,
266+
openMetrics2Properties);
244267
}
245268
}
246269
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static PrometheusProperties load(Map<Object, Object> externalProperties)
4040
ExporterPushgatewayProperties.load(propertySource);
4141
ExporterOpenTelemetryProperties exporterOpenTelemetryProperties =
4242
ExporterOpenTelemetryProperties.load(propertySource);
43+
OpenMetrics2Properties openMetrics2Properties = OpenMetrics2Properties.load(propertySource);
4344
validateAllPropertiesProcessed(propertySource);
4445
return new PrometheusProperties(
4546
defaultMetricsProperties,
@@ -49,7 +50,8 @@ public static PrometheusProperties load(Map<Object, Object> externalProperties)
4950
exporterFilterProperties,
5051
exporterHttpServerProperties,
5152
exporterPushgatewayProperties,
52-
exporterOpenTelemetryProperties);
53+
exporterOpenTelemetryProperties,
54+
openMetrics2Properties);
5355
}
5456

5557
// This will remove entries from propertySource when they are processed.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package io.prometheus.metrics.config;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import org.junit.jupiter.api.Test;
9+
10+
class OpenMetrics2PropertiesTest {
11+
12+
@Test
13+
void load() {
14+
OpenMetrics2Properties properties =
15+
load(
16+
new HashMap<>(
17+
Map.of(
18+
"io.prometheus.open_metrics2.content_negotiation",
19+
"true",
20+
"io.prometheus.open_metrics2.disable_suffix_appending",
21+
"true",
22+
"io.prometheus.open_metrics2.composite_values",
23+
"true",
24+
"io.prometheus.open_metrics2.exemplar_compliance",
25+
"true",
26+
"io.prometheus.open_metrics2.native_histograms",
27+
"true")));
28+
assertThat(properties.getContentNegotiation()).isTrue();
29+
assertThat(properties.getDisableSuffixAppending()).isTrue();
30+
assertThat(properties.getCompositeValues()).isTrue();
31+
assertThat(properties.getExemplarCompliance()).isTrue();
32+
assertThat(properties.getNativeHistograms()).isTrue();
33+
}
34+
35+
@Test
36+
void loadInvalidValue() {
37+
assertThatExceptionOfType(PrometheusPropertiesException.class)
38+
.isThrownBy(
39+
() ->
40+
load(
41+
new HashMap<>(
42+
Map.of("io.prometheus.open_metrics2.content_negotiation", "invalid"))))
43+
.withMessage(
44+
"io.prometheus.open_metrics2.content_negotiation: Expecting 'true' or 'false'. Found:"
45+
+ " invalid");
46+
assertThatExceptionOfType(PrometheusPropertiesException.class)
47+
.isThrownBy(
48+
() ->
49+
load(
50+
new HashMap<>(
51+
Map.of("io.prometheus.open_metrics2.disable_suffix_appending", "invalid"))))
52+
.withMessage(
53+
"io.prometheus.open_metrics2.disable_suffix_appending: Expecting 'true' or 'false'."
54+
+ " Found: invalid");
55+
assertThatExceptionOfType(PrometheusPropertiesException.class)
56+
.isThrownBy(
57+
() ->
58+
load(
59+
new HashMap<>(
60+
Map.of("io.prometheus.open_metrics2.composite_values", "invalid"))))
61+
.withMessage(
62+
"io.prometheus.open_metrics2.composite_values: Expecting 'true' or 'false'. Found:"
63+
+ " invalid");
64+
assertThatExceptionOfType(PrometheusPropertiesException.class)
65+
.isThrownBy(
66+
() ->
67+
load(
68+
new HashMap<>(
69+
Map.of("io.prometheus.open_metrics2.exemplar_compliance", "invalid"))))
70+
.withMessage(
71+
"io.prometheus.open_metrics2.exemplar_compliance: Expecting 'true' or 'false'. Found:"
72+
+ " invalid");
73+
assertThatExceptionOfType(PrometheusPropertiesException.class)
74+
.isThrownBy(
75+
() ->
76+
load(
77+
new HashMap<>(
78+
Map.of("io.prometheus.open_metrics2.native_histograms", "invalid"))))
79+
.withMessage(
80+
"io.prometheus.open_metrics2.native_histograms: Expecting 'true' or 'false'. Found:"
81+
+ " invalid");
82+
}
83+
84+
private static OpenMetrics2Properties load(Map<String, String> map) {
85+
Map<Object, Object> regularProperties = new HashMap<>(map);
86+
PropertySource propertySource = new PropertySource(regularProperties);
87+
return OpenMetrics2Properties.load(propertySource);
88+
}
89+
90+
@Test
91+
void builder() {
92+
OpenMetrics2Properties properties =
93+
OpenMetrics2Properties.builder()
94+
.contentNegotiation(true)
95+
.disableSuffixAppending(true)
96+
.compositeValues(false)
97+
.exemplarCompliance(true)
98+
.nativeHistograms(false)
99+
.build();
100+
assertThat(properties.getContentNegotiation()).isTrue();
101+
assertThat(properties.getDisableSuffixAppending()).isTrue();
102+
assertThat(properties.getCompositeValues()).isFalse();
103+
assertThat(properties.getExemplarCompliance()).isTrue();
104+
assertThat(properties.getNativeHistograms()).isFalse();
105+
}
106+
107+
@Test
108+
void builderEnableAll() {
109+
OpenMetrics2Properties properties = OpenMetrics2Properties.builder().enableAll().build();
110+
assertThat(properties.getContentNegotiation()).isTrue();
111+
assertThat(properties.getDisableSuffixAppending()).isTrue();
112+
assertThat(properties.getCompositeValues()).isTrue();
113+
assertThat(properties.getExemplarCompliance()).isTrue();
114+
assertThat(properties.getNativeHistograms()).isTrue();
115+
}
116+
117+
@Test
118+
void defaultValues() {
119+
OpenMetrics2Properties properties = OpenMetrics2Properties.builder().build();
120+
assertThat(properties.getContentNegotiation()).isFalse();
121+
assertThat(properties.getDisableSuffixAppending()).isFalse();
122+
assertThat(properties.getCompositeValues()).isFalse();
123+
assertThat(properties.getExemplarCompliance()).isFalse();
124+
assertThat(properties.getNativeHistograms()).isFalse();
125+
}
126+
127+
@Test
128+
void partialConfiguration() {
129+
OpenMetrics2Properties properties =
130+
OpenMetrics2Properties.builder().contentNegotiation(true).compositeValues(true).build();
131+
assertThat(properties.getContentNegotiation()).isTrue();
132+
assertThat(properties.getDisableSuffixAppending()).isFalse();
133+
assertThat(properties.getCompositeValues()).isTrue();
134+
assertThat(properties.getExemplarCompliance()).isFalse();
135+
assertThat(properties.getNativeHistograms()).isFalse();
136+
}
137+
}

0 commit comments

Comments
 (0)