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
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  /**
077   * Unregister all Collectors.
078   */
079  public void clear() {
080    synchronized (collectorsToNames) {
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 (collectorsToNames) {
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        case HISTOGRAM:
113          names.add(family.name + "_count");
114          names.add(family.name + "_sum");
115          names.add(family.name + "_bucket");
116        default:
117          names.add(family.name);
118      }
119    }
120    return names;
121  }
122
123  /**
124   * Enumeration of metrics of all registered collectors.
125   */
126  public Enumeration<Collector.MetricFamilySamples> metricFamilySamples() {
127    return new MetricFamilySamplesEnumeration();
128  }
129
130  public Enumeration<Collector.MetricFamilySamples> filteredMetricFamilySamples(Set<String> includedNames) {
131    return new MetricFamilySamplesEnumeration(includedNames);
132  }
133
134  class MetricFamilySamplesEnumeration implements Enumeration<Collector.MetricFamilySamples> {
135
136    private final Iterator<Collector> collectorIter;
137    private Iterator<Collector.MetricFamilySamples> metricFamilySamples;
138    private Collector.MetricFamilySamples next;
139    private Set<String> includedNames;
140
141    MetricFamilySamplesEnumeration(Set<String> includedNames) {
142      this.includedNames = includedNames;
143      collectorIter = includedCollectorIterator(includedNames);
144      findNextElement();
145    }
146
147    private Iterator<Collector> includedCollectorIterator(Set<String> includedNames) {
148      if (includedNames.isEmpty()) {
149        return collectors().iterator();
150      } else {
151        HashSet<Collector> collectors = new HashSet<Collector>();
152        synchronized (namesToCollectors) {
153          for (Map.Entry<String, Collector> entry : namesToCollectors.entrySet()) {
154            if (includedNames.contains(entry.getKey())) {
155              collectors.add(entry.getValue());
156            }
157          }
158        }
159
160        return collectors.iterator();
161      }
162    }
163
164    MetricFamilySamplesEnumeration() {
165      this(Collections.<String>emptySet());
166    }
167
168    private void findNextElement() {
169      next = null;
170
171      while (metricFamilySamples != null && metricFamilySamples.hasNext()) {
172        next = filter(metricFamilySamples.next());
173        if (next != null) {
174          return;
175        }
176      }
177
178      if (next == null) {
179        while (collectorIter.hasNext()) {
180          metricFamilySamples = collectorIter.next().collect().iterator();
181          while (metricFamilySamples.hasNext()) {
182            next = filter(metricFamilySamples.next());
183            if (next != null) {
184              return;
185            }
186          }
187        }
188      }
189    }
190
191    private Collector.MetricFamilySamples filter(Collector.MetricFamilySamples next) {
192      if (includedNames.isEmpty()) {
193        return next;
194      } else {
195        Iterator<Collector.MetricFamilySamples.Sample> it = next.samples.iterator();
196        while (it.hasNext()) {
197            if (!includedNames.contains(it.next().name)) {
198                it.remove();
199            }
200        }
201        if (next.samples.size() == 0) {
202          return null;
203        }
204        return next;
205      }
206    }
207
208    public Collector.MetricFamilySamples nextElement() {
209      Collector.MetricFamilySamples current = next;
210      if (current == null) {
211        throw new NoSuchElementException();
212      }
213      findNextElement();
214      return current;
215    }
216
217    public boolean hasMoreElements() {
218      return next != null;
219    }
220  }
221
222  /**
223   * Returns the given value, or null if it doesn't exist.
224   * <p>
225   * This is inefficient, and intended only for use in unittests.
226   */
227  public Double getSampleValue(String name) {
228    return getSampleValue(name, new String[]{}, new String[]{});
229  }
230
231  /**
232   * Returns the given value, or null if it doesn't exist.
233   * <p>
234   * This is inefficient, and intended only for use in unittests.
235   */
236  public Double getSampleValue(String name, String[] labelNames, String[] labelValues) {
237    for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(metricFamilySamples())) {
238      for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) {
239        if (sample.name.equals(name)
240                && Arrays.equals(sample.labelNames.toArray(), labelNames)
241                && Arrays.equals(sample.labelValues.toArray(), labelValues)) {
242          return sample.value;
243        }
244      }
245    }
246    return null;
247  }
248
249}