io.prometheus.client.SimpleCollector Maven / Gradle / Ivy
Show all versions of simpleclient Show documentation
package io.prometheus.client;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.Arrays;
import java.util.List;
/**
* Common functionality for {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}.
*
* This class handles common initialization and label logic for the standard metrics.
* You should never subclass this class.
*
*
Initialization
* After calling build() on a subclass, {@link Builder#name(String) name},
* {@link SimpleCollector.Builder#help(String) help},
* {@link SimpleCollector.Builder#labelNames(String...) labelNames},
* {@link SimpleCollector.Builder#namespace(String) namespace} and
* {@link SimpleCollector.Builder#subsystem(String) subsystem} can be called to configure the Collector.
* These return this
to allow calls to be chained.
* Once configured, call {@link SimpleCollector.Builder#create create}
* (which is also called by {@link SimpleCollector.Builder#register register}).
*
* The fullname of the metric is namespace_subsystem_name
, but only name
is required.
*
*
Labels
* {@link SimpleCollector.Builder#labelNames labelNames} specifies which (if any) labels the metrics will have, and
* {@link #labels} returns the Child of the metric that represents that particular set of labels.
* {@link Gauge}, {@link Counter} and {@link Summary} all offer convenience methods to avoid needing to call
* {@link #labels} for metrics with no labels.
*
* {@link #remove} and {@link #clear} can be used to remove children.
*
* Warning #1: Metrics that don't always export something are difficult to monitor, if you know in advance
* what labels will be in use you should initialise them be calling {@link #labels}.
* This is done for you for metrics with no labels.
*
* Warning #2: While labels are very powerful, avoid overly granular metric labels.
* The combinatorial explosion of breaking out a metric in many dimensions can produce huge numbers
* of timeseries, which will then take longer and more resources to process.
*
* As a rule of thumb aim to keep the cardinality of metrics below ten, and limit where the
* cardinality exceeds that value. For example rather than breaking out latency
* by customer and endpoint in one metric, you might have two metrics with one breaking out
* by each. If the cardinality is in the hundreds, you may wish to consider removing the breakout
* by one of the dimensions altogether.
*/
public abstract class SimpleCollector extends Collector {
protected final String fullname;
protected final String help;
protected final List labelNames;
protected final ConcurrentMap, Child> children = new ConcurrentHashMap, Child>();
protected Child noLabelsChild;
/**
* Return the Child with the given labels, creating it if needed.
*
* Must be passed the same number of labels are were passed to {@link #labelNames}.
*/
public Child labels(String... labelValues) {
if (labelValues.length != labelNames.size()) {
throw new IllegalArgumentException("Incorrect number of labels.");
}
for (String label: labelValues) {
if (label == null) {
throw new IllegalArgumentException("Label cannot be null.");
}
}
List key = Arrays.asList(labelValues);
Child c = children.get(key);
if (c != null) {
return c;
}
children.putIfAbsent(key, newChild());
return children.get(key);
}
/**
* Remove the Child with the given labels.
*
* Any references to the Child are invalidated.
*/
public void remove(String... labelValues) {
children.remove(Arrays.asList(labelValues));
initializeNoLabelsChild();
}
/**
* Remove all children.
*
* Any references to any children are invalidated.
*/
public void clear() {
children.clear();
initializeNoLabelsChild();
}
/**
* Initialize the child with no labels.
*/
protected void initializeNoLabelsChild() {
// Initialize metric if it has no labels.
if (labelNames.size() == 0) {
noLabelsChild = labels();
}
}
/**
* Replace the Child with the given labels.
*
* This is intended for advanced uses, in particular proxying metrics
* from another monitoring system. This allows for callbacks for returning
* values for {@link Counter} and {@link Gauge} without having to implement
* a full {@link Collector}.
*
* An example with {@link Gauge}:
*
* {@code
* Gauge.build().name("current_time").help("Current unixtime.").create()
* .setChild(new Gauge.Child() {
* public double get() {
* return System.currentTimeMillis() / MILLISECONDS_PER_SECOND;
* }
* }).register();
* }
*
*
* Any references any previous Child with these labelValues are invalidated.
* A metric should be either all callbacks, or none.
*/
public T setChild(Child child, String... labelValues) {
if (labelValues.length != labelNames.size()) {
throw new IllegalArgumentException("Incorrect number of labels.");
}
children.put(Arrays.asList(labelValues), child);
return (T)this;
}
/**
* Return a new child, workaround for Java generics limitations.
*/
protected abstract Child newChild();
protected List familySamplesList(Collector.Type type, List samples) {
MetricFamilySamples mfs = new MetricFamilySamples(fullname, type, help, samples);
List mfsList = new ArrayList(1);
mfsList.add(mfs);
return mfsList;
}
protected SimpleCollector(Builder b) {
if (b.name.isEmpty()) throw new IllegalStateException("Name hasn't been set.");
String name = b.name;
if (!b.subsystem.isEmpty()) {
name = b.subsystem + '_' + name;
}
if (!b.namespace.isEmpty()) {
name = b.namespace + '_' + name;
}
fullname = name;
checkMetricName(fullname);
if (b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set.");
help = b.help;
labelNames = Arrays.asList(b.labelNames);
for (String n: labelNames) {
checkMetricLabelName(n);
}
if (!b.dontInitializeNoLabelsChild) {
initializeNoLabelsChild();
}
}
/**
* Builders let you configure and then create collectors.
*/
public abstract static class Builder, C extends SimpleCollector> {
String namespace = "";
String subsystem = "";
String name = "";
String fullname = "";
String help = "";
String[] labelNames = new String[]{};
// Some metrics require additional setup before the initialization can be done.
boolean dontInitializeNoLabelsChild;
/**
* Set the name of the metric. Required.
*/
public B name(String name) {
this.name = name;
return (B)this;
}
/**
* Set the subsystem of the metric. Optional.
*/
public B subsystem(String subsystem) {
this.subsystem = subsystem;
return (B)this;
}
/**
* Set the namespace of the metric. Optional.
*/
public B namespace(String namespace) {
this.namespace = namespace;
return (B)this;
}
/**
* Set the help string of the metric. Required.
*/
public B help(String help) {
this.help = help;
return (B)this;
}
/**
* Set the labelNames of the metric. Optional, defaults to no labels.
*/
public B labelNames(String... labelNames) {
this.labelNames = labelNames;
return (B)this;
}
/**
* Return the constructed collector.
*
* Abstract due to generics limitations.
*/
public abstract C create();
/**
* Create and register the Collector with the default registry.
*/
public C register() {
return register(CollectorRegistry.defaultRegistry);
}
/**
* Create and register the Collector with the given registry.
*/
public C register(CollectorRegistry registry) {
C sc = create();
registry.register(sc);
return sc;
}
}
}