Skip to content

Commit 9ba5246

Browse files
committed
protobuf
1 parent 8148c35 commit 9ba5246

File tree

11 files changed

+470
-166
lines changed

11 files changed

+470
-166
lines changed

prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.google.protobuf.TextFormat;
77
import io.prometheus.metrics.config.EscapingScheme;
88
import io.prometheus.metrics.expositionformats.ExpositionFormatWriter;
9+
import io.prometheus.metrics.expositionformats.TextFormatUtil;
910
import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_1.Metrics;
1011
import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
1112
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
@@ -43,8 +44,9 @@ public String getContentType() {
4344

4445
@Override
4546
public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) {
47+
MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots);
4648
StringBuilder stringBuilder = new StringBuilder();
47-
for (MetricSnapshot s : metricSnapshots) {
49+
for (MetricSnapshot s : merged) {
4850
MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
4951
if (!snapshot.getDataPoints().isEmpty()) {
5052
stringBuilder.append(TextFormat.printer().printToString(convert(snapshot, escapingScheme)));
@@ -57,7 +59,8 @@ public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esca
5759
public void write(
5860
OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme)
5961
throws IOException {
60-
for (MetricSnapshot s : metricSnapshots) {
62+
MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots);
63+
for (MetricSnapshot s : merged) {
6164
MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
6265
if (!snapshot.getDataPoints().isEmpty()) {
6366
convert(snapshot, escapingScheme).writeDelimitedTo(out);
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
package io.prometheus.metrics.expositionformats;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import io.prometheus.metrics.config.EscapingScheme;
6+
import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_1.Metrics;
7+
import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl;
8+
import io.prometheus.metrics.model.registry.Collector;
9+
import io.prometheus.metrics.model.registry.PrometheusRegistry;
10+
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
11+
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
12+
import io.prometheus.metrics.model.snapshots.Labels;
13+
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
14+
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
15+
import java.io.ByteArrayInputStream;
16+
import java.io.ByteArrayOutputStream;
17+
import java.io.IOException;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import org.junit.jupiter.api.Test;
21+
22+
class DuplicateNamesProtobufTest {
23+
24+
private static PrometheusRegistry getPrometheusRegistry() {
25+
PrometheusRegistry registry = new PrometheusRegistry();
26+
27+
registry.register(
28+
new Collector() {
29+
@Override
30+
public MetricSnapshot collect() {
31+
return CounterSnapshot.builder()
32+
.name("api_responses")
33+
.help("API responses")
34+
.dataPoint(
35+
CounterSnapshot.CounterDataPointSnapshot.builder()
36+
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
37+
.value(100)
38+
.build())
39+
.build();
40+
}
41+
42+
@Override
43+
public String getPrometheusName() {
44+
return "api_responses_total";
45+
}
46+
});
47+
48+
registry.register(
49+
new Collector() {
50+
@Override
51+
public MetricSnapshot collect() {
52+
return CounterSnapshot.builder()
53+
.name("api_responses")
54+
.help("API responses")
55+
.dataPoint(
56+
CounterSnapshot.CounterDataPointSnapshot.builder()
57+
.labels(
58+
Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT"))
59+
.value(10)
60+
.build())
61+
.build();
62+
}
63+
64+
@Override
65+
public String getPrometheusName() {
66+
return "api_responses_total";
67+
}
68+
});
69+
return registry;
70+
}
71+
72+
@Test
73+
void testDuplicateNames_differentLabels_producesSingleMetricFamily() throws IOException {
74+
PrometheusRegistry registry = getPrometheusRegistry();
75+
76+
MetricSnapshots snapshots = registry.scrape();
77+
ByteArrayOutputStream out = new ByteArrayOutputStream();
78+
PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
79+
writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
80+
81+
List<Metrics.MetricFamily> metricFamilies = parseProtobufOutput(out);
82+
83+
assertThat(metricFamilies).hasSize(1);
84+
Metrics.MetricFamily family = metricFamilies.get(0);
85+
assertThat(family.getName()).isEqualTo("api_responses_total");
86+
assertThat(family.getHelp()).isEqualTo("API responses");
87+
assertThat(family.getType()).isEqualTo(Metrics.MetricType.COUNTER);
88+
assertThat(family.getMetricCount()).isEqualTo(2);
89+
90+
Metrics.Metric successMetric =
91+
family.getMetricList().stream()
92+
.filter(
93+
m ->
94+
m.getLabelList().stream()
95+
.anyMatch(
96+
l -> l.getName().equals("outcome") && l.getValue().equals("SUCCESS")))
97+
.findFirst()
98+
.orElseThrow(() -> new AssertionError("SUCCESS metric not found"));
99+
assertThat(successMetric.getCounter().getValue()).isEqualTo(100.0);
100+
101+
Metrics.Metric failureMetric =
102+
family.getMetricList().stream()
103+
.filter(
104+
m ->
105+
m.getLabelList().stream()
106+
.anyMatch(
107+
l ->
108+
l.getName().equals("outcome")
109+
&& l.getValue().equals("FAILURE"))
110+
&& m.getLabelList().stream()
111+
.anyMatch(
112+
l -> l.getName().equals("error") && l.getValue().equals("TIMEOUT")))
113+
.findFirst()
114+
.orElseThrow(() -> new AssertionError("FAILURE metric not found"));
115+
assertThat(failureMetric.getCounter().getValue()).isEqualTo(10.0);
116+
}
117+
118+
@Test
119+
void testDuplicateNames_multipleDataPoints_producesSingleMetricFamily() throws IOException {
120+
PrometheusRegistry registry = new PrometheusRegistry();
121+
122+
registry.register(
123+
new Collector() {
124+
@Override
125+
public MetricSnapshot collect() {
126+
return CounterSnapshot.builder()
127+
.name("api_responses")
128+
.help("API responses")
129+
.dataPoint(
130+
CounterSnapshot.CounterDataPointSnapshot.builder()
131+
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
132+
.value(100)
133+
.build())
134+
.dataPoint(
135+
CounterSnapshot.CounterDataPointSnapshot.builder()
136+
.labels(Labels.of("uri", "/world", "outcome", "SUCCESS"))
137+
.value(200)
138+
.build())
139+
.build();
140+
}
141+
142+
@Override
143+
public String getPrometheusName() {
144+
return "api_responses_total";
145+
}
146+
});
147+
148+
registry.register(
149+
new Collector() {
150+
@Override
151+
public MetricSnapshot collect() {
152+
return CounterSnapshot.builder()
153+
.name("api_responses")
154+
.help("API responses")
155+
.dataPoint(
156+
CounterSnapshot.CounterDataPointSnapshot.builder()
157+
.labels(
158+
Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT"))
159+
.value(10)
160+
.build())
161+
.dataPoint(
162+
CounterSnapshot.CounterDataPointSnapshot.builder()
163+
.labels(
164+
Labels.of("uri", "/world", "outcome", "FAILURE", "error", "NOT_FOUND"))
165+
.value(5)
166+
.build())
167+
.build();
168+
}
169+
170+
@Override
171+
public String getPrometheusName() {
172+
return "api_responses_total";
173+
}
174+
});
175+
176+
MetricSnapshots snapshots = registry.scrape();
177+
ByteArrayOutputStream out = new ByteArrayOutputStream();
178+
PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
179+
writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
180+
181+
List<Metrics.MetricFamily> metricFamilies = parseProtobufOutput(out);
182+
183+
assertThat(metricFamilies).hasSize(1);
184+
Metrics.MetricFamily family = metricFamilies.get(0);
185+
assertThat(family.getName()).isEqualTo("api_responses_total");
186+
assertThat(family.getMetricCount()).isEqualTo(4);
187+
188+
long successCount =
189+
family.getMetricList().stream()
190+
.filter(
191+
m ->
192+
m.getLabelList().stream()
193+
.anyMatch(
194+
l -> l.getName().equals("outcome") && l.getValue().equals("SUCCESS")))
195+
.count();
196+
197+
long failureCount =
198+
family.getMetricList().stream()
199+
.filter(
200+
m ->
201+
m.getLabelList().stream()
202+
.anyMatch(
203+
l -> l.getName().equals("outcome") && l.getValue().equals("FAILURE")))
204+
.count();
205+
206+
assertThat(successCount).isEqualTo(2);
207+
assertThat(failureCount).isEqualTo(2);
208+
}
209+
210+
@Test
211+
void testDifferentMetrics_producesSeparateMetricFamilies()
212+
throws IOException {
213+
MetricSnapshots snapshots = getMetricSnapshots();
214+
ByteArrayOutputStream out = new ByteArrayOutputStream();
215+
PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
216+
writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
217+
218+
List<Metrics.MetricFamily> metricFamilies = parseProtobufOutput(out);
219+
220+
assertThat(metricFamilies).hasSize(2);
221+
222+
Metrics.MetricFamily counterFamily = null;
223+
Metrics.MetricFamily gaugeFamily = null;
224+
for (Metrics.MetricFamily family : metricFamilies) {
225+
if (family.getName().equals("http_requests_total")) {
226+
counterFamily = family;
227+
} else if (family.getName().equals("active_sessions")) {
228+
gaugeFamily = family;
229+
}
230+
}
231+
232+
assertThat(counterFamily).isNotNull();
233+
assertThat(counterFamily.getType()).isEqualTo(Metrics.MetricType.COUNTER);
234+
assertThat(counterFamily.getMetricCount()).isEqualTo(1);
235+
assertThat(counterFamily.getMetric(0).getCounter().getValue()).isEqualTo(100.0);
236+
237+
assertThat(gaugeFamily).isNotNull();
238+
assertThat(gaugeFamily.getType()).isEqualTo(Metrics.MetricType.GAUGE);
239+
assertThat(gaugeFamily.getMetricCount()).isEqualTo(1);
240+
assertThat(gaugeFamily.getMetric(0).getGauge().getValue()).isEqualTo(50.0);
241+
}
242+
243+
private static MetricSnapshots getMetricSnapshots() {
244+
PrometheusRegistry registry = new PrometheusRegistry();
245+
246+
registry.register(
247+
new Collector() {
248+
@Override
249+
public MetricSnapshot collect() {
250+
return CounterSnapshot.builder()
251+
.name("http_requests")
252+
.help("HTTP Request counter")
253+
.dataPoint(
254+
CounterSnapshot.CounterDataPointSnapshot.builder()
255+
.labels(Labels.of("method", "GET"))
256+
.value(100)
257+
.build())
258+
.build();
259+
}
260+
261+
@Override
262+
public String getPrometheusName() {
263+
return "http_requests_total";
264+
}
265+
});
266+
267+
registry.register(
268+
new Collector() {
269+
@Override
270+
public MetricSnapshot collect() {
271+
return GaugeSnapshot.builder()
272+
.name("active_sessions")
273+
.help("Active sessions gauge")
274+
.dataPoint(
275+
GaugeSnapshot.GaugeDataPointSnapshot.builder()
276+
.labels(Labels.of("method", "POST"))
277+
.value(50)
278+
.build())
279+
.build();
280+
}
281+
282+
@Override
283+
public String getPrometheusName() {
284+
return "active_sessions";
285+
}
286+
});
287+
288+
return registry.scrape();
289+
}
290+
291+
private List<Metrics.MetricFamily> parseProtobufOutput(ByteArrayOutputStream out)
292+
throws IOException {
293+
List<Metrics.MetricFamily> metricFamilies = new ArrayList<>();
294+
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
295+
while (in.available() > 0) {
296+
metricFamilies.add(Metrics.MetricFamily.parseDelimitedFrom(in));
297+
}
298+
return metricFamilies;
299+
}
300+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import static io.prometheus.metrics.expositionformats.TextFormatUtil.mergeDuplicates;
44
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
5+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
56
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
67
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
78
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
89
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp;
9-
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
1010
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
1111
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
1212

0 commit comments

Comments
 (0)