Skip to content

Commit 44d01a1

Browse files
committed
register validation
1 parent a57de22 commit 44d01a1

File tree

15 files changed

+740
-255
lines changed

15 files changed

+740
-255
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricDat
9292
return new CounterSnapshot(getMetadata(), data);
9393
}
9494

95+
@Override
96+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
97+
return io.prometheus.metrics.model.registry.MetricType.COUNTER;
98+
}
99+
95100
@Override
96101
protected DataPoint newDataPoint() {
97102
if (exemplarSamplerConfig != null) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ protected GaugeSnapshot collect(List<Labels> labels, List<DataPoint> metricData)
9494
return new GaugeSnapshot(getMetadata(), dataPointSnapshots);
9595
}
9696

97+
@Override
98+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
99+
return io.prometheus.metrics.model.registry.MetricType.GAUGE;
100+
}
101+
97102
@Override
98103
protected DataPoint newDataPoint() {
99104
if (exemplarSamplerConfig != null) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,11 @@ public HistogramSnapshot collect() {
640640
return (HistogramSnapshot) super.collect();
641641
}
642642

643+
@Override
644+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
645+
return io.prometheus.metrics.model.registry.MetricType.HISTOGRAM;
646+
}
647+
643648
@Override
644649
protected HistogramSnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
645650
List<HistogramSnapshot.HistogramDataPointSnapshot> data = new ArrayList<>(labels.size());

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public InfoSnapshot collect() {
105105
return new InfoSnapshot(getMetadata(), data);
106106
}
107107

108+
@Override
109+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
110+
return io.prometheus.metrics.model.registry.MetricType.INFO;
111+
}
112+
108113
public static Builder builder() {
109114
return new Builder(PrometheusProperties.get());
110115
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ public StateSetSnapshot collect() {
7373
return (StateSetSnapshot) super.collect();
7474
}
7575

76+
@Override
77+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
78+
return io.prometheus.metrics.model.registry.MetricType.STATESET;
79+
}
80+
7681
@Override
7782
protected StateSetSnapshot collect(List<Labels> labels, List<DataPoint> metricDataList) {
7883
List<StateSetSnapshot.StateSetDataPointSnapshot> data = new ArrayList<>(labels.size());

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ public SummarySnapshot collect() {
109109
return (SummarySnapshot) super.collect();
110110
}
111111

112+
@Override
113+
public io.prometheus.metrics.model.registry.MetricType getMetricType() {
114+
return io.prometheus.metrics.model.registry.MetricType.SUMMARY;
115+
}
116+
112117
@Override
113118
protected SummarySnapshot collect(List<Labels> labels, List<DataPoint> metricData) {
114119
List<SummarySnapshot.SummaryDataPointSnapshot> data = new ArrayList<>(labels.size());

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package io.prometheus.metrics.expositionformats;
22

3-
import static io.prometheus.metrics.expositionformats.TextFormatUtil.*;
3+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.mergeDuplicates;
4+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
5+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
6+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
7+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
8+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp;
9+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
410
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
511
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
612

@@ -30,9 +36,7 @@
3036
import java.io.OutputStreamWriter;
3137
import java.io.Writer;
3238
import java.nio.charset.StandardCharsets;
33-
import java.util.LinkedHashMap;
3439
import java.util.List;
35-
import java.util.Map;
3640
import javax.annotation.Nullable;
3741

3842
/**

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package io.prometheus.metrics.expositionformats;
22

3-
import static io.prometheus.metrics.expositionformats.TextFormatUtil.*;
3+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.mergeDuplicates;
4+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
5+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
6+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
7+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
8+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
9+
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp;
410
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot;
511
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
612
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;

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

Lines changed: 75 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -75,87 +75,20 @@ void testDuplicateNames_differentLabels_producesValidOutput() throws IOException
7575
writer.write(out, snapshots);
7676
String output = out.toString(UTF_8);
7777

78-
System.out.println("=== Duplicate Names (Different Labels) Output ===");
79-
System.out.println(output);
80-
System.out.println("=== End Output ===\n");
81-
82-
// Verify output contains both metrics
83-
assertThat(output).contains("api_responses_total{");
84-
assertThat(output).contains("outcome=\"SUCCESS\"");
85-
assertThat(output).contains("outcome=\"FAILURE\"");
86-
assertThat(output).contains("error=\"TIMEOUT\"");
87-
assertThat(output).contains(" 100");
88-
assertThat(output).contains(" 10");
89-
90-
// Verify TYPE declaration appears (may appear multiple times)
91-
assertThat(output).contains("# TYPE api_responses_total counter");
92-
93-
// Count how many times the metric name appears in data lines
94-
long metricLines =
95-
output.lines().filter(line -> line.startsWith("api_responses_total{")).count();
96-
assertThat(metricLines).isEqualTo(2);
97-
}
98-
99-
@Test
100-
void testDuplicateNames_sameLabels_throwsException() {
101-
PrometheusRegistry registry = new PrometheusRegistry();
102-
103-
// Counter 1
104-
registry.register(
105-
new Collector() {
106-
@Override
107-
public MetricSnapshot collect() {
108-
return CounterSnapshot.builder()
109-
.name("api_responses")
110-
.help("API responses")
111-
.dataPoint(
112-
CounterSnapshot.CounterDataPointSnapshot.builder()
113-
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
114-
.value(100)
115-
.build())
116-
.build();
117-
}
118-
119-
@Override
120-
public String getPrometheusName() {
121-
return "api_responses_total";
122-
}
123-
});
124-
125-
// Counter 2: SAME labels, different value - this should cause an exception during scrape
126-
registry.register(
127-
new Collector() {
128-
@Override
129-
public MetricSnapshot collect() {
130-
return CounterSnapshot.builder()
131-
.name("api_responses")
132-
.help("API responses")
133-
.dataPoint(
134-
CounterSnapshot.CounterDataPointSnapshot.builder()
135-
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
136-
.value(50)
137-
.build())
138-
.build();
139-
}
140-
141-
@Override
142-
public String getPrometheusName() {
143-
return "api_responses_total";
144-
}
145-
});
146-
147-
// Scraping should throw exception due to duplicate time series (same name + same labels)
148-
assertThatThrownBy(registry::scrape)
149-
.isInstanceOf(IllegalArgumentException.class)
150-
.hasMessageContaining("Duplicate labels detected")
151-
.hasMessageContaining("api_responses");
78+
String expected = """
79+
# HELP api_responses_total API responses
80+
# TYPE api_responses_total counter
81+
api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0
82+
api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0
83+
""";
84+
85+
assertThat(output).isEqualTo(expected);
15286
}
15387

15488
@Test
15589
void testDuplicateNames_multipleDataPoints_producesValidOutput() throws IOException {
15690
PrometheusRegistry registry = new PrometheusRegistry();
15791

158-
// Counter 1: Multiple data points
15992
registry.register(
16093
new Collector() {
16194
@Override
@@ -182,7 +115,6 @@ public String getPrometheusName() {
182115
}
183116
});
184117

185-
// Counter 2: Multiple data points with additional error label
186118
registry.register(
187119
new Collector() {
188120
@Override
@@ -211,112 +143,94 @@ public String getPrometheusName() {
211143
}
212144
});
213145

214-
// Scrape and write to text format
215146
MetricSnapshots snapshots = registry.scrape();
216147
ByteArrayOutputStream out = new ByteArrayOutputStream();
217148
PrometheusTextFormatWriter writer = PrometheusTextFormatWriter.create();
218149
writer.write(out, snapshots);
219150
String output = out.toString(UTF_8);
220151

221-
System.out.println("=== Duplicate Names (Multiple Data Points) Output ===");
222-
System.out.println(output);
223-
System.out.println("=== End Output ===\n");
152+
String expected = """
153+
# HELP api_responses_total API responses
154+
# TYPE api_responses_total counter
155+
api_responses_total{error="NOT_FOUND",outcome="FAILURE",uri="/world"} 5.0
156+
api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0
157+
api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0
158+
api_responses_total{outcome="SUCCESS",uri="/world"} 200.0
159+
""";
160+
assertThat(output).isEqualTo(expected);
224161

225-
long metricLines =
226-
output.lines().filter(line -> line.startsWith("api_responses_total{")).count();
227-
assertThat(metricLines).isEqualTo(4);
228-
229-
assertThat(output).contains(" 100");
230-
assertThat(output).contains(" 200");
231-
assertThat(output).contains(" 10");
232-
assertThat(output).contains(" 5");
233162
}
234163

235164
@Test
236-
void testBackwardCompatibility_strictModeWorksAsExpected() throws IOException {
237-
PrometheusRegistry registry = new PrometheusRegistry(); // Strict mode
238-
239-
registry.register(
240-
new Collector() {
241-
@Override
242-
public MetricSnapshot collect() {
243-
return CounterSnapshot.builder()
244-
.name("requests")
245-
.help("Request counter")
246-
.dataPoint(
247-
CounterSnapshot.CounterDataPointSnapshot.builder()
248-
.labels(Labels.of("method", "GET"))
249-
.value(100)
250-
.build())
251-
.build();
252-
}
253-
254-
@Override
255-
public String getPrometheusName() {
256-
return "requests_total";
257-
}
258-
});
259-
260-
registry.register(
261-
new Collector() {
262-
@Override
263-
public MetricSnapshot collect() {
264-
return GaugeSnapshot.builder()
265-
.name("active_requests")
266-
.help("Active requests gauge")
267-
.dataPoint(
268-
GaugeSnapshot.GaugeDataPointSnapshot.builder()
269-
.labels(Labels.of("method", "POST"))
270-
.value(50)
271-
.build())
272-
.build();
273-
}
274-
275-
@Override
276-
public String getPrometheusName() {
277-
return "active_requests";
278-
}
279-
});
165+
void testOpenMetricsFormat_withDuplicateNames() throws IOException {
166+
PrometheusRegistry registry = getPrometheusRegistry();
280167

281168
MetricSnapshots snapshots = registry.scrape();
282169
ByteArrayOutputStream out = new ByteArrayOutputStream();
283-
PrometheusTextFormatWriter writer = PrometheusTextFormatWriter.create();
170+
OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, false);
284171
writer.write(out, snapshots);
285172
String output = out.toString(UTF_8);
286173

287-
System.out.println("=== Backward Compatibility (Strict Mode) Output ===");
288-
System.out.println(output);
289-
System.out.println("=== End Output ===\n");
290-
291-
// Verify both metrics appear with unique names
292-
assertThat(output).contains("# TYPE requests_total counter");
293-
assertThat(output).contains("# TYPE active_requests gauge");
294-
assertThat(output).contains("requests_total{");
295-
assertThat(output).contains("active_requests{");
174+
String expected = """
175+
# TYPE api_responses counter
176+
# HELP api_responses API responses
177+
api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0
178+
api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0
179+
# EOF
180+
""";
181+
assertThat(output).isEqualTo(expected);
296182
}
297183

298184
@Test
299-
void testOpenMetricsFormat_withDuplicateNames() throws IOException {
300-
PrometheusRegistry registry = getPrometheusRegistry();
301-
302-
MetricSnapshots snapshots = registry.scrape();
303-
ByteArrayOutputStream out = new ByteArrayOutputStream();
304-
OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, false);
305-
writer.write(out, snapshots);
306-
String output = out.toString(UTF_8);
185+
void testDuplicateNames_sameLabels_throwsException() {
186+
PrometheusRegistry registry = new PrometheusRegistry();
307187

308-
System.out.println("=== OpenMetrics Format with Duplicate Names ===");
309-
System.out.println(output);
310-
System.out.println("=== End Output ===\n");
188+
registry.register(
189+
new Collector() {
190+
@Override
191+
public MetricSnapshot collect() {
192+
return CounterSnapshot.builder()
193+
.name("api_responses")
194+
.help("API responses")
195+
.dataPoint(
196+
CounterSnapshot.CounterDataPointSnapshot.builder()
197+
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
198+
.value(100)
199+
.build())
200+
.build();
201+
}
202+
203+
@Override
204+
public String getPrometheusName() {
205+
return "api_responses_total";
206+
}
207+
});
311208

312-
assertThat(output).contains("# TYPE api_responses counter");
313-
assertThat(output).contains("api_responses_total{");
314-
assertThat(output).contains("outcome=\"SUCCESS\"");
315-
assertThat(output).contains("outcome=\"FAILURE\"");
316-
assertThat(output).contains("# EOF");
209+
registry.register(
210+
new Collector() {
211+
@Override
212+
public MetricSnapshot collect() {
213+
return CounterSnapshot.builder()
214+
.name("api_responses")
215+
.help("API responses")
216+
.dataPoint(
217+
CounterSnapshot.CounterDataPointSnapshot.builder()
218+
.labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
219+
.value(50)
220+
.build())
221+
.build();
222+
}
223+
224+
@Override
225+
public String getPrometheusName() {
226+
return "api_responses_total";
227+
}
228+
});
317229

318-
long metricLines =
319-
output.lines().filter(line -> line.startsWith("api_responses_total{")).count();
320-
assertThat(metricLines).isEqualTo(2);
230+
// Scraping should throw exception due to duplicate time series (same name + same labels)
231+
assertThatThrownBy(registry::scrape)
232+
.isInstanceOf(IllegalArgumentException.class)
233+
.hasMessageContaining("Duplicate labels detected")
234+
.hasMessageContaining("api_responses");
321235
}
322236
}

0 commit comments

Comments
 (0)