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