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