001 002package io.prometheus.client; 003 004import io.prometheus.client.exemplars.Exemplar; 005 006import java.util.ArrayList; 007import java.util.List; 008import java.util.regex.Pattern; 009 010/** 011 * A collector for a set of metrics. 012 * <p> 013 * Normal users should use {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}. 014 * <p> 015 * Subclasssing Collector is for advanced uses, such as proxying metrics from another monitoring system. 016 * It is it the responsibility of subclasses to ensure they produce valid metrics. 017 * @see <a href="http://prometheus.io/docs/instrumenting/exposition_formats/">Exposition formats</a>. 018 */ 019public abstract class Collector { 020 021 /** 022 * Return all metrics of this Collector. 023 */ 024 public abstract List<MetricFamilySamples> collect(); 025 026 /** 027 * Like {@link #collect()}, but the result should only contain {@code MetricFamilySamples} where 028 * {@code sampleNameFilter.test(name)} is {@code true} for at least one Sample name. 029 * <p> 030 * The default implementation first collects all {@code MetricFamilySamples} and then discards the ones 031 * where {@code sampleNameFilter.test(name)} returns {@code false} for all names in 032 * {@link MetricFamilySamples#getNames()}. 033 * To improve performance, collector implementations should override this method to prevent 034 * {@code MetricFamilySamples} from being collected if they will be discarded anyways. 035 * See {@code ThreadExports} for an example. 036 * <p> 037 * Note that the resulting List may contain {@code MetricFamilySamples} where some Sample names return 038 * {@code true} for {@code sampleNameFilter.test(name)} but some Sample names return {@code false}. 039 * This is ok, because before we produce the output format we will call 040 * {@link MetricFamilySamples#filter(Predicate)} to strip all Samples where {@code sampleNameFilter.test(name)} 041 * returns {@code false}. 042 * 043 * @param sampleNameFilter may be {@code null}, indicating that all metrics should be collected. 044 */ 045 public List<MetricFamilySamples> collect(Predicate<String> sampleNameFilter) { 046 List<MetricFamilySamples> all = collect(); 047 if (sampleNameFilter == null) { 048 return all; 049 } 050 List<MetricFamilySamples> remaining = new ArrayList<MetricFamilySamples>(all.size()); 051 for (MetricFamilySamples mfs : all) { 052 for (String name : mfs.getNames()) { 053 if (sampleNameFilter.test(name)) { 054 remaining.add(mfs); 055 break; 056 } 057 } 058 } 059 return remaining; 060 } 061 062 public enum Type { 063 UNKNOWN, // This is untyped in Prometheus text format. 064 COUNTER, 065 GAUGE, 066 STATE_SET, 067 INFO, 068 HISTOGRAM, 069 GAUGE_HISTOGRAM, 070 SUMMARY, 071 } 072 073 /** 074 * A metric, and all of its samples. 075 */ 076 static public class MetricFamilySamples { 077 public final String name; 078 public final String unit; 079 public final Type type; 080 public final String help; 081 public final List<Sample> samples; // this list is modified when samples are added/removed. 082 083 public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) { 084 this(name, "", type, help, samples); 085 } 086 087 public MetricFamilySamples(String name, String unit, Type type, String help, List<Sample> samples) { 088 if (!unit.isEmpty() && !name.endsWith("_" + unit)) { 089 throw new IllegalArgumentException("Metric's unit is not the suffix of the metric name: " + name); 090 } 091 if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) { 092 throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name); 093 } 094 List<Sample> mungedSamples = samples; 095 // Deal with _total from pre-OM automatically. 096 if (type == Type.COUNTER) { 097 if (name.endsWith("_total")) { 098 name = name.substring(0, name.length() - 6); 099 } 100 String withTotal = name + "_total"; 101 mungedSamples = new ArrayList<Sample>(samples.size()); 102 for (Sample s: samples) { 103 String n = s.name; 104 if (name.equals(n)) { 105 n = withTotal; 106 } 107 mungedSamples.add(new Sample(n, s.labelNames, s.labelValues, s.value, s.exemplar, s.timestampMs)); 108 } 109 } 110 this.name = name; 111 this.unit = unit; 112 this.type = type; 113 this.help = help; 114 this.samples = mungedSamples; 115 } 116 117 /** 118 * @param sampleNameFilter may be {@code null} indicating that the result contains the complete list of samples. 119 * @return A new MetricFamilySamples containing only the Samples matching the {@code sampleNameFilter}, 120 * or {@code null} if no Sample matches. 121 */ 122 public MetricFamilySamples filter(Predicate<String> sampleNameFilter) { 123 if (sampleNameFilter == null) { 124 return this; 125 } 126 List<Sample> remainingSamples = new ArrayList<Sample>(samples.size()); 127 for (Sample sample : samples) { 128 if (sampleNameFilter.test(sample.name)) { 129 remainingSamples.add(sample); 130 } 131 } 132 if (remainingSamples.isEmpty()) { 133 return null; 134 } 135 return new MetricFamilySamples(name, unit, type, help, remainingSamples); 136 } 137 138 /** 139 * List of names that are reserved for Samples in these MetricsFamilySamples. 140 * <p> 141 * This is used in two places: 142 * <ol> 143 * <li>To check potential name collisions in {@link CollectorRegistry#register(Collector)}. 144 * <li>To check if a collector may contain metrics matching the metric name filter 145 * in {@link Collector#collect(Predicate)}. 146 * </ol> 147 * Note that {@code getNames()} always includes the name without suffix, even though some 148 * metrics types (like Counter) will not have a Sample with that name. 149 * The reason is that the name without suffix is used in the metadata comments ({@code # TYPE}, {@code # UNIT}, 150 * {@code # HELP}), and as this name <a href="https://github.com/prometheus/common/issues/319">must be unique</a> 151 * we include the name without suffix here as well. 152 */ 153 public String[] getNames() { 154 switch (type) { 155 case COUNTER: 156 return new String[]{ 157 name + "_total", 158 name + "_created", 159 name 160 }; 161 case SUMMARY: 162 return new String[]{ 163 name + "_count", 164 name + "_sum", 165 name + "_created", 166 name 167 }; 168 case HISTOGRAM: 169 return new String[]{ 170 name + "_count", 171 name + "_sum", 172 name + "_bucket", 173 name + "_created", 174 name 175 }; 176 case GAUGE_HISTOGRAM: 177 return new String[]{ 178 name + "_gcount", 179 name + "_gsum", 180 name + "_bucket", 181 name 182 }; 183 case INFO: 184 return new String[]{ 185 name + "_info", 186 name 187 }; 188 default: 189 return new String[]{name}; 190 } 191 } 192 193 194 @Override 195 public boolean equals(Object obj) { 196 if (!(obj instanceof MetricFamilySamples)) { 197 return false; 198 } 199 MetricFamilySamples other = (MetricFamilySamples) obj; 200 201 return other.name.equals(name) 202 && other.unit.equals(unit) 203 && other.type.equals(type) 204 && other.help.equals(help) 205 && other.samples.equals(samples); 206 } 207 208 @Override 209 public int hashCode() { 210 int hash = 1; 211 hash = 37 * hash + name.hashCode(); 212 hash = 37 * hash + unit.hashCode(); 213 hash = 37 * hash + type.hashCode(); 214 hash = 37 * hash + help.hashCode(); 215 hash = 37 * hash + samples.hashCode(); 216 return hash; 217 } 218 219 @Override 220 public String toString() { 221 return "Name: " + name + " Unit:" + unit + " Type: " + type + " Help: " + help + 222 " Samples: " + samples; 223 } 224 225 /** 226 * A single Sample, with a unique name and set of labels. 227 */ 228 public static class Sample { 229 public final String name; 230 public final List<String> labelNames; 231 public final List<String> labelValues; // Must have same length as labelNames. 232 public final double value; 233 public final Exemplar exemplar; 234 public final Long timestampMs; // It's an epoch format with milliseconds value included (this field is subject to change). 235 236 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar, Long timestampMs) { 237 this.name = name; 238 this.labelNames = labelNames; 239 this.labelValues = labelValues; 240 this.value = value; 241 this.exemplar = exemplar; 242 this.timestampMs = timestampMs; 243 } 244 245 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Long timestampMs) { 246 this(name, labelNames, labelValues, value, null, timestampMs); 247 } 248 249 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar) { 250 this(name, labelNames, labelValues, value, exemplar, null); 251 } 252 253 public Sample(String name, List<String> labelNames, List<String> labelValues, double value) { 254 this(name, labelNames, labelValues, value, null, null); 255 } 256 257 @Override 258 public boolean equals(Object obj) { 259 if (!(obj instanceof Sample)) { 260 return false; 261 } 262 Sample other = (Sample) obj; 263 264 return other.name.equals(name) && 265 other.labelNames.equals(labelNames) && 266 other.labelValues.equals(labelValues) && 267 other.value == value && 268 (exemplar == null && other.exemplar == null || other.exemplar != null && other.exemplar.equals(exemplar)) && 269 (timestampMs == null && other.timestampMs == null || other.timestampMs != null && other.timestampMs.equals(timestampMs)); 270 } 271 272 @Override 273 public int hashCode() { 274 int hash = 1; 275 hash = 37 * hash + name.hashCode(); 276 hash = 37 * hash + labelNames.hashCode(); 277 hash = 37 * hash + labelValues.hashCode(); 278 long d = Double.doubleToLongBits(value); 279 hash = 37 * hash + (int)(d ^ (d >>> 32)); 280 if (timestampMs != null) { 281 hash = 37 * hash + timestampMs.hashCode(); 282 } 283 if (exemplar != null) { 284 hash = 37 * exemplar.hashCode(); 285 } 286 return hash; 287 } 288 289 @Override 290 public String toString() { 291 return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues + 292 " Value: " + value + " TimestampMs: " + timestampMs; 293 } 294 } 295 } 296 297 /** 298 * Register the Collector with the default registry. 299 */ 300 public <T extends Collector> T register() { 301 return register(CollectorRegistry.defaultRegistry); 302 } 303 304 /** 305 * Register the Collector with the given registry. 306 */ 307 public <T extends Collector> T register(CollectorRegistry registry) { 308 registry.register(this); 309 return (T)this; 310 } 311 312 public interface Describable { 313 /** 314 * Provide a list of metric families this Collector is expected to return. 315 * 316 * These should exclude the samples. This is used by the registry to 317 * detect collisions and duplicate registrations. 318 * 319 * Usually custom collectors do not have to implement Describable. If 320 * Describable is not implemented and the CollectorRegistry was created 321 * with auto describe enabled (which is the case for the default registry) 322 * then {@link #collect} will be called at registration time instead of 323 * describe. If this could cause problems, either implement a proper 324 * describe, or if that's not practical have describe return an empty 325 * list. 326 */ 327 List<MetricFamilySamples> describe(); 328 } 329 330 331 /* Various utility functions for implementing Collectors. */ 332 333 /** 334 * Number of nanoseconds in a second. 335 */ 336 public static final double NANOSECONDS_PER_SECOND = 1E9; 337 /** 338 * Number of milliseconds in a second. 339 */ 340 public static final double MILLISECONDS_PER_SECOND = 1E3; 341 342 private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*"); 343 private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); 344 private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*"); 345 346 /** 347 * Throw an exception if the metric name is invalid. 348 */ 349 protected static void checkMetricName(String name) { 350 if (!METRIC_NAME_RE.matcher(name).matches()) { 351 throw new IllegalArgumentException("Invalid metric name: " + name); 352 } 353 } 354 355 private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_:]"); 356 private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); 357 358 /** 359 * Sanitize metric name 360 */ 361 public static String sanitizeMetricName(String metricName) { 362 return SANITIZE_BODY_PATTERN.matcher( 363 SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_") 364 ).replaceAll("_"); 365 } 366 367 /** 368 * Throw an exception if the metric label name is invalid. 369 */ 370 protected static void checkMetricLabelName(String name) { 371 if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) { 372 throw new IllegalArgumentException("Invalid metric label name: " + name); 373 } 374 if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) { 375 throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name); 376 } 377 } 378 379 /** 380 * Convert a double to its string representation in Go. 381 */ 382 public static String doubleToGoString(double d) { 383 if (d == Double.POSITIVE_INFINITY) { 384 return "+Inf"; 385 } 386 if (d == Double.NEGATIVE_INFINITY) { 387 return "-Inf"; 388 } 389 return Double.toString(d); 390 } 391}