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}