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}