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