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 081 public Sample(String name, List<String> labelNames, List<String> labelValues, double value) { 082 this.name = name; 083 this.labelNames = labelNames; 084 this.labelValues = labelValues; 085 this.value = value; 086 } 087 088 @Override 089 public boolean equals(Object obj) { 090 if (!(obj instanceof Sample)) { 091 return false; 092 } 093 Sample other = (Sample) obj; 094 return other.name.equals(name) && other.labelNames.equals(labelNames) 095 && other.labelValues.equals(labelValues) && other.value == value; 096 } 097 098 @Override 099 public int hashCode() { 100 int hash = 1; 101 hash = 37 * hash + name.hashCode(); 102 hash = 37 * hash + labelNames.hashCode(); 103 hash = 37 * hash + labelValues.hashCode(); 104 long d = Double.doubleToLongBits(value); 105 hash = 37 * hash + (int)(d ^ (d >>> 32)); 106 return hash; 107 } 108 109 @Override 110 public String toString() { 111 return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues + 112 " Value: " + value; 113 } 114 } 115 } 116 117 /** 118 * Register the Collector with the default registry. 119 */ 120 public <T extends Collector> T register() { 121 return register(CollectorRegistry.defaultRegistry); 122 } 123 124 /** 125 * Register the Collector with the given registry. 126 */ 127 public <T extends Collector> T register(CollectorRegistry registry) { 128 registry.register(this); 129 return (T)this; 130 } 131 132 public interface Describable { 133 /** 134 * Provide a list of metric families this Collector is expected to return. 135 * 136 * These should exclude the samples. This is used by the registry to 137 * detect collisions and duplicate registrations. 138 * 139 * Usually custom collectors do not have to implement Describable. If 140 * Describable is not implemented and the CollectorRegistry was created 141 * with auto describe enabled (which is the case for the default registry) 142 * then {@link collect} will be called at registration time instead of 143 * describe. If this could cause problems, either implement a proper 144 * describe, or if that's not practical have describe return an empty 145 * list. 146 */ 147 List<MetricFamilySamples> describe(); 148 } 149 150 151 /* Various utility functions for implementing Collectors. */ 152 153 /** 154 * Number of nanoseconds in a second. 155 */ 156 public static final double NANOSECONDS_PER_SECOND = 1E9; 157 /** 158 * Number of milliseconds in a second. 159 */ 160 public static final double MILLISECONDS_PER_SECOND = 1E3; 161 162 private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*"); 163 private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); 164 private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*"); 165 166 /** 167 * Throw an exception if the metric name is invalid. 168 */ 169 protected static void checkMetricName(String name) { 170 if (!METRIC_NAME_RE.matcher(name).matches()) { 171 throw new IllegalArgumentException("Invalid metric name: " + name); 172 } 173 } 174 175 private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_]"); 176 private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_]"); 177 178 /** 179 * Sanitize metric name 180 */ 181 public static String sanitizeMetricName(String metricName) { 182 return SANITIZE_BODY_PATTERN.matcher( 183 SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_") 184 ).replaceAll("_"); 185 } 186 187 /** 188 * Throw an exception if the metric label name is invalid. 189 */ 190 protected static void checkMetricLabelName(String name) { 191 if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) { 192 throw new IllegalArgumentException("Invalid metric label name: " + name); 193 } 194 if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) { 195 throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name); 196 } 197 } 198 199 /** 200 * Convert a double to its string representation in Go. 201 */ 202 public static String doubleToGoString(double d) { 203 if (d == Double.POSITIVE_INFINITY) { 204 return "+Inf"; 205 } 206 if (d == Double.NEGATIVE_INFINITY) { 207 return "-Inf"; 208 } 209 if (Double.isNaN(d)) { 210 return "NaN"; 211 } 212 return Double.toString(d); 213 } 214}