forked from prometheus/client_java
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCollector.java
398 lines (361 loc) · 13.6 KB
/
Collector.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
package io.prometheus.client;
import io.prometheus.client.exemplars.Exemplar;
import java.util.*;
import java.util.regex.Pattern;
/**
* A collector for a set of metrics.
* <p>
* Normal users should use {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}.
* <p>
* Subclasssing Collector is for advanced uses, such as proxying metrics from another monitoring system.
* It is it the responsibility of subclasses to ensure they produce valid metrics.
* @see <a href="http://prometheus.io/docs/instrumenting/exposition_formats/">Exposition formats</a>.
*/
public abstract class Collector {
/**
* Return all metrics of this Collector.
*/
public abstract List<MetricFamilySamples> collect();
/**
* Like {@link #collect()}, but the result should only contain {@code MetricFamilySamples} where
* {@code sampleNameFilter.test(name)} is {@code true} for at least one Sample name.
* <p>
* The default implementation first collects all {@code MetricFamilySamples} and then discards the ones
* where {@code sampleNameFilter.test(name)} returns {@code false} for all names in
* {@link MetricFamilySamples#getNames()}.
* To improve performance, collector implementations should override this method to prevent
* {@code MetricFamilySamples} from being collected if they will be discarded anyways.
* See {@code ThreadExports} for an example.
* <p>
* Note that the resulting List may contain {@code MetricFamilySamples} where some Sample names return
* {@code true} for {@code sampleNameFilter.test(name)} but some Sample names return {@code false}.
* This is ok, because before we produce the output format we will call
* {@link MetricFamilySamples#filter(Predicate)} to strip all Samples where {@code sampleNameFilter.test(name)}
* returns {@code false}.
*
* @param sampleNameFilter may be {@code null}, indicating that all metrics should be collected.
*/
public List<MetricFamilySamples> collect(Predicate<String> sampleNameFilter) {
List<MetricFamilySamples> all = collect();
if (sampleNameFilter == null) {
return all;
}
List<MetricFamilySamples> remaining = new ArrayList<MetricFamilySamples>(all.size());
for (MetricFamilySamples mfs : all) {
for (String name : mfs.getNames()) {
if (sampleNameFilter.test(name)) {
remaining.add(mfs);
break;
}
}
}
return remaining;
}
public enum Type {
UNKNOWN, // This is untyped in Prometheus text format.
COUNTER,
GAUGE,
STATE_SET,
INFO,
HISTOGRAM,
GAUGE_HISTOGRAM,
SUMMARY,
}
/**
* A metric, and all of its samples.
*/
static public class MetricFamilySamples {
public final String name;
public final String unit;
public final Type type;
public final String help;
public final List<Sample> samples; // this list is modified when samples are added/removed.
public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) {
this(name, "", type, help, samples);
}
public MetricFamilySamples(String name, String unit, Type type, String help, List<Sample> samples) {
if (!unit.isEmpty() && !name.endsWith("_" + unit)) {
throw new IllegalArgumentException("Metric's unit is not the suffix of the metric name: " + name);
}
if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) {
throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name);
}
List<Sample> mungedSamples = samples;
// Deal with _total from pre-OM automatically.
if (type == Type.COUNTER) {
if (name.endsWith("_total")) {
name = name.substring(0, name.length() - 6);
}
String withTotal = name + "_total";
mungedSamples = new ArrayList<Sample>(samples.size());
for (Sample s: samples) {
String n = s.name;
if (name.equals(n)) {
n = withTotal;
}
mungedSamples.add(new Sample(n, s.labelNames, s.labelValues, s.value, s.exemplar, s.timestampMs));
}
}
this.name = name;
this.unit = unit;
this.type = type;
this.help = help;
this.samples = mungedSamples;
}
/**
* @param sampleNameFilter may be {@code null} indicating that the result contains the complete list of samples.
* @return A new MetricFamilySamples containing only the Samples matching the {@code sampleNameFilter},
* or {@code null} if no Sample matches.
*/
public MetricFamilySamples filter(Predicate<String> sampleNameFilter) {
if (sampleNameFilter == null) {
return this;
}
List<Sample> remainingSamples = new ArrayList<Sample>(samples.size());
for (Sample sample : samples) {
if (sampleNameFilter.test(sample.name)) {
remainingSamples.add(sample);
}
}
if (remainingSamples.isEmpty()) {
return null;
}
return new MetricFamilySamples(name, unit, type, help, remainingSamples);
}
/**
* List of names that are reserved for Samples in these MetricsFamilySamples.
* <p>
* This is used in two places:
* <ol>
* <li>To check potential name collisions in {@link CollectorRegistry#register(Collector)}.
* <li>To check if a collector may contain metrics matching the metric name filter
* in {@link Collector#collect(Predicate)}.
* </ol>
* Note that {@code getNames()} always includes the name without suffix, even though some
* metrics types (like Counter) will not have a Sample with that name.
* The reason is that the name without suffix is used in the metadata comments ({@code # TYPE}, {@code # UNIT},
* {@code # HELP}), and as this name <a href="https://github.com/prometheus/common/issues/319">must be unique</a>
* we include the name without suffix here as well.
*/
public String[] getNames() {
switch (type) {
case COUNTER:
return new String[]{
name + "_total",
name + "_created",
name
};
case SUMMARY:
return new String[]{
name + "_count",
name + "_sum",
name + "_created",
name
};
case HISTOGRAM:
return new String[]{
name + "_count",
name + "_sum",
name + "_bucket",
name + "_created",
name
};
case GAUGE_HISTOGRAM:
return new String[]{
name + "_gcount",
name + "_gsum",
name + "_bucket",
name
};
case INFO:
return new String[]{
name + "_info",
name
};
default:
return new String[]{name};
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MetricFamilySamples)) {
return false;
}
MetricFamilySamples other = (MetricFamilySamples) obj;
return other.name.equals(name)
&& other.unit.equals(unit)
&& other.type.equals(type)
&& other.help.equals(help)
&& other.samples.equals(samples);
}
@Override
public int hashCode() {
int hash = 1;
hash = 37 * hash + name.hashCode();
hash = 37 * hash + unit.hashCode();
hash = 37 * hash + type.hashCode();
hash = 37 * hash + help.hashCode();
hash = 37 * hash + samples.hashCode();
return hash;
}
@Override
public String toString() {
return "Name: " + name + " Unit:" + unit + " Type: " + type + " Help: " + help +
" Samples: " + samples;
}
/**
* A single Sample, with a unique name and set of labels.
*/
public static class Sample {
public final String name;
public final List<String> labelNames;
public final List<String> labelValues; // Must have same length as labelNames.
public final double value;
public final Exemplar exemplar;
public final Long timestampMs; // It's an epoch format with milliseconds value included (this field is subject to change).
public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar, Long timestampMs) {
this.name = name;
this.labelNames = labelNames;
this.labelValues = labelValues;
this.value = value;
this.exemplar = exemplar;
this.timestampMs = timestampMs;
}
public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Long timestampMs) {
this(name, labelNames, labelValues, value, null, timestampMs);
}
public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar) {
this(name, labelNames, labelValues, value, exemplar, null);
}
public Sample(String name, List<String> labelNames, List<String> labelValues, double value) {
this(name, labelNames, labelValues, value, null, null);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Sample)) {
return false;
}
Sample other = (Sample) obj;
return other.name.equals(name) &&
other.labelNames.equals(labelNames) &&
other.labelValues.equals(labelValues) &&
other.value == value &&
(exemplar == null && other.exemplar == null || other.exemplar != null && other.exemplar.equals(exemplar)) &&
(timestampMs == null && other.timestampMs == null || other.timestampMs != null && other.timestampMs.equals(timestampMs));
}
@Override
public int hashCode() {
int hash = 1;
hash = 37 * hash + name.hashCode();
hash = 37 * hash + labelNames.hashCode();
hash = 37 * hash + labelValues.hashCode();
long d = Double.doubleToLongBits(value);
hash = 37 * hash + (int)(d ^ (d >>> 32));
if (timestampMs != null) {
hash = 37 * hash + timestampMs.hashCode();
}
if (exemplar != null) {
hash = 37 * exemplar.hashCode();
}
return hash;
}
@Override
public String toString() {
return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues +
" Value: " + value + " TimestampMs: " + timestampMs;
}
}
}
/**
* Register the Collector with the default registry.
*/
public <T extends Collector> T register() {
return register(CollectorRegistry.defaultRegistry);
}
/**
* Register the Collector with the given registry.
*/
public <T extends Collector> T register(CollectorRegistry registry) {
registry.register(this);
return (T)this;
}
public interface Describable {
/**
* Provide a list of metric families this Collector is expected to return.
*
* These should exclude the samples. This is used by the registry to
* detect collisions and duplicate registrations.
*
* Usually custom collectors do not have to implement Describable. If
* Describable is not implemented and the CollectorRegistry was created
* with auto describe enabled (which is the case for the default registry)
* then {@link #collect} will be called at registration time instead of
* describe. If this could cause problems, either implement a proper
* describe, or if that's not practical have describe return an empty
* list.
*/
List<MetricFamilySamples> describe();
}
/* Various utility functions for implementing Collectors. */
/**
* Number of nanoseconds in a second.
*/
public static final double NANOSECONDS_PER_SECOND = 1E9;
/**
* Number of milliseconds in a second.
*/
public static final double MILLISECONDS_PER_SECOND = 1E3;
private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*");
private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*");
/**
* Throw an exception if the metric name is invalid.
*/
protected static void checkMetricName(String name) {
if (!METRIC_NAME_RE.matcher(name).matches()) {
throw new IllegalArgumentException("Invalid metric name: " + name);
}
}
/**
* Sanitize metric name
*/
public static String sanitizeMetricName(String metricName) {
int length = metricName.length();
char[] sanitized = new char[length];
for(int i = 0; i < length; i++) {
char ch = metricName.charAt(i);
if(ch == ':' ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(i > 0 && ch >= '0' && ch <= '9')) {
sanitized[i] = ch;
} else {
sanitized[i] = '_';
}
}
return new String(sanitized);
}
/**
* Throw an exception if the metric label name is invalid.
*/
protected static void checkMetricLabelName(String name) {
if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) {
throw new IllegalArgumentException("Invalid metric label name: " + name);
}
if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) {
throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name);
}
}
/**
* Convert a double to its string representation in Go.
*/
public static String doubleToGoString(double d) {
if (d == Double.POSITIVE_INFINITY) {
return "+Inf";
}
if (d == Double.NEGATIVE_INFINITY) {
return "-Inf";
}
return Double.toString(d);
}
}