001package io.prometheus.client;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.Enumeration;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.Iterator;
010import java.util.Map;
011import java.util.NoSuchElementException;
012import java.util.Set;
013import java.util.List;
014
015/**
016 * A registry of Collectors.
017 * <p>
018 * The majority of users should use the {@link #defaultRegistry}, rather than instantiating their own.
019 * <p>
020 * Creating a registry other than the default is primarily useful for unittests, or
021 * pushing a subset of metrics to the <a href="https://github.com/prometheus/pushgateway">Pushgateway</a>
022 * from batch jobs.
023 */
024public class CollectorRegistry {
025  /**
026   * The default registry.
027   */
028  public static final CollectorRegistry defaultRegistry = new CollectorRegistry(true);
029
030
031  private final Map<Collector, List<String>> collectorsToNames = new HashMap<Collector, List<String>>();
032  private final Map<String, Collector> namesToCollectors = new HashMap<String, Collector>();
033
034  private final boolean autoDescribe;
035
036  public CollectorRegistry(){
037    this(false);
038  }
039
040  public CollectorRegistry(boolean autoDescribe) {
041    this.autoDescribe = autoDescribe;
042  }
043
044  /**
045   * Register a Collector.
046   * <p>
047   * A collector can be registered to multiple CollectorRegistries.
048   */
049  public void register(Collector m) {
050    List<String> names = collectorNames(m);
051    synchronized (collectorsToNames) {
052      for (String name : names) {
053        if(namesToCollectors.containsKey(name)) {
054          throw new IllegalArgumentException("Collector already registered that provides name: " + name);
055        }
056      }
057      for (String name : names) {
058        namesToCollectors.put(name, m);
059      }
060      collectorsToNames.put(m, names);
061    }
062  }
063
064  /**
065   * Unregister a Collector.
066   */
067  public void unregister(Collector m) {
068    synchronized (collectorsToNames) {
069      for (String name : collectorsToNames.get(m)) {
070        namesToCollectors.remove(name);
071      }
072      collectorsToNames.remove(m);
073    }
074  }
075  /**
076   * Unregister all Collectors.
077   */
078  public void clear() {
079    synchronized (collectorsToNames) {
080      collectorsToNames.clear();
081      namesToCollectors.clear();
082    }
083  }
084
085  /**
086   * A snapshot of the current collectors.
087   */
088  private Set<Collector> collectors() {
089    synchronized (collectorsToNames) {
090      return new HashSet(collectorsToNames.keySet());
091    }
092  }
093
094  private List<String> collectorNames(Collector m) {
095    List<Collector.MetricFamilySamples> mfs;
096    if (m instanceof Collector.Describable) {
097      mfs = ((Collector.Describable)m).describe();
098    } else if (autoDescribe) {
099      mfs = m.collect();
100    } else {
101      mfs = Collections.emptyList();
102    }
103
104    List<String> names = new ArrayList<String>();
105    for (Collector.MetricFamilySamples family : mfs) {
106      switch (family.type) {
107        case SUMMARY:
108          names.add(family.name + "_count");
109          names.add(family.name + "_sum");
110          names.add(family.name);
111        case HISTOGRAM:
112          names.add(family.name + "_count");
113          names.add(family.name + "_sum");
114          names.add(family.name + "_bucket");
115        default:
116          names.add(family.name);
117      }
118    }
119    return names;
120  }
121
122  /**
123   * Enumeration of metrics of all registered collectors.
124   */
125  public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
126    return new MetricFamilySamplesEnumeration();
127  }
128  class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
129
130    private final Iterator<Collector> collectorIter = collectors().iterator();
131    private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
132    private Collector.MetricFamilySamples next;
133
134    MetricFamilySamplesEnumeration() {
135      findNextElement();
136    }
137
138    private void findNextElement() {
139      if (metricFamilySamples != null && metricFamilySamples.hasNext()) {
140        next = metricFamilySamples.next();
141      } else {
142        while (collectorIter.hasNext()) {
143          metricFamilySamples = collectorIter.next().collect().iterator();
144          if (metricFamilySamples.hasNext()) {
145            next = metricFamilySamples.next();
146            return;
147          }
148        }
149        next = null;
150      }
151    }
152
153    public Collector.MetricFamilySamples nextElement() {
154      Collector.MetricFamilySamples current = next;
155      if (current == null) {
156        throw new NoSuchElementException();
157      }
158      findNextElement();
159      return current;
160    }
161
162    public boolean hasMoreElements() {
163      return next != null;
164    }
165  }
166
167  /**
168   * Returns the given value, or null if it doesn't exist.
169   * <p>
170   * This is inefficient, and intended only for use in unittests.
171   */
172  public Double getSampleValue(String name) {
173    return getSampleValue(name, new String[]{}, new String[]{});
174  }
175
176  /**
177   * Returns the given value, or null if it doesn't exist.
178   * <p>
179   * This is inefficient, and intended only for use in unittests.
180   */
181  public Double getSampleValue(String name, String[] labelNames, String[] labelValues) {
182    for (Collector.MetricFamilySamples metricFamilySamples: Collections.list(metricFamilySamples())) {
183      for (Collector.MetricFamilySamples.Sample sample: metricFamilySamples.samples) {
184        if (sample.name.equals(name)
185            && Arrays.equals(sample.labelNames.toArray(), labelNames)
186            && Arrays.equals(sample.labelValues.toArray(), labelValues)) {
187          return sample.value;
188        }
189      }
190    }
191    return null;
192  }
193
194}