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}