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 * Return all of the metrics of this Collector. 022 */ 023 public abstract List<MetricFamilySamples> collect(); 024 public enum Type { 025 UNKNOWN, // This is untyped in Prometheus text format. 026 COUNTER, 027 GAUGE, 028 STATE_SET, 029 INFO, 030 HISTOGRAM, 031 GAUGE_HISTOGRAM, 032 SUMMARY, 033 } 034 035 /** 036 * A metric, and all of its samples. 037 */ 038 static public class MetricFamilySamples { 039 public final String name; 040 public final String unit; 041 public final Type type; 042 public final String help; 043 public final List<Sample> samples; 044 045 public MetricFamilySamples(String name, String unit, Type type, String help, List<Sample> samples) { 046 if (!unit.isEmpty() && !name.endsWith("_" + unit)) { 047 throw new IllegalArgumentException("Metric's unit is not the suffix of the metric name: " + name); 048 } 049 if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) { 050 throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name); 051 } 052 List<Sample> mungedSamples = samples; 053 // Deal with _total from pre-OM automatically. 054 if (type == Type.COUNTER) { 055 if (name.endsWith("_total")) { 056 name = name.substring(0, name.length() - 6); 057 } 058 String withTotal = name + "_total"; 059 mungedSamples = new ArrayList<Sample>(samples.size()); 060 for (Sample s: samples) { 061 String n = s.name; 062 if (name.equals(n)) { 063 n = withTotal; 064 } 065 mungedSamples.add(new Sample(n, s.labelNames, s.labelValues, s.value, s.exemplar, s.timestampMs)); 066 } 067 } 068 this.name = name; 069 this.unit = unit; 070 this.type = type; 071 this.help = help; 072 this.samples = mungedSamples; 073 } 074 075 public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) { 076 this(name, "", type, help, samples); 077 } 078 079 @Override 080 public boolean equals(Object obj) { 081 if (!(obj instanceof MetricFamilySamples)) { 082 return false; 083 } 084 MetricFamilySamples other = (MetricFamilySamples) obj; 085 086 return other.name.equals(name) 087 && other.unit.equals(unit) 088 && other.type.equals(type) 089 && other.help.equals(help) 090 && other.samples.equals(samples); 091 } 092 093 @Override 094 public int hashCode() { 095 int hash = 1; 096 hash = 37 * hash + name.hashCode(); 097 hash = 37 * hash + unit.hashCode(); 098 hash = 37 * hash + type.hashCode(); 099 hash = 37 * hash + help.hashCode(); 100 hash = 37 * hash + samples.hashCode(); 101 return hash; 102 } 103 104 @Override 105 public String toString() { 106 return "Name: " + name + " Unit:" + unit + " Type: " + type + " Help: " + help + 107 " Samples: " + samples; 108 } 109 110 /** 111 * A single Sample, with a unique name and set of labels. 112 */ 113 public static class Sample { 114 public final String name; 115 public final List<String> labelNames; 116 public final List<String> labelValues; // Must have same length as labelNames. 117 public final double value; 118 public final Exemplar exemplar; 119 public final Long timestampMs; // It's an epoch format with milliseconds value included (this field is subject to change). 120 121 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar, Long timestampMs) { 122 this.name = name; 123 this.labelNames = labelNames; 124 this.labelValues = labelValues; 125 this.value = value; 126 this.exemplar = exemplar; 127 this.timestampMs = timestampMs; 128 } 129 130 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Long timestampMs) { 131 this(name, labelNames, labelValues, value, null, timestampMs); 132 } 133 134 public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Exemplar exemplar) { 135 this(name, labelNames, labelValues, value, exemplar, null); 136 } 137 138 public Sample(String name, List<String> labelNames, List<String> labelValues, double value) { 139 this(name, labelNames, labelValues, value, null, null); 140 } 141 142 @Override 143 public boolean equals(Object obj) { 144 if (!(obj instanceof Sample)) { 145 return false; 146 } 147 Sample other = (Sample) obj; 148 149 return other.name.equals(name) && 150 other.labelNames.equals(labelNames) && 151 other.labelValues.equals(labelValues) && 152 other.value == value && 153 (exemplar == null && other.exemplar == null || other.exemplar != null && other.exemplar.equals(exemplar)) && 154 (timestampMs == null && other.timestampMs == null || other.timestampMs != null && other.timestampMs.equals(timestampMs)); 155 } 156 157 @Override 158 public int hashCode() { 159 int hash = 1; 160 hash = 37 * hash + name.hashCode(); 161 hash = 37 * hash + labelNames.hashCode(); 162 hash = 37 * hash + labelValues.hashCode(); 163 long d = Double.doubleToLongBits(value); 164 hash = 37 * hash + (int)(d ^ (d >>> 32)); 165 if (timestampMs != null) { 166 hash = 37 * hash + timestampMs.hashCode(); 167 } 168 if (exemplar != null) { 169 hash = 37 * exemplar.hashCode(); 170 } 171 return hash; 172 } 173 174 @Override 175 public String toString() { 176 return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues + 177 " Value: " + value + " TimestampMs: " + timestampMs; 178 } 179 } 180 } 181 182 /** 183 * Register the Collector with the default registry. 184 */ 185 public <T extends Collector> T register() { 186 return register(CollectorRegistry.defaultRegistry); 187 } 188 189 /** 190 * Register the Collector with the given registry. 191 */ 192 public <T extends Collector> T register(CollectorRegistry registry) { 193 registry.register(this); 194 return (T)this; 195 } 196 197 public interface Describable { 198 /** 199 * Provide a list of metric families this Collector is expected to return. 200 * 201 * These should exclude the samples. This is used by the registry to 202 * detect collisions and duplicate registrations. 203 * 204 * Usually custom collectors do not have to implement Describable. If 205 * Describable is not implemented and the CollectorRegistry was created 206 * with auto describe enabled (which is the case for the default registry) 207 * then {@link collect} will be called at registration time instead of 208 * describe. If this could cause problems, either implement a proper 209 * describe, or if that's not practical have describe return an empty 210 * list. 211 */ 212 List<MetricFamilySamples> describe(); 213 } 214 215 216 /* Various utility functions for implementing Collectors. */ 217 218 /** 219 * Number of nanoseconds in a second. 220 */ 221 public static final double NANOSECONDS_PER_SECOND = 1E9; 222 /** 223 * Number of milliseconds in a second. 224 */ 225 public static final double MILLISECONDS_PER_SECOND = 1E3; 226 227 private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*"); 228 private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); 229 private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*"); 230 231 /** 232 * Throw an exception if the metric name is invalid. 233 */ 234 protected static void checkMetricName(String name) { 235 if (!METRIC_NAME_RE.matcher(name).matches()) { 236 throw new IllegalArgumentException("Invalid metric name: " + name); 237 } 238 } 239 240 private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_:]"); 241 private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); 242 243 /** 244 * Sanitize metric name 245 */ 246 public static String sanitizeMetricName(String metricName) { 247 return SANITIZE_BODY_PATTERN.matcher( 248 SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_") 249 ).replaceAll("_"); 250 } 251 252 /** 253 * Throw an exception if the metric label name is invalid. 254 */ 255 protected static void checkMetricLabelName(String name) { 256 if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) { 257 throw new IllegalArgumentException("Invalid metric label name: " + name); 258 } 259 if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) { 260 throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name); 261 } 262 } 263 264 /** 265 * Convert a double to its string representation in Go. 266 */ 267 public static String doubleToGoString(double d) { 268 if (d == Double.POSITIVE_INFINITY) { 269 return "+Inf"; 270 } 271 if (d == Double.NEGATIVE_INFINITY) { 272 return "-Inf"; 273 } 274 return Double.toString(d); 275 } 276}