Skip to content

Commit e65218b

Browse files
committed
Add filter for in/excluding metrics by name prefix
Signed-off-by: Fabian Stäber <[email protected]>
1 parent 17c98eb commit e65218b

File tree

8 files changed

+652
-189
lines changed

8 files changed

+652
-189
lines changed

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

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.prometheus.client.exemplars.Exemplar;
55

66
import java.util.ArrayList;
7+
import java.util.Collections;
78
import java.util.List;
89
import java.util.regex.Pattern;
910

@@ -17,10 +18,50 @@
1718
* @see <a href="http://prometheus.io/docs/instrumenting/exposition_formats/">Exposition formats</a>.
1819
*/
1920
public abstract class Collector {
21+
2022
/**
21-
* Return all of the metrics of this Collector.
23+
* Return all metrics of this Collector.
2224
*/
2325
public abstract List<MetricFamilySamples> collect();
26+
27+
/**
28+
* Like {@link #collect()}, but the result may exclude {@code MetricFamilySamples} where
29+
* {@code metricNameFilter.test(name)} is {@code false} for all Sample names.
30+
* <p>
31+
* The default implementation first collects all {@code MetricFamilySamples} and then discards the ones
32+
* where {@code metricNameFilter.test(name)} returns {@code false} for all names in
33+
* {@link MetricFamilySamples#getNames()}.
34+
* To improve performance, collector implementations should override this method to prevent
35+
* {@code MetricFamilySamples} from being collected if they will be discarded anyways.
36+
* See {@code ThreadExports} for an example.
37+
* <p>
38+
* Note that the resulting List may contain "false positives", i.e. {@code MetricFamilySamples} where it turns
39+
* out that none of the Sample names returns {@code true} for {@code metricNameFilter.test(name)},
40+
* or "partial matches", i.e. {@code MetricFamilySamples} where some Sample names return {@code true} for
41+
* {@code metricNameFilter.test(name)} but some Sample names return {@code false}.
42+
* This is ok, because before we produce the output format we will call
43+
* {@link MetricFamilySamples#filter(Predicate)} to strip all Samples where {@code metricNameFilter.test(name)}
44+
* returns {@code false}.
45+
*
46+
* @param metricNameFilter may be {@code null}, indicating that all metrics should be collected.
47+
*/
48+
public List<MetricFamilySamples> collect(Predicate<String> metricNameFilter) {
49+
List<MetricFamilySamples> all = collect();
50+
if (metricNameFilter == null) {
51+
return all;
52+
}
53+
List<MetricFamilySamples> remaining = new ArrayList<MetricFamilySamples>(all.size());
54+
for (MetricFamilySamples mfs : all) {
55+
for (String name : mfs.getNames()) {
56+
if (metricNameFilter.test(name)) {
57+
remaining.add(mfs);
58+
break;
59+
}
60+
}
61+
}
62+
return remaining;
63+
}
64+
2465
public enum Type {
2566
UNKNOWN, // This is untyped in Prometheus text format.
2667
COUNTER,
@@ -40,7 +81,11 @@ static public class MetricFamilySamples {
4081
public final String unit;
4182
public final Type type;
4283
public final String help;
43-
public final List<Sample> samples;
84+
public final List<Sample> samples; // this list can be modified as samples are added/removed.
85+
86+
public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) {
87+
this(name, "", type, help, samples);
88+
}
4489

4590
public MetricFamilySamples(String name, String unit, Type type, String help, List<Sample> samples) {
4691
if (!unit.isEmpty() && !name.endsWith("_" + unit)) {
@@ -72,10 +117,84 @@ public MetricFamilySamples(String name, String unit, Type type, String help, Lis
72117
this.samples = mungedSamples;
73118
}
74119

75-
public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) {
76-
this(name, "", type, help, samples);
120+
/**
121+
*
122+
* @param metricNameFilter may be {@link null} indicating that the result contains the complete list of samples.
123+
* @return A new MetricFamilySamples containing only the Samples matching the metricNameFilter,
124+
* or {@code null} if no Sample matches.
125+
*/
126+
public MetricFamilySamples filter(Predicate<String> metricNameFilter) {
127+
if (metricNameFilter == null) {
128+
return this;
129+
}
130+
List<Sample> remainingSamples = new ArrayList<Sample>(samples.size());
131+
for (Sample sample : samples) {
132+
if (metricNameFilter.test(sample.name)) {
133+
remainingSamples.add(sample);
134+
}
135+
}
136+
if (remainingSamples.isEmpty()) {
137+
return null;
138+
}
139+
return new MetricFamilySamples(name, unit, type, help, remainingSamples);
77140
}
78141

142+
/**
143+
* List of names that are reserved for Samples in these MetricsFamilySamples.
144+
* <p>
145+
* This is used in two places:
146+
* <ol>
147+
* <li>To check potential name collisions in {@link CollectorRegistry#register(Collector)}.
148+
* <li>To check if a collector may contain metrics matching the metric name filter
149+
* in {@link Collector#collect(Predicate)}.
150+
* </ol>
151+
* Note that {@code getNames()} always includes the name without suffix, even though some
152+
* metrics types (like Counter) will not have a Sample with that name.
153+
* The reason is that the name without suffix is used in the metadata comments ({@code # TYPE}, {@code # UNIT},
154+
* {@code # HELP}), and as this name <a href="https://github.com/prometheus/common/issues/319">must be unique</a>
155+
* we include the name without suffix here as well.
156+
*/
157+
public String[] getNames() {
158+
switch (type) {
159+
case COUNTER:
160+
return new String[]{
161+
name + "_total",
162+
name + "_created",
163+
name
164+
};
165+
case SUMMARY:
166+
return new String[]{
167+
name + "_count",
168+
name + "_sum",
169+
name + "_created",
170+
name
171+
};
172+
case HISTOGRAM:
173+
return new String[]{
174+
name + "_count",
175+
name + "_sum",
176+
name + "_bucket",
177+
name + "_created",
178+
name
179+
};
180+
case GAUGE_HISTOGRAM:
181+
return new String[]{
182+
name + "_gcount",
183+
name + "_gsum",
184+
name + "_bucket",
185+
name
186+
};
187+
case INFO:
188+
return new String[]{
189+
name + "_info",
190+
name
191+
};
192+
default:
193+
return new String[]{name};
194+
}
195+
}
196+
197+
79198
@Override
80199
public boolean equals(Object obj) {
81200
if (!(obj instanceof MetricFamilySamples)) {
@@ -204,7 +323,7 @@ public interface Describable {
204323
* Usually custom collectors do not have to implement Describable. If
205324
* Describable is not implemented and the CollectorRegistry was created
206325
* with auto describe enabled (which is the case for the default registry)
207-
* then {@link collect} will be called at registration time instead of
326+
* then {@link #collect} will be called at registration time instead of
208327
* describe. If this could cause problems, either implement a proper
209328
* describe, or if that's not practical have describe return an empty
210329
* list.

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

Lines changed: 27 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -117,38 +117,7 @@ private List<String> collectorNames(Collector m) {
117117

118118
List<String> names = new ArrayList<String>();
119119
for (Collector.MetricFamilySamples family : mfs) {
120-
switch (family.type) {
121-
case COUNTER:
122-
names.add(family.name + "_total");
123-
names.add(family.name + "_created");
124-
names.add(family.name);
125-
break;
126-
case SUMMARY:
127-
names.add(family.name + "_count");
128-
names.add(family.name + "_sum");
129-
names.add(family.name + "_created");
130-
names.add(family.name);
131-
break;
132-
case HISTOGRAM:
133-
names.add(family.name + "_count");
134-
names.add(family.name + "_sum");
135-
names.add(family.name + "_bucket");
136-
names.add(family.name + "_created");
137-
names.add(family.name);
138-
break;
139-
case GAUGE_HISTOGRAM:
140-
names.add(family.name + "_gcount");
141-
names.add(family.name + "_gsum");
142-
names.add(family.name + "_bucket");
143-
names.add(family.name);
144-
break;
145-
case INFO:
146-
names.add(family.name + "_info");
147-
names.add(family.name);
148-
break;
149-
default:
150-
names.add(family.name);
151-
}
120+
names.addAll(Arrays.asList(family.getNames()));
152121
}
153122
return names;
154123
}
@@ -168,83 +137,73 @@ public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
168137
* histogram, you must include the '_count', '_sum' and '_bucket' names.
169138
*/
170139
public Enumeration<Collector.MetricFamilySamples> filteredMetricFamilySamples(Set<String> includedNames) {
171-
return new MetricFamilySamplesEnumeration(includedNames);
140+
return new MetricFamilySamplesEnumeration(new MetricNameFilter.Builder().includeNames(includedNames).build());
141+
}
142+
143+
/**
144+
* Enumeration of metrics where {@code metricNameFilter.test(name)} returns {@code true} for any {@code name} in
145+
* {@link Collector.MetricFamilySamples#getNames()}.
146+
* @param metricNameFilter may be {@code null}, indicating that the enumeration should contain all metrics.
147+
*/
148+
public Enumeration<Collector.MetricFamilySamples> filteredMetricFamilySamples(Predicate<String> metricNameFilter) {
149+
return new MetricFamilySamplesEnumeration(metricNameFilter);
172150
}
173151

174152
class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
175153

176154
private final Iterator<Collector> collectorIter;
177155
private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
178156
private Collector.MetricFamilySamples next;
179-
private Set<String> includedNames;
157+
private final Predicate<String> metricNameFilter;
180158

181-
MetricFamilySamplesEnumeration(Set<String> includedNames) {
182-
this.includedNames = includedNames;
183-
collectorIter = includedCollectorIterator(includedNames);
159+
MetricFamilySamplesEnumeration(Predicate<String> metricNameFilter) {
160+
this.metricNameFilter = metricNameFilter;
161+
this.collectorIter = filteredCollectorIterator();
184162
findNextElement();
185163
}
186164

187-
private Iterator<Collector> includedCollectorIterator(Set<String> includedNames) {
188-
if (includedNames.isEmpty()) {
165+
private Iterator<Collector> filteredCollectorIterator() {
166+
if (metricNameFilter == null) {
189167
return collectors().iterator();
190168
} else {
191169
HashSet<Collector> collectors = new HashSet<Collector>();
192170
synchronized (namesCollectorsLock) {
193171
for (Map.Entry<String, Collector> entry : namesToCollectors.entrySet()) {
194-
if (includedNames.contains(entry.getKey())) {
172+
// Note that namesToCollectors contains keys for all combinations of suffixes (_total, _info, etc.).
173+
if (metricNameFilter.test(entry.getKey())) {
195174
collectors.add(entry.getValue());
196175
}
197176
}
198177
}
199-
200178
return collectors.iterator();
201179
}
202180
}
203181

204182
MetricFamilySamplesEnumeration() {
205-
this(Collections.<String>emptySet());
183+
this(null);
206184
}
207185

208186
private void findNextElement() {
209187
next = null;
210188

211189
while (metricFamilySamples != null && metricFamilySamples.hasNext()) {
212-
next = filter(metricFamilySamples.next());
190+
next = metricFamilySamples.next().filter(metricNameFilter);
213191
if (next != null) {
214192
return;
215193
}
216194
}
217195

218-
if (next == null) {
219-
while (collectorIter.hasNext()) {
220-
metricFamilySamples = collectorIter.next().collect().iterator();
221-
while (metricFamilySamples.hasNext()) {
222-
next = filter(metricFamilySamples.next());
223-
if (next != null) {
224-
return;
225-
}
196+
while (collectorIter.hasNext()) {
197+
metricFamilySamples = collectorIter.next().collect(metricNameFilter).iterator();
198+
while (metricFamilySamples.hasNext()) {
199+
next = metricFamilySamples.next().filter(metricNameFilter);
200+
if (next != null) {
201+
return;
226202
}
227203
}
228204
}
229205
}
230206

231-
private Collector.MetricFamilySamples filter(Collector.MetricFamilySamples next) {
232-
if (includedNames.isEmpty()) {
233-
return next;
234-
} else {
235-
Iterator<Collector.MetricFamilySamples.Sample> it = next.samples.iterator();
236-
while (it.hasNext()) {
237-
if (!includedNames.contains(it.next().name)) {
238-
it.remove();
239-
}
240-
}
241-
if (next.samples.size() == 0) {
242-
return null;
243-
}
244-
return next;
245-
}
246-
}
247-
248207
public Collector.MetricFamilySamples nextElement() {
249208
Collector.MetricFamilySamples current = next;
250209
if (current == null) {

0 commit comments

Comments
 (0)