Skip to content

Commit 500ab03

Browse files
authored
Add describe and autodescribe to detect dupes. (prometheus#162)
This works largely the same as this feature in the Python client. This works by adding an optional Describable interface to collectors, which returns data in the same format as collect (though hopefully without the samples). If implemented it is called at registration time. If describe is not present and auto describe is set on the registry, then collect is called instead. This is enabled by default on the default registry, but disabled elsewhere. Put in empty describes on some custom collectors that deal with arbitrary data.
1 parent 575488d commit 500ab03

9 files changed

Lines changed: 194 additions & 17 deletions

File tree

simpleclient/src/main/java/io/prometheus/client/Collector.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ public <T extends Collector> T register(CollectorRegistry registry) {
129129
return (T)this;
130130
}
131131

132+
public interface Describable {
133+
/**
134+
* Provide a list of metric families this Collector is expected to return.
135+
*
136+
* These should exclude the samples. This is used by the registry to
137+
* detect collisions and duplicate registrations.
138+
*
139+
* Usually custom collectors do not have to implement Describable. If
140+
* Describable is not implemented and the CollectorRegistry was created
141+
* with auto desribe enabled (which is the case for the default registry)
142+
* then {@link collect} will be called at registration time instead of
143+
* describe. If this could cause problems, either implement a proper
144+
* describe, or if that's not practical have describe return an empty
145+
* list.
146+
*/
147+
public List<MetricFamilySamples> describe();
148+
}
149+
150+
132151
/* Various utility functions for implementing Collectors. */
133152

134153
/**

simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package io.prometheus.client;
22

3+
import java.util.ArrayList;
34
import java.util.Arrays;
45
import java.util.Collections;
5-
import java.util.concurrent.ConcurrentHashMap;
66
import java.util.Enumeration;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
79
import java.util.Iterator;
10+
import java.util.Map;
811
import java.util.NoSuchElementException;
912
import java.util.Set;
13+
import java.util.List;
1014

1115
/**
1216
* A registry of Collectors.
@@ -21,31 +25,98 @@ public class CollectorRegistry {
2125
/**
2226
* The default registry.
2327
*/
24-
public static final CollectorRegistry defaultRegistry = new CollectorRegistry();
28+
public static final CollectorRegistry defaultRegistry = new CollectorRegistry(true);
2529

26-
private final Set<Collector> collectors =
27-
Collections.newSetFromMap(new ConcurrentHashMap<Collector, Boolean>());
30+
31+
private final Map<Collector, List<String>> collectorsToNames = new HashMap<Collector, List<String>>();
32+
private final Map<String, Collector> namesToCollectors = new HashMap<String, Collector>();
33+
34+
private final boolean autoDescribe;
35+
36+
public CollectorRegistry(){
37+
this(false);
38+
}
39+
40+
public CollectorRegistry(boolean autoDescribe) {
41+
this.autoDescribe = autoDescribe;
42+
}
2843

2944
/**
3045
* Register a Collector.
3146
* <p>
3247
* A collector can be registered to multiple CollectorRegistries.
3348
*/
3449
public void register(Collector m) {
35-
collectors.add(m);
50+
List<String> names = collectorNames(m);
51+
synchronized (collectorsToNames) {
52+
for (String name : names) {
53+
if(namesToCollectors.containsKey(name)) {
54+
throw new IllegalArgumentException("Collector already registered that provides name: " + name);
55+
}
56+
}
57+
for (String name : names) {
58+
namesToCollectors.put(name, m);
59+
}
60+
collectorsToNames.put(m, names);
61+
}
3662
}
37-
63+
3864
/**
3965
* Unregister a Collector.
4066
*/
4167
public void unregister(Collector m) {
42-
collectors.remove(m);
68+
synchronized (collectorsToNames) {
69+
for (String name : collectorsToNames.get(m)) {
70+
namesToCollectors.remove(name);
71+
}
72+
collectorsToNames.remove(m);
73+
}
4374
}
4475
/**
4576
* Unregister all Collectors.
4677
*/
4778
public void clear() {
48-
collectors.clear();
79+
synchronized (collectorsToNames) {
80+
collectorsToNames.clear();
81+
namesToCollectors.clear();
82+
}
83+
}
84+
85+
/**
86+
* A snapshot of the current collectors.
87+
*/
88+
private Set<Collector> collectors() {
89+
synchronized (collectorsToNames) {
90+
return new HashSet(collectorsToNames.keySet());
91+
}
92+
}
93+
94+
private List<String> collectorNames(Collector m) {
95+
List<Collector.MetricFamilySamples> mfs;
96+
if (m instanceof Collector.Describable) {
97+
mfs = ((Collector.Describable)m).describe();
98+
} else if (autoDescribe) {
99+
mfs = m.collect();
100+
} else {
101+
mfs = Collections.emptyList();
102+
}
103+
104+
List<String> names = new ArrayList<String>();
105+
for (Collector.MetricFamilySamples family : mfs) {
106+
switch (family.type) {
107+
case SUMMARY:
108+
names.add(family.name + "_count");
109+
names.add(family.name + "_sum");
110+
names.add(family.name);
111+
case HISTOGRAM:
112+
names.add(family.name + "_count");
113+
names.add(family.name + "_sum");
114+
names.add(family.name + "_bucket");
115+
default:
116+
names.add(family.name);
117+
}
118+
}
119+
return names;
49120
}
50121

51122
/**
@@ -56,14 +127,14 @@ public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
56127
}
57128
class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
58129

59-
private final Iterator<Collector> collectorIter = collectors.iterator();
130+
private final Iterator<Collector> collectorIter = collectors().iterator();
60131
private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
61132
private Collector.MetricFamilySamples next;
62133

63134
MetricFamilySamplesEnumeration() {
64135
findNextElement();
65136
}
66-
137+
67138
private void findNextElement() {
68139
if (metricFamilySamples != null && metricFamilySamples.hasNext()) {
69140
next = metricFamilySamples.next();
@@ -87,7 +158,7 @@ public Collector.MetricFamilySamples nextElement() {
87158
findNextElement();
88159
return current;
89160
}
90-
161+
91162
public boolean hasMoreElements() {
92163
return next != null;
93164
}

simpleclient/src/main/java/io/prometheus/client/Counter.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
* These can be aggregated and processed together much more easily in the Promtheus
6565
* server than individual metrics for each labelset.
6666
*/
67-
public class Counter extends SimpleCollector<Counter.Child> {
67+
public class Counter extends SimpleCollector<Counter.Child> implements Collector.Describable {
6868

6969
Counter(Builder b) {
7070
super(b);
@@ -148,4 +148,10 @@ public List<MetricFamilySamples> collect() {
148148
mfsList.add(mfs);
149149
return mfsList;
150150
}
151+
152+
public List<MetricFamilySamples> describe() {
153+
List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
154+
mfsList.add(new CounterMetricFamily(fullname, help, labelNames));
155+
return mfsList;
156+
}
151157
}

simpleclient/src/main/java/io/prometheus/client/Gauge.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
* These can be aggregated and processed together much more easily in the Prometheus
6363
* server than individual metrics for each labelset.
6464
*/
65-
public class Gauge extends SimpleCollector<Gauge.Child> {
65+
public class Gauge extends SimpleCollector<Gauge.Child> implements Collector.Describable {
6666

6767
Gauge(Builder b) {
6868
super(b);
@@ -254,6 +254,12 @@ public List<MetricFamilySamples> collect() {
254254
return mfsList;
255255
}
256256

257+
public List<MetricFamilySamples> describe() {
258+
List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
259+
mfsList.add(new GaugeMetricFamily(fullname, help, labelNames));
260+
return mfsList;
261+
}
262+
257263
static class TimeProvider {
258264
long currentTimeMillis() {
259265
return System.currentTimeMillis();

simpleclient/src/main/java/io/prometheus/client/Histogram.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
* {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets}
5353
* offer easy ways to set common bucket patterns.
5454
*/
55-
public class Histogram extends SimpleCollector<Histogram.Child> {
55+
public class Histogram extends SimpleCollector<Histogram.Child> implements Collector.Describable {
5656
private final double[] buckets;
5757

5858
Histogram(Builder b) {
@@ -269,6 +269,12 @@ public List<MetricFamilySamples> collect() {
269269
return mfsList;
270270
}
271271

272+
public List<MetricFamilySamples> describe() {
273+
List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
274+
mfsList.add(new MetricFamilySamples(fullname, Type.HISTOGRAM, help, new ArrayList<MetricFamilySamples.Sample>()));
275+
return mfsList;
276+
}
277+
272278
double[] getBuckets() {
273279
return buckets;
274280
}

simpleclient/src/main/java/io/prometheus/client/Summary.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
*
7171
* See https://prometheus.io/docs/practices/histograms/ for more info on quantiles.
7272
*/
73-
public class Summary extends SimpleCollector<Summary.Child> {
73+
public class Summary extends SimpleCollector<Summary.Child> implements Counter.Describable {
7474

7575
final List<Quantile> quantiles; // Can be empty, but can never be null.
7676
final long maxAgeSeconds;
@@ -284,6 +284,12 @@ public List<MetricFamilySamples> collect() {
284284
return mfsList;
285285
}
286286

287+
public List<MetricFamilySamples> describe() {
288+
List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>();
289+
mfsList.add(new SummaryMetricFamily(fullname, help, labelNames));
290+
return mfsList;
291+
}
292+
287293
static class TimeProvider {
288294
long nanoTime() {
289295
return System.nanoTime();

simpleclient/src/test/java/io/prometheus/client/CollectorRegistryTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,60 @@ public void testRegistryWithEmptyCollectorHasNoMoreElements() {
8181
registry.register(new EmptyCollector());
8282
assertFalse(registry.metricFamilySamples().hasMoreElements());
8383
}
84+
85+
@Test(expected=IllegalArgumentException.class)
86+
public void testCounterAndGaugeWithSameNameThrows() {
87+
Gauge.build().name("g").help("h").register(registry);
88+
Counter.build().name("g").help("h").register(registry);
89+
}
90+
91+
@Test(expected=IllegalArgumentException.class)
92+
public void testCounterAndSummaryWithSameNameThrows() {
93+
Counter.build().name("s").help("h").register(registry);
94+
Summary.build().name("s").help("h").register(registry);
95+
}
96+
97+
@Test(expected=IllegalArgumentException.class)
98+
public void testCounterSumAndSummaryWithSameNameThrows() {
99+
Counter.build().name("s_sum").help("h").register(registry);
100+
Summary.build().name("s").help("h").register(registry);
101+
}
102+
103+
@Test(expected=IllegalArgumentException.class)
104+
public void testHistogramAndSummaryWithSameNameThrows() {
105+
Histogram.build().name("s").help("h").register(registry);
106+
Summary.build().name("s").help("h").register(registry);
107+
}
108+
109+
@Test
110+
public void testCanUnAndReregister() {
111+
Histogram h = Histogram.build().name("s").help("h").create();
112+
registry.register(h);
113+
registry.unregister(h);
114+
registry.register(h);
115+
}
116+
117+
class MyCollector extends Collector {
118+
public List<MetricFamilySamples> collect() {
119+
List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
120+
mfs.add(new GaugeMetricFamily("g", "help", 42));
121+
return mfs;
122+
}
123+
}
124+
125+
@Test
126+
public void testAutoDescribeDisabledByDefault() {
127+
CollectorRegistry r = new CollectorRegistry();
128+
new MyCollector().register(r);
129+
// This doesn't throw.
130+
new MyCollector().register(r);
131+
}
132+
133+
@Test(expected=IllegalArgumentException.class)
134+
public void testAutoDescribeThrowsOnReregisteringCustomCollector() {
135+
CollectorRegistry r = new CollectorRegistry(true);
136+
new MyCollector().register(r);
137+
new MyCollector().register(r);
138+
}
84139

85140
}

simpleclient_dropwizard/src/main/java/io/prometheus/client/dropwizard/DropwizardExports.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
/**
1414
* Collect Dropwizard metrics from a MetricRegistry.
1515
*/
16-
public class DropwizardExports extends io.prometheus.client.Collector {
16+
public class DropwizardExports extends io.prometheus.client.Collector implements io.prometheus.client.Collector.Describable {
1717
private MetricRegistry registry;
1818
private static final Logger LOGGER = Logger.getLogger(DropwizardExports.class.getName());
1919

@@ -146,4 +146,8 @@ public List<MetricFamilySamples> collect() {
146146
}
147147
return mfSamples;
148148
}
149+
150+
public List<MetricFamilySamples> describe() {
151+
return new ArrayList<MetricFamilySamples>();
152+
}
149153
}

simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/SpringBootMetricsCollector.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* </code></pre>
2424
*/
2525
@Component
26-
public class SpringBootMetricsCollector extends Collector {
26+
public class SpringBootMetricsCollector extends Collector implements Collector.Describable {
2727
private final Collection<PublicMetrics> publicMetrics;
2828

2929
@Autowired
@@ -46,4 +46,8 @@ public List<MetricFamilySamples> collect() {
4646
}
4747
return samples;
4848
}
49+
50+
public List<MetricFamilySamples> describe() {
51+
return new ArrayList<MetricFamilySamples>();
52+
}
4953
}

0 commit comments

Comments
 (0)