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 COUNTER:
109          names.add(family.name + "_total");
110          names.add(family.name + "_created");
111          names.add(family.name);
112          break;
113        case SUMMARY:
114          names.add(family.name + "_count");
115          names.add(family.name + "_sum");
116          names.add(family.name + "_created");
117          names.add(family.name);
118          break;
119        case HISTOGRAM:
120          names.add(family.name + "_count");
121          names.add(family.name + "_sum");
122          names.add(family.name + "_bucket");
123          names.add(family.name + "_created");
124          names.add(family.name);
125          break;
126        case GAUGE_HISTOGRAM:
127          names.add(family.name + "_gcount");
128          names.add(family.name + "_gsum");
129          names.add(family.name + "_bucket");
130          names.add(family.name);
131          break;
132        case INFO:
133          names.add(family.name + "_info");
134          names.add(family.name);
135          break;
136        default:
137          names.add(family.name);
138      }
139    }
140    return names;
141  }
142
143  /**
144   * Enumeration of metrics of all registered collectors.
145   */
146  public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
147    return new MetricFamilySamplesEnumeration();
148  }
149
150  /**
151   * Enumeration of metrics matching the specified names.
152   * <p>
153   * Note that the provided set of names will be matched against the time series
154   * name and not the metric name. For instance, to retrieve all samples from a
155   * histogram, you must include the '_count', '_sum' and '_bucket' names.
156   */
157  public Enumeration<Collector.MetricFamilySamples> filteredMetricFamilySamples(Set<String> includedNames) {
158    return new MetricFamilySamplesEnumeration(includedNames);
159  }
160
161  class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
162
163    private final Iterator<Collector> collectorIter;
164    private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
165    private Collector.MetricFamilySamples next;
166    private Set<String> includedNames;
167
168    MetricFamilySamplesEnumeration(Set<String> includedNames) {
169      this.includedNames = includedNames;
170      collectorIter = includedCollectorIterator(includedNames);
171      findNextElement();
172    }
173
174    private Iterator<Collector> includedCollectorIterator(Set<String> includedNames) {
175      if (includedNames.isEmpty()) {
176        return collectors().iterator();
177      } else {
178        HashSet<Collector> collectors = new HashSet<Collector>();
179        synchronized (namesCollectorsLock) {
180          for (Map.Entry<String, Collector> entry : namesToCollectors.entrySet()) {
181            if (includedNames.contains(entry.getKey())) {
182              collectors.add(entry.getValue());
183            }
184          }
185        }
186
187        return collectors.iterator();
188      }
189    }
190
191    MetricFamilySamplesEnumeration() {
192      this(Collections.<String>emptySet());
193    }
194
195    private void findNextElement() {
196      next = null;
197
198      while (metricFamilySamples != null && metricFamilySamples.hasNext()) {
199        next = filter(metricFamilySamples.next());
200        if (next != null) {
201          return;
202        }
203      }
204
205      if (next == null) {
206        while (collectorIter.hasNext()) {
207          metricFamilySamples = collectorIter.next().collect().iterator();
208          while (metricFamilySamples.hasNext()) {
209            next = filter(metricFamilySamples.next());
210            if (next != null) {
211              return;
212            }
213          }
214        }
215      }
216    }
217
218    private Collector.MetricFamilySamples filter(Collector.MetricFamilySamples next) {
219      if (includedNames.isEmpty()) {
220        return next;
221      } else {
222        Iterator<Collector.MetricFamilySamples.Sample> it = next.samples.iterator();
223        while (it.hasNext()) {
224            if (!includedNames.contains(it.next().name)) {
225                it.remove();
226            }
227        }
228        if (next.samples.size() == 0) {
229          return null;
230        }
231        return next;
232      }
233    }
234
235    public Collector.MetricFamilySamples nextElement() {
236      Collector.MetricFamilySamples current = next;
237      if (current == null) {
238        throw new NoSuchElementException();
239      }
240      findNextElement();
241      return current;
242    }
243
244    public boolean hasMoreElements() {
245      return next != null;
246    }
247  }
248
249  /**
250   * Returns the given value, or null if it doesn't exist.
251   * <p>
252   * This is inefficient, and intended only for use in unittests.
253   */
254  public Double getSampleValue(String name) {
255    return getSampleValue(name, new String[]{}, new String[]{});
256  }
257
258  /**
259   * Returns the given value, or null if it doesn't exist.
260   * <p>
261   * This is inefficient, and intended only for use in unittests.
262   */
263  public Double getSampleValue(String name, String[] labelNames, String[] labelValues) {
264    for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(metricFamilySamples())) {
265      for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) {
266        if (sample.name.equals(name)
267                && Arrays.equals(sample.labelNames.toArray(), labelNames)
268                && Arrays.equals(sample.labelValues.toArray(), labelValues)) {
269          return sample.value;
270        }
271      }
272    }
273    return null;
274  }
275
276}