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