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 List<String> labelNames; 053 054 protected final ConcurrentMap<List<String>, Child> children = new ConcurrentHashMap<List<String>, Child>(); 055 protected Child noLabelsChild; 056 057 /** 058 * Return the Child with the given labels, creating it if needed. 059 * <p> 060 * Must be passed the same number of labels are were passed to {@link #labelNames}. 061 */ 062 public Child labels(String... labelValues) { 063 if (labelValues.length != labelNames.size()) { 064 throw new IllegalArgumentException("Incorrect number of labels."); 065 } 066 for (String label: labelValues) { 067 if (label == null) { 068 throw new IllegalArgumentException("Label cannot be null."); 069 } 070 } 071 List<String> key = Arrays.asList(labelValues); 072 Child c = children.get(key); 073 if (c != null) { 074 return c; 075 } 076 children.putIfAbsent(key, newChild()); 077 return children.get(key); 078 } 079 080 /** 081 * Remove the Child with the given labels. 082 * <p> 083 * Any references to the Child are invalidated. 084 */ 085 public void remove(String... labelValues) { 086 children.remove(Arrays.asList(labelValues)); 087 initializeNoLabelsChild(); 088 } 089 090 /** 091 * Remove all children. 092 * <p> 093 * Any references to any children are invalidated. 094 */ 095 public void clear() { 096 children.clear(); 097 initializeNoLabelsChild(); 098 } 099 100 /** 101 * Initialize the child with no labels. 102 */ 103 protected void initializeNoLabelsChild() { 104 // Initialize metric if it has no labels. 105 if (labelNames.size() == 0) { 106 noLabelsChild = labels(); 107 } 108 } 109 110 /** 111 * Replace the Child with the given labels. 112 * <p> 113 * This is intended for advanced uses, in particular proxying metrics 114 * from another monitoring system. This allows for callbacks for returning 115 * values for {@link Counter} and {@link Gauge} without having to implement 116 * a full {@link Collector}. 117 * <p> 118 * An example with {@link Gauge}: 119 * <pre> 120 * {@code 121 * Gauge.build().name("current_time").help("Current unixtime.").create() 122 * .setChild(new Gauge.Child() { 123 * public double get() { 124 * return System.currentTimeMillis() / MILLISECONDS_PER_SECOND; 125 * } 126 * }).register(); 127 * } 128 * </pre> 129 * <p> 130 * Any references any previous Child with these labelValues are invalidated. 131 * A metric should be either all callbacks, or none. 132 */ 133 public <T extends Collector> T setChild(Child child, String... labelValues) { 134 if (labelValues.length != labelNames.size()) { 135 throw new IllegalArgumentException("Incorrect number of labels."); 136 } 137 children.put(Arrays.asList(labelValues), child); 138 return (T)this; 139 } 140 141 /** 142 * Return a new child, workaround for Java generics limitations. 143 */ 144 protected abstract Child newChild(); 145 146 protected List<MetricFamilySamples> familySamplesList(Collector.Type type, List<MetricFamilySamples.Sample> samples) { 147 MetricFamilySamples mfs = new MetricFamilySamples(fullname, type, help, samples); 148 List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>(1); 149 mfsList.add(mfs); 150 return mfsList; 151 } 152 153 protected SimpleCollector(Builder b) { 154 if (b.name.isEmpty()) throw new IllegalStateException("Name hasn't been set."); 155 String name = b.name; 156 if (!b.subsystem.isEmpty()) { 157 name = b.subsystem + '_' + name; 158 } 159 if (!b.namespace.isEmpty()) { 160 name = b.namespace + '_' + name; 161 } 162 fullname = name; 163 checkMetricName(fullname); 164 if (b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set."); 165 help = b.help; 166 labelNames = Arrays.asList(b.labelNames); 167 168 for (String n: labelNames) { 169 checkMetricLabelName(n); 170 } 171 172 if (!b.dontInitializeNoLabelsChild) { 173 initializeNoLabelsChild(); 174 } 175 } 176 177 /** 178 * Builders let you configure and then create collectors. 179 */ 180 public abstract static class Builder<B extends Builder<B, C>, C extends SimpleCollector> { 181 String namespace = ""; 182 String subsystem = ""; 183 String name = ""; 184 String fullname = ""; 185 String help = ""; 186 String[] labelNames = new String[]{}; 187 // Some metrics require additional setup before the initialization can be done. 188 boolean dontInitializeNoLabelsChild; 189 190 /** 191 * Set the name of the metric. Required. 192 */ 193 public B name(String name) { 194 this.name = name; 195 return (B)this; 196 } 197 /** 198 * Set the subsystem of the metric. Optional. 199 */ 200 public B subsystem(String subsystem) { 201 this.subsystem = subsystem; 202 return (B)this; 203 } 204 /** 205 * Set the namespace of the metric. Optional. 206 */ 207 public B namespace(String namespace) { 208 this.namespace = namespace; 209 return (B)this; 210 } 211 /** 212 * Set the help string of the metric. Required. 213 */ 214 public B help(String help) { 215 this.help = help; 216 return (B)this; 217 } 218 /** 219 * Set the labelNames of the metric. Optional, defaults to no labels. 220 */ 221 public B labelNames(String... labelNames) { 222 this.labelNames = labelNames; 223 return (B)this; 224 } 225 226 /** 227 * Return the constructed collector. 228 * <p> 229 * Abstract due to generics limitations. 230 */ 231 public abstract C create(); 232 233 /** 234 * Create and register the Collector with the default registry. 235 */ 236 public C register() { 237 return register(CollectorRegistry.defaultRegistry); 238 } 239 240 /** 241 * Create and register the Collector with the given registry. 242 */ 243 public C register(CollectorRegistry registry) { 244 C sc = create(); 245 registry.register(sc); 246 return sc; 247 } 248 } 249}