Skip to content

Commit 9bf69c8

Browse files
committed
be consistent about help/unit validation, ensure deregister labels
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
1 parent 28a8514 commit 9bf69c8

File tree

2 files changed

+178
-182
lines changed

2 files changed

+178
-182
lines changed

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java

Lines changed: 60 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.prometheus.metrics.model.registry;
22

3+
import static java.util.Collections.emptySet;
4+
35
import io.prometheus.metrics.model.snapshots.MetricMetadata;
46
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
57
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
@@ -53,8 +55,7 @@ private static class MultiCollectorRegistration {
5355

5456
/**
5557
* Tracks registration information for each metric name to enable validation of type consistency,
56-
* label schema uniqueness, and help/unit consistency. Stores metadata to enable O(1)
57-
* unregistration without iterating through all collectors.
58+
* label schema uniqueness, and help/unit consistency.
5859
*/
5960
private static class RegistrationInfo {
6061
private final MetricType type;
@@ -80,31 +81,25 @@ static RegistrationInfo of(
8081
@Nullable Unit unit) {
8182
Set<Set<String>> labelSchemas = ConcurrentHashMap.newKeySet();
8283
Set<String> normalized =
83-
(labelNames == null || labelNames.isEmpty()) ? Collections.emptySet() : labelNames;
84+
(labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames;
8485
labelSchemas.add(normalized);
8586
return new RegistrationInfo(type, labelSchemas, help, unit);
8687
}
8788

8889
/**
89-
* Validates that the given help and unit are consistent with this registration. Throws if
90-
* non-null values conflict. When stored help/unit is null and the new value is non-null,
91-
* captures the first non-null so subsequent registrations are validated consistently.
90+
* Validates that the given help and unit are exactly equal to this registration. Throws if
91+
* values differ, including when one is null and the other is non-null. This ensures consistent
92+
* metadata across all collectors sharing the same metric name.
9293
*/
9394
void validateMetadata(@Nullable String newHelp, @Nullable Unit newUnit) {
94-
if (help != null && newHelp != null && !Objects.equals(help, newHelp)) {
95+
if (!Objects.equals(help, newHelp)) {
9596
throw new IllegalArgumentException(
9697
"Conflicting help strings. Existing: \"" + help + "\", new: \"" + newHelp + "\"");
9798
}
98-
if (unit != null && newUnit != null && !Objects.equals(unit, newUnit)) {
99+
if (!Objects.equals(unit, newUnit)) {
99100
throw new IllegalArgumentException(
100101
"Conflicting unit. Existing: " + unit + ", new: " + newUnit);
101102
}
102-
if (help == null && newHelp != null) {
103-
this.help = newHelp;
104-
}
105-
if (unit == null && newUnit != null) {
106-
this.unit = newUnit;
107-
}
108103
}
109104

110105
/**
@@ -115,7 +110,7 @@ void validateMetadata(@Nullable String newHelp, @Nullable Unit newUnit) {
115110
*/
116111
boolean addLabelSet(@Nullable Set<String> labelNames) {
117112
Set<String> normalized =
118-
(labelNames == null || labelNames.isEmpty()) ? Collections.emptySet() : labelNames;
113+
(labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames;
119114
return labelSchemas.add(normalized);
120115
}
121116

@@ -126,7 +121,7 @@ boolean addLabelSet(@Nullable Set<String> labelNames) {
126121
*/
127122
void removeLabelSet(@Nullable Set<String> labelNames) {
128123
Set<String> normalized =
129-
(labelNames == null || labelNames.isEmpty()) ? Collections.emptySet() : labelNames;
124+
(labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames;
130125
labelSchemas.remove(normalized);
131126
}
132127

@@ -147,11 +142,56 @@ MetricType getType() {
147142
*/
148143
private static Set<String> immutableLabelNames(@Nullable Set<String> labelNames) {
149144
if (labelNames == null || labelNames.isEmpty()) {
150-
return Collections.emptySet();
145+
return emptySet();
151146
}
152147
return Collections.unmodifiableSet(new HashSet<>(labelNames));
153148
}
154149

150+
/**
151+
* Validates the registration of a metric with the given parameters. Ensures type consistency,
152+
* label schema uniqueness, and help/unit consistency.
153+
*/
154+
private void validateRegistration(
155+
String prometheusName,
156+
MetricType metricType,
157+
Set<String> normalizedLabels,
158+
@Nullable String help,
159+
@Nullable Unit unit) {
160+
final MetricType type = metricType;
161+
final Set<String> names = normalizedLabels;
162+
final String helpForValidation = help;
163+
final Unit unitForValidation = unit;
164+
registered.compute(
165+
prometheusName,
166+
(n, existingInfo) -> {
167+
if (existingInfo == null) {
168+
return RegistrationInfo.of(type, names, helpForValidation, unitForValidation);
169+
} else {
170+
if (existingInfo.getType() != type) {
171+
throw new IllegalArgumentException(
172+
prometheusName
173+
+ ": Conflicting metric types. Existing: "
174+
+ existingInfo.getType()
175+
+ ", new: "
176+
+ type);
177+
}
178+
// Check label set first; only mutate help/unit after validation passes.
179+
if (!existingInfo.addLabelSet(names)) {
180+
throw new IllegalArgumentException(
181+
prometheusName + ": duplicate metric name with identical label schema " + names);
182+
}
183+
// Roll back label schema if metadata validation fails
184+
try {
185+
existingInfo.validateMetadata(helpForValidation, unitForValidation);
186+
} catch (IllegalArgumentException e) {
187+
existingInfo.removeLabelSet(names);
188+
throw e;
189+
}
190+
return existingInfo;
191+
}
192+
});
193+
}
194+
155195
public void register(Collector collector) {
156196
if (!collectors.add(collector)) {
157197
throw new IllegalArgumentException("Collector instance is already registered");
@@ -167,35 +207,7 @@ public void register(Collector collector) {
167207
// Only perform validation if collector provides sufficient metadata.
168208
// Collectors that don't implement getPrometheusName()/getMetricType() will skip validation.
169209
if (prometheusName != null && metricType != null) {
170-
final String name = prometheusName;
171-
final MetricType type = metricType;
172-
final Set<String> names = normalizedLabels;
173-
final String helpForValidation = help;
174-
final Unit unitForValidation = unit;
175-
registered.compute(
176-
prometheusName,
177-
(n, existingInfo) -> {
178-
if (existingInfo == null) {
179-
return RegistrationInfo.of(type, names, helpForValidation, unitForValidation);
180-
} else {
181-
if (existingInfo.getType() != type) {
182-
throw new IllegalArgumentException(
183-
name
184-
+ ": Conflicting metric types. Existing: "
185-
+ existingInfo.getType()
186-
+ ", new: "
187-
+ type);
188-
}
189-
// Check label set first; only mutate help/unit after validation passes.
190-
if (!existingInfo.addLabelSet(names)) {
191-
throw new IllegalArgumentException(
192-
name + ": duplicate metric name with identical label schema " + names);
193-
}
194-
existingInfo.validateMetadata(helpForValidation, unitForValidation);
195-
return existingInfo;
196-
}
197-
});
198-
210+
validateRegistration(prometheusName, metricType, normalizedLabels, help, unit);
199211
collectorMetadata.put(
200212
collector, new CollectorRegistration(prometheusName, normalizedLabels));
201213
}
@@ -225,37 +237,7 @@ public void register(MultiCollector collector) {
225237
Unit unit = metadata != null ? metadata.getUnit() : null;
226238

227239
if (metricType != null) {
228-
final MetricType type = metricType;
229-
final Set<String> labelNamesForValidation = normalizedLabels;
230-
final String helpForValidation = help;
231-
final Unit unitForValidation = unit;
232-
registered.compute(
233-
prometheusName,
234-
(name, existingInfo) -> {
235-
if (existingInfo == null) {
236-
return RegistrationInfo.of(
237-
type, labelNamesForValidation, helpForValidation, unitForValidation);
238-
} else {
239-
if (existingInfo.getType() != type) {
240-
throw new IllegalArgumentException(
241-
prometheusName
242-
+ ": Conflicting metric types. Existing: "
243-
+ existingInfo.getType()
244-
+ ", new: "
245-
+ type);
246-
}
247-
// Check label set first; only mutate help/unit after validation passes.
248-
if (!existingInfo.addLabelSet(labelNamesForValidation)) {
249-
throw new IllegalArgumentException(
250-
prometheusName
251-
+ ": duplicate metric name with identical label schema "
252-
+ labelNamesForValidation);
253-
}
254-
existingInfo.validateMetadata(helpForValidation, unitForValidation);
255-
return existingInfo;
256-
}
257-
});
258-
240+
validateRegistration(prometheusName, metricType, normalizedLabels, help, unit);
259241
registrations.add(new MultiCollectorRegistration(prometheusName, normalizedLabels));
260242
}
261243
}
@@ -300,7 +282,7 @@ private void unregisterLabelSchema(String prometheusName, Set<String> labelNames
300282
(name, info) -> {
301283
info.removeLabelSet(labelNames);
302284
if (info.isEmpty()) {
303-
return null; // remove from registered map
285+
return null;
304286
}
305287
return info;
306288
});

0 commit comments

Comments
 (0)