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}