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