001package io.prometheus.client; 002 003import java.util.ArrayList; 004import java.util.concurrent.ConcurrentHashMap; 005import java.util.concurrent.ConcurrentMap; 006import java.util.Arrays; 007import java.util.List; 008 009/** 010 * Common functionality for {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}. 011 * <p> 012 * This class handles common initialization and label logic for the standard metrics. 013 * You should never subclass this class. 014 * <p> 015 * <h2>Initialization</h2> 016 * After calling build() on a subclass, {@link Builder#name(String) name}, 017 * {@link SimpleCollector.Builder#help(String) help}, 018 * {@link SimpleCollector.Builder#labelNames(String...) labelNames}, 019 * {@link SimpleCollector.Builder#namespace(String) namespace} and 020 * {@link SimpleCollector.Builder#subsystem(String) subsystem} can be called to configure the Collector. 021 * These return <code>this</code> to allow calls to be chained. 022 * Once configured, call {@link SimpleCollector.Builder#create create} 023 * (which is also called by {@link SimpleCollector.Builder#register register}). 024 * <p> 025 * The fullname of the metric is <code>namespace_subsystem_name</code>, but only <code>name</code> is required. 026 * 027 * <h2>Labels</h2> 028 * {@link SimpleCollector.Builder#labelNames labelNames} specifies which (if any) labels the metrics will have, and 029 * {@link #labels} returns the Child of the metric that represents that particular set of labels. 030 * {@link Gauge}, {@link Counter} and {@link Summary} all offer convenience methods to avoid needing to call 031 * {@link #labels} for metrics with no labels. 032 * <p> 033 * {@link #remove} and {@link #clear} can be used to remove children. 034 * <p> 035 * <em>Warning #1:</em> Metrics that don't always export something are difficult to monitor, if you know in advance 036 * what labels will be in use you should initialise them be calling {@link #labels}. 037 * This is done for you for metrics with no labels. 038 * <p> 039 * <em>Warning #2:</em> While labels are very powerful, avoid overly granular metric labels. 040 * The combinatorial explosion of breaking out a metric in many dimensions can produce huge numbers 041 * of timeseries, which will then take longer and more resources to process. 042 * <p> 043 * As a rule of thumb aim to keep the cardinality of metrics below ten, and limit where the 044 * cardinality exceeds that value. For example rather than breaking out latency 045 * by customer and endpoint in one metric, you might have two metrics with one breaking out 046 * by each. If the cardinality is in the hundreds, you may wish to consider removing the breakout 047 * by one of the dimensions altogether. 048 */ 049public abstract class SimpleCollector<Child> extends Collector { 050 protected final String fullname; 051 protected final String help; 052 protected final String unit; 053 protected final List<String> labelNames; 054 055 protected final ConcurrentMap<List<String>, Child> children = new ConcurrentHashMap<List<String>, Child>(); 056 protected Child noLabelsChild; 057 058 /** 059 * Return the Child with the given labels, creating it if needed. 060 * <p> 061 * Must be passed the same number of labels are were passed to {@link #labelNames}. 062 */ 063 public Child labels(String... labelValues) { 064 if (labelValues.length != labelNames.size()) { 065 throw new IllegalArgumentException("Incorrect number of labels."); 066 } 067 for (String label: labelValues) { 068 if (label == null) { 069 throw new IllegalArgumentException("Label cannot be null."); 070 } 071 } 072 List<String> key = Arrays.asList(labelValues); 073 Child c = children.get(key); 074 if (c != null) { 075 return c; 076 } 077 Child c2 = newChild(); 078 Child tmp = children.putIfAbsent(key, c2); 079 return tmp == null ? c2 : tmp; 080 } 081 082 /** 083 * Remove the Child with the given labels. 084 * <p> 085 * Any references to the Child are invalidated. 086 */ 087 public void remove(String... labelValues) { 088 children.remove(Arrays.asList(labelValues)); 089 initializeNoLabelsChild(); 090 } 091 092 /** 093 * Remove all children. 094 * <p> 095 * Any references to any children are invalidated. 096 */ 097 public void clear() { 098 children.clear(); 099 initializeNoLabelsChild(); 100 } 101 102 /** 103 * Initialize the child with no labels. 104 */ 105 protected void initializeNoLabelsChild() { 106 // Initialize metric if it has no labels. 107 if (labelNames.size() == 0) { 108 noLabelsChild = labels(); 109 } 110 } 111 112 /** 113 * Replace the Child with the given labels. 114 * <p> 115 * This is intended for advanced uses, in particular proxying metrics 116 * from another monitoring system. This allows for callbacks for returning 117 * values for {@link Counter} and {@link Gauge} without having to implement 118 * a full {@link Collector}. 119 * <p> 120 * An example with {@link Gauge}: 121 * <pre> 122 * {@code 123 * Gauge.build().name("current_time").help("Current unixtime.").create() 124 * .setChild(new Gauge.Child() { 125 * public double get() { 126 * return System.currentTimeMillis() / MILLISECONDS_PER_SECOND; 127 * } 128 * }).register(); 129 * } 130 * </pre> 131 * <p> 132 * Any references any previous Child with these labelValues are invalidated. 133 * A metric should be either all callbacks, or none. 134 */ 135 public <T extends Collector> T setChild(Child child, String... labelValues) { 136 if (labelValues.length != labelNames.size()) { 137 throw new IllegalArgumentException("Incorrect number of labels."); 138 } 139 children.put(Arrays.asList(labelValues), child); 140 return (T)this; 141 } 142 143 /** 144 * Return a new child, workaround for Java generics limitations. 145 */ 146 protected abstract Child newChild(); 147 148 protected List<MetricFamilySamples> familySamplesList(Collector.Type type, List<MetricFamilySamples.Sample> samples) { 149 MetricFamilySamples mfs = new MetricFamilySamples(fullname, unit, type, help, samples); 150 List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>(1); 151 mfsList.add(mfs); 152 return mfsList; 153 } 154 155 protected SimpleCollector(Builder b) { 156 if (b.name.isEmpty()) throw new IllegalStateException("Name hasn't been set."); 157 String name = b.name; 158 if (!b.subsystem.isEmpty()) { 159 name = b.subsystem + '_' + name; 160 } 161 if (!b.namespace.isEmpty()) { 162 name = b.namespace + '_' + name; 163 } 164 unit = b.unit; 165 if (!unit.isEmpty() && !name.endsWith("_" + unit)) { 166 name += "_" + unit; 167 } 168 fullname = name; 169 checkMetricName(fullname); 170 if (b.help != null && b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set."); 171 help = b.help; 172 labelNames = Arrays.asList(b.labelNames); 173 174 for (String n: labelNames) { 175 checkMetricLabelName(n); 176 } 177 178 if (!b.dontInitializeNoLabelsChild) { 179 initializeNoLabelsChild(); 180 } 181 } 182 183 /** 184 * Builders let you configure and then create collectors. 185 */ 186 public abstract static class Builder<B extends Builder<B, C>, C extends SimpleCollector> { 187 String namespace = ""; 188 String subsystem = ""; 189 String name = ""; 190 String fullname = ""; 191 String unit = ""; 192 String help = ""; 193 String[] labelNames = new String[]{}; 194 // Some metrics require additional setup before the initialization can be done. 195 boolean dontInitializeNoLabelsChild; 196 197 /** 198 * Set the name of the metric. Required. 199 */ 200 public B name(String name) { 201 this.name = name; 202 return (B)this; 203 } 204 /** 205 * Set the subsystem of the metric. Optional. 206 */ 207 public B subsystem(String subsystem) { 208 this.subsystem = subsystem; 209 return (B)this; 210 } 211 /** 212 * Set the namespace of the metric. Optional. 213 */ 214 public B namespace(String namespace) { 215 this.namespace = namespace; 216 return (B)this; 217 } 218 /** 219 * Set the unit of the metric. Optional. 220 * 221 * @since 0.10.0 222 */ 223 public B unit(String unit) { 224 this.unit = unit; 225 return (B)this; 226 } 227 /** 228 * Set the help string of the metric. Required. 229 */ 230 public B help(String help) { 231 this.help = help; 232 return (B)this; 233 } 234 /** 235 * Set the labelNames of the metric. Optional, defaults to no labels. 236 */ 237 public B labelNames(String... labelNames) { 238 this.labelNames = labelNames; 239 return (B)this; 240 } 241 242 /** 243 * Return the constructed collector. 244 * <p> 245 * Abstract due to generics limitations. 246 */ 247 public abstract C create(); 248 249 /** 250 * Create and register the Collector with the default registry. 251 */ 252 public C register() { 253 return register(CollectorRegistry.defaultRegistry); 254 } 255 256 /** 257 * Create and register the Collector with the given registry. 258 */ 259 public C register(CollectorRegistry registry) { 260 C sc = create(); 261 registry.register(sc); 262 return sc; 263 } 264 } 265}