Skip to content

Commit 0fa1ad7

Browse files
feat: Add logic for handling content negotation flag with OM2 (#1986)
closes #1946 --------- Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com> Co-authored-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent deb782f commit 0fa1ad7

File tree

2 files changed

+154
-27
lines changed

2 files changed

+154
-27
lines changed

prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,17 @@ public ExpositionFormatWriter findWriter(@Nullable String acceptHeader) {
6363
return prometheusProtobufWriter;
6464
}
6565

66-
// Prefer OM2 over OM1 when any OM2 feature is enabled
6766
if (isOpenMetrics2Enabled() && openMetrics2TextFormatWriter.accepts(acceptHeader)) {
68-
return openMetrics2TextFormatWriter;
67+
if (openMetrics2TextFormatWriter.getOpenMetrics2Properties().getContentNegotiation()) {
68+
String version = parseOpenMetricsVersion(acceptHeader);
69+
if ("2.0.0".equals(version)) {
70+
return openMetrics2TextFormatWriter;
71+
}
72+
// version=1.0.0 or no version: fall through to OM1
73+
} else {
74+
// contentNegotiation=false: OM2 handles all OpenMetrics requests
75+
return openMetrics2TextFormatWriter;
76+
}
6977
}
7078

7179
if (openMetricsTextFormatWriter.accepts(acceptHeader)) {
@@ -94,4 +102,27 @@ public OpenMetricsTextFormatWriter getOpenMetricsTextFormatWriter() {
94102
public OpenMetrics2TextFormatWriter getOpenMetrics2TextFormatWriter() {
95103
return openMetrics2TextFormatWriter;
96104
}
105+
106+
@Nullable
107+
private static String parseOpenMetricsVersion(@Nullable String acceptHeader) {
108+
if (acceptHeader == null) {
109+
return null;
110+
}
111+
for (String mediaType : acceptHeader.split(",")) {
112+
if (mediaType.contains("application/openmetrics-text")) {
113+
for (String param : mediaType.split(";")) {
114+
String[] tokens = param.split("=");
115+
if (tokens.length == 2) {
116+
String key = tokens[0].trim();
117+
String value = tokens[1].trim();
118+
if (key.equals("version")) {
119+
return value;
120+
}
121+
}
122+
}
123+
return null;
124+
}
125+
}
126+
return null;
127+
}
97128
}

prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java

Lines changed: 121 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ void testOM2EnabledWithFeatureFlags() {
155155
.build())
156156
.build();
157157
ExpositionFormats formats = ExpositionFormats.init(props);
158-
ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text");
158+
ExpositionFormatWriter writer =
159+
formats.findWriter("application/openmetrics-text; version=2.0.0");
159160
assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class);
160161
}
161162

@@ -173,6 +174,94 @@ void testProtobufWriterTakesPrecedence() {
173174
assertThat(writer).isInstanceOf(PrometheusProtobufWriter.class);
174175
}
175176

177+
@Test
178+
void testOM2ContentNegotiationWithVersion2() {
179+
PrometheusProperties props =
180+
PrometheusProperties.builder()
181+
.openMetrics2Properties(
182+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build())
183+
.build();
184+
ExpositionFormats formats = ExpositionFormats.init(props);
185+
ExpositionFormatWriter writer =
186+
formats.findWriter("application/openmetrics-text; version=2.0.0");
187+
assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class);
188+
}
189+
190+
@Test
191+
void testOM2ContentNegotiationWithVersion1() {
192+
PrometheusProperties props =
193+
PrometheusProperties.builder()
194+
.openMetrics2Properties(
195+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build())
196+
.build();
197+
ExpositionFormats formats = ExpositionFormats.init(props);
198+
ExpositionFormatWriter writer =
199+
formats.findWriter("application/openmetrics-text; version=1.0.0");
200+
assertThat(writer).isInstanceOf(OpenMetricsTextFormatWriter.class);
201+
}
202+
203+
@Test
204+
void testOM2ContentNegotiationWithNoVersion() {
205+
// When contentNegotiation=true and Accept header has no version, use OM1 writer
206+
PrometheusProperties props =
207+
PrometheusProperties.builder()
208+
.openMetrics2Properties(
209+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build())
210+
.build();
211+
ExpositionFormats formats = ExpositionFormats.init(props);
212+
ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text");
213+
assertThat(writer).isInstanceOf(OpenMetricsTextFormatWriter.class);
214+
}
215+
216+
@Test
217+
void testOM2ContentNegotiationDisabled() {
218+
PrometheusProperties props =
219+
PrometheusProperties.builder()
220+
.openMetrics2Properties(
221+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(false).build())
222+
.build();
223+
ExpositionFormats formats = ExpositionFormats.init(props);
224+
ExpositionFormatWriter writer1 = formats.findWriter("application/openmetrics-text");
225+
ExpositionFormatWriter writer2 =
226+
formats.findWriter("application/openmetrics-text; version=1.0.0");
227+
ExpositionFormatWriter writer3 =
228+
formats.findWriter("application/openmetrics-text; version=2.0.0");
229+
assertThat(writer1).isInstanceOf(OpenMetrics2TextFormatWriter.class);
230+
assertThat(writer2).isInstanceOf(OpenMetrics2TextFormatWriter.class);
231+
assertThat(writer3).isInstanceOf(OpenMetrics2TextFormatWriter.class);
232+
}
233+
234+
@Test
235+
void testOM2ContentNegotiationMultiTypeAcceptHeaderWithoutOMVersion() {
236+
// When the Accept header has multiple media types and only text/plain has a version,
237+
// the openmetrics type should be treated as having no version (use OM1).
238+
PrometheusProperties props =
239+
PrometheusProperties.builder()
240+
.openMetrics2Properties(
241+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build())
242+
.build();
243+
ExpositionFormats formats = ExpositionFormats.init(props);
244+
ExpositionFormatWriter writer =
245+
formats.findWriter("application/openmetrics-text, text/plain; version=0.0.4");
246+
assertThat(writer).isInstanceOf(OpenMetricsTextFormatWriter.class);
247+
}
248+
249+
@Test
250+
void testOM2ContentNegotiationMultiTypeAcceptHeaderWithOMVersion() {
251+
// When the Accept header has multiple media types and openmetrics has version=2.0.0,
252+
// the OM2 writer should be selected.
253+
PrometheusProperties props =
254+
PrometheusProperties.builder()
255+
.openMetrics2Properties(
256+
OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build())
257+
.build();
258+
ExpositionFormats formats = ExpositionFormats.init(props);
259+
ExpositionFormatWriter writer =
260+
formats.findWriter(
261+
"application/openmetrics-text; version=2.0.0, text/plain; version=0.0.4");
262+
assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class);
263+
}
264+
176265
@Test
177266
void testCounterComplete() throws IOException {
178267
String openMetricsText =
@@ -1977,14 +2066,14 @@ void testClassicGaugeHistogramCountAndSum() throws IOException {
19772066
void testClassicHistogramWithDots() throws IOException {
19782067
String openMetricsText =
19792068
"""
1980-
# TYPE U__my_2e_request_2e_duration_2e_seconds histogram
1981-
# UNIT U__my_2e_request_2e_duration_2e_seconds seconds
1982-
# HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds
1983-
U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 130 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383
1984-
U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 130
1985-
U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.01
1986-
# EOF
1987-
""";
2069+
# TYPE U__my_2e_request_2e_duration_2e_seconds histogram
2070+
# UNIT U__my_2e_request_2e_duration_2e_seconds seconds
2071+
# HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds
2072+
U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 130 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383
2073+
U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 130
2074+
U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.01
2075+
# EOF
2076+
""";
19882077
String openMetricsTextWithExemplarsOnAllTimeSeries =
19892078
"# TYPE my_request_duration_seconds histogram\n"
19902079
+ "# UNIT my_request_duration_seconds seconds\n"
@@ -2398,14 +2487,14 @@ void testNativeHistogramMinimal() throws IOException {
23982487
void testNativeHistogramWithDots() throws IOException {
23992488
String openMetricsText =
24002489
"""
2401-
# TYPE U__my_2e_request_2e_duration_2e_seconds histogram
2402-
# UNIT U__my_2e_request_2e_duration_2e_seconds seconds
2403-
# HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds
2404-
U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 4 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383
2405-
U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 4
2406-
U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 3.2
2407-
# EOF
2408-
""";
2490+
# TYPE U__my_2e_request_2e_duration_2e_seconds histogram
2491+
# UNIT U__my_2e_request_2e_duration_2e_seconds seconds
2492+
# HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds
2493+
U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 4 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383
2494+
U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 4
2495+
U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 3.2
2496+
# EOF
2497+
""";
24092498
String openMetricsTextWithExemplarsOnAllTimeSeries =
24102499
"# TYPE my_request_duration_seconds histogram\n"
24112500
+ "# UNIT my_request_duration_seconds seconds\n"
@@ -2876,12 +2965,19 @@ void testLabelValueEscape() throws IOException {
28762965

28772966
@ParameterizedTest
28782967
@CsvSource({
2879-
"'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores'",
2968+
"'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited',"
2969+
+ " 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily;"
2970+
+ " encoding=delimited; escaping=underscores'",
28802971
"'text/plain;version=0.0.4', 'text/plain; version=0.0.4; charset=utf-8; escaping=underscores'",
2881-
"'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8'",
2882-
"'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'",
2883-
"'application/openmetrics-text;version=0.0.1; escaping=underscores', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'",
2884-
"'text/plain;version=0.0.4; escaping=allow-utf-8', 'text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8'"
2972+
"'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;"
2973+
+ " escaping=allow-utf-8', 'application/vnd.google.protobuf;"
2974+
+ " proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8'",
2975+
"'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8;"
2976+
+ " escaping=underscores'",
2977+
"'application/openmetrics-text;version=0.0.1; escaping=underscores',"
2978+
+ " 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'",
2979+
"'text/plain;version=0.0.4; escaping=allow-utf-8', 'text/plain; version=0.0.4; charset=utf-8;"
2980+
+ " escaping=allow-utf-8'"
28852981
})
28862982
public void testFindWriter(String acceptHeaderValue, String expectedFmt) {
28872983
ExpositionFormats expositionFormats = ExpositionFormats.init();
@@ -2910,9 +3006,9 @@ void testWrite() throws IOException {
29103006

29113007
String expected =
29123008
"""
2913-
# TYPE foo_metric untyped
2914-
foo_metric 1.234
2915-
""";
3009+
# TYPE foo_metric untyped
3010+
foo_metric 1.234
3011+
""";
29163012

29173013
assertThat(new String(out, UTF_8)).hasToString(expected);
29183014
}

0 commit comments

Comments
 (0)