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