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.List;
011import java.util.Map;
012import java.util.NoSuchElementException;
013import java.util.Set;
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  private final Object namesCollectorsLock = new Object();
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 (namesCollectorsLock) {
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 (namesCollectorsLock) {
069      List<String> names = collectorsToNames.remove(m);
070      for (String name : names) {
071        namesToCollectors.remove(name);
072      }
073    }
074  }
075
076  /**
077   * Unregister all Collectors.
078   */
079  public void clear() {
080    synchronized (namesCollectorsLock) {
081      collectorsToNames.clear();
082      namesToCollectors.clear();
083    }
084  }
085
086  /**
087   * A snapshot of the current collectors.
088   */
089  private Set<Collector> collectors() {
090    synchronized (namesCollectorsLock) {
091      return new HashSet<Collector>(collectorsToNames.keySet());
092    }
093  }
094
095  private List<String> collectorNames(Collector m) {
096    List<Collector.MetricFamilySamples> mfs;
097    if (m instanceof Collector.Describable) {
098      mfs = ((Collector.Describable) m).describe();
099    } else if (autoDescribe) {
100      mfs = m.collect();
101    } else {
102      mfs = Collections.emptyList();
103    }
104
105    List<String> names = new ArrayList<String>();
106    for (Collector.MetricFamilySamples family : mfs) {
107      switch (family.type) {
108        case SUMMARY:
109          names.add(family.name + "_count");
110          names.add(family.name + "_sum");
111          names.add(family.name);
112          break;
113        case HISTOGRAM:
114          names.add(family.name + "_count");
115          names.add(family.name + "_sum");
116          names.add(family.name + "_bucket");
117          names.add(family.name);
118          break;
119        default:
120          names.add(family.name);
121      }
122    }
123    return names;
124  }
125
126  /**
127   * Enumeration of metrics of all registered collectors.
128   */
129  public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
130    return new MetricFamilySamplesEnumeration();
131  }
132
133  /**
134   * Enumeration of metrics matching the specified names.
135   * <p>
136   * Note that the provided set of names will be matched against the time series
137   * name and not the metric name. For instance, to retrieve all samples from a
138   * histogram, you must include the '_count', '_sum' and '_bucket' names.
139   */
140  public Enumeration<Collector.MetricFamilySamples> filteredMetricFamilySamples(Set<String> includedNames) {
141    return new MetricFamilySamplesEnumeration(includedNames);
142  }
143
144  class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
145
146    private final Iterator<Collector> collectorIter;
147    private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
148    private Collector.MetricFamilySamples next;
149    private Set<String> includedNames;
150
151    MetricFamilySamplesEnumeration(Set<String> includedNames) {
152      this.includedNames = includedNames;
153      collectorIter = includedCollectorIterator(includedNames);
154      findNextElement();
155    }
156
157    private Iterator<Collector> includedCollectorIterator(Set<String> includedNames) {
158      if (includedNames.isEmpty()) {
159        return collectors().iterator();
160      } else {
161        HashSet<Collector> collectors = new HashSet<Collector>();
162        synchronized (namesCollectorsLock) {
163          for (Map.Entry<String, Collector> entry : namesToCollectors.entrySet()) {
164            if (includedNames.contains(entry.getKey())) {
165              collectors.add(entry.getValue());
166            }
167          }
168        }
169
170        return collectors.iterator();
171      }
172    }
173
174    MetricFamilySamplesEnumeration() {
175      this(Collections.<String>emptySet());
176    }
177
178    private void findNextElement() {
179      next = null;
180
181      while (metricFamilySamples != null && metricFamilySamples.hasNext()) {
182        next = filter(metricFamilySamples.next());
183        if (next != null) {
184          return;
185        }
186      }
187
188      if (next == null) {
189        while (collectorIter.hasNext()) {
190          metricFamilySamples = collectorIter.next().collect().iterator();
191          while (metricFamilySamples.hasNext()) {
192            next = filter(metricFamilySamples.next());
193            if (next != null) {
194              return;
195            }
196          }
197        }
198      }
199    }
200
201    private Collector.MetricFamilySamples filter(Collector.MetricFamilySamples next) {
202      if (includedNames.isEmpty()) {
203        return next;
204      } else {
205        Iterator<Collector.MetricFamilySamples.Sample> it = next.samples.iterator();
206        while (it.hasNext()) {
207            if (!includedNames.contains(it.next().name)) {
208                it.remove();
209            }
210        }
211        if (next.samples.size() == 0) {
212          return null;
213        }
214        return next;
215      }
216    }
217
218    public Collector.MetricFamilySamples nextElement() {
219      Collector.MetricFamilySamples current = next;
220      if (current == null) {
221        throw new NoSuchElementException();
222      }
223      findNextElement();
224      return current;
225    }
226
227    public boolean hasMoreElements() {
228      return next != null;
229    }
230  }
231
232  /**
233   * Returns the given value, or null if it doesn't exist.
234   * <p>
235   * This is inefficient, and intended only for use in unittests.
236   */
237  public Double getSampleValue(String name) {
238    return getSampleValue(name, new String[]{}, new String[]{});
239  }
240
241  /**
242   * Returns the given value, or null if it doesn't exist.
243   * <p>
244   * This is inefficient, and intended only for use in unittests.
245   */
246  public Double getSampleValue(String name, String[] labelNames, String[] labelValues) {
247    for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(metricFamilySamples())) {
248      for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) {
249        if (sample.name.equals(name)
250                && Arrays.equals(sample.labelNames.toArray(), labelNames)
251                && Arrays.equals(sample.labelValues.toArray(), labelValues)) {
252          return sample.value;
253        }
254      }
255    }
256    return null;
257  }
258
259}