View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.metrics2.lib;
20  
21  import java.util.Map.Entry;
22  import java.util.Set;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.ConcurrentMap;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.metrics2.MetricsException;
29  import org.apache.hadoop.metrics2.MetricsRecordBuilder;
30  import org.apache.hadoop.metrics2.MetricsTag;
31  
32  /**
33   * An optional metrics registry class for creating and maintaining a
34   * collection of MetricsMutables, making writing metrics source easier.
35   * NOTE: this is a copy of org.apache.hadoop.metrics2.lib.MetricsRegistry with added one
36   *       feature: metrics can be removed. When HADOOP-8313 is fixed, usages of this class
37   *       should be substituted with org.apache.hadoop.metrics2.lib.MetricsRegistry.
38   *       This implementation also provides handy methods for creating metrics dynamically.
39   *       Another difference is that metricsMap & tagsMap implementation is substituted with
40   *       concurrent map, as we allow dynamic metrics additions/removals.
41   */
42  public class DynamicMetricsRegistry {
43  
44    private final Log LOG = LogFactory.getLog(this.getClass());
45  
46    /** key for the context tag */
47    public static final String CONTEXT_KEY = "context";
48    /** description for the context tag */
49    public static final String CONTEXT_DESC = "Metrics context";
50  
51    private final ConcurrentMap<String, MetricMutable> metricsMap =
52        new ConcurrentHashMap<String, MetricMutable>();
53    private final ConcurrentMap<String, MetricsTag> tagsMap =
54        new ConcurrentHashMap<String, MetricsTag>();
55    private final String name;
56    private final MetricMutableFactory mf;
57  
58    /**
59     * Construct the registry with a record name
60     * @param name  of the record of the metrics
61     */
62    public DynamicMetricsRegistry(String name) {
63      this.name = name;
64      this.mf = new MetricMutableFactory();
65    }
66  
67    /**
68     * Construct the registry with a name and a metric factory
69     * @param name  of the record of the metrics
70     * @param factory for creating new mutable metrics
71     */
72    public DynamicMetricsRegistry(String name, MetricMutableFactory factory) {
73      this.name = name;
74      this.mf = factory;
75    }
76  
77    /**
78     * @return  the name of the metrics registry
79     */
80    public String name() {
81      return name;
82    }
83  
84    /**
85     * Get a metric by name
86     * @param name  of the metric
87     * @return  the metric object
88     */
89    public MetricMutable get(String name) {
90      return metricsMap.get(name);
91    }
92  
93    /**
94     * Create a mutable integer counter
95     * @param name  of the metric
96     * @param description of the metric
97     * @param initValue of the metric
98     * @return  a new counter object
99     */
100   public MetricMutableCounterInt
101   newCounter(String name, String description, int initValue) {
102     MetricMutableCounterInt ret = mf.newCounter(name, description, initValue);
103     return addNewMetricIfAbsent(name, ret, MetricMutableCounterInt.class);
104   }
105 
106   /**
107    * Create a mutable long integer counter
108    * @param name  of the metric
109    * @param description of the metric
110    * @param initValue of the metric
111    * @return  a new counter object
112    */
113   public MetricMutableCounterLong
114   newCounter(String name, String description, long initValue) {
115     MetricMutableCounterLong ret = mf.newCounter(name, description, initValue);
116     return addNewMetricIfAbsent(name, ret, MetricMutableCounterLong.class);
117   }
118 
119   /**
120    * Create a mutable integer gauge
121    * @param name  of the metric
122    * @param description of the metric
123    * @param initValue of the metric
124    * @return  a new gauge object
125    */
126   public MetricMutableGaugeInt
127   newGauge(String name, String description, int initValue) {
128     MetricMutableGaugeInt ret = mf.newGauge(name, description, initValue);
129     return addNewMetricIfAbsent(name, ret, MetricMutableGaugeInt.class);
130   }
131 
132   /**
133    * Create a mutable long integer gauge
134    * @param name  of the metric
135    * @param description of the metric
136    * @param initValue of the metric
137    * @return  a new gauge object
138    */
139   public MetricMutableGaugeLong
140   newGauge(String name, String description, long initValue) {
141     MetricMutableGaugeLong ret = mf.newGauge(name, description, initValue);
142     return addNewMetricIfAbsent(name, ret, MetricMutableGaugeLong.class);
143   }
144 
145   /**
146    * Create a mutable metric with stats
147    * @param name  of the metric
148    * @param description of the metric
149    * @param sampleName  of the metric (e.g., "ops")
150    * @param valueName   of the metric (e.g., "time" or "latency")
151    * @param extended    produce extended stat (stdev, min/max etc.) if true.
152    * @return  a new metric object
153    */
154   public MetricMutableStat newStat(String name, String description,
155                                    String sampleName, String valueName,
156                                    boolean extended) {
157     MetricMutableStat ret =
158         mf.newStat(name, description, sampleName, valueName, extended);
159     return addNewMetricIfAbsent(name, ret, MetricMutableStat.class);
160   }
161 
162   /**
163    * Create a mutable metric with stats
164    * @param name  of the metric
165    * @param description of the metric
166    * @param sampleName  of the metric (e.g., "ops")
167    * @param valueName   of the metric (e.g., "time" or "latency")
168    * @return  a new metric object
169    */
170   public MetricMutableStat newStat(String name, String description,
171                                    String sampleName, String valueName) {
172     return newStat(name, description, sampleName, valueName, false);
173   }
174 
175   /**
176    * Create a mutable metric with stats using the name only
177    * @param name  of the metric
178    * @return a new metric object
179    */
180   public MetricMutableStat newStat(String name) {
181     return newStat(name, "", "ops", "time", false);
182   }
183 
184   /**
185    * Create a new histogram.
186    * @param name Name of the histogram.
187    * @return A new MutableHistogram
188    */
189   public MetricMutableHistogram newHistogram(String name) {
190     return newHistogram(name, "");
191   }
192 
193   /**
194    * Create a new histogram.
195    * @param name The name of the histogram
196    * @param desc The description of the data in the histogram.
197    * @return A new MutableHistogram
198    */
199   public MetricMutableHistogram newHistogram(String name, String desc) {
200     MetricMutableHistogram histo = new MetricMutableHistogram(name, desc);
201     return addNewMetricIfAbsent(name, histo, MetricMutableHistogram.class);
202   }
203 
204   /**
205    * Create a new histogram with time range counts.
206    * @param name Name of the histogram.
207    * @return A new MetricMutableTimeHistogram
208    */
209   public MetricMutableTimeHistogram newTimeHistogram(String name) {
210      return newTimeHistogram(name, "");
211   }
212 
213   /**
214    * Create a new histogram with time range counts.
215    * @param name The name of the histogram
216    * @param desc The description of the data in the histogram.
217    * @return A new MetricMutableTimeHistogram
218    */
219   public MetricMutableTimeHistogram newTimeHistogram(String name, String desc) {
220     MetricMutableTimeHistogram histo = new MetricMutableTimeHistogram(name, desc);
221     return addNewMetricIfAbsent(name, histo, MetricMutableTimeHistogram.class);
222   }
223   
224   /**
225    * Create a new histogram with size range counts.
226    * @param name Name of the histogram.
227    * @return A new MetricMutableSizeHistogram
228    */
229   public MetricMutableSizeHistogram newSizeHistogram(String name) {
230      return newSizeHistogram(name, "");
231   }
232 
233   /**
234    * Create a new histogram with size range counts.
235    * @param name The name of the histogram
236    * @param desc The description of the data in the histogram.
237    * @return A new MetricMutableSizeHistogram
238    */
239   public MetricMutableSizeHistogram newSizeHistogram(String name, String desc) {
240     MetricMutableSizeHistogram histo = new MetricMutableSizeHistogram(name, desc);
241     return addNewMetricIfAbsent(name, histo, MetricMutableSizeHistogram.class);
242   }
243 
244   /**
245    * Create a new MutableQuantile(A more accurate histogram).
246    * @param name The name of the histogram
247    * @return a new MutableQuantile
248    */
249   public MetricMutableQuantiles newQuantile(String name) {
250     return newQuantile(name, "");
251   }
252 
253   /**
254    * Create a new MutableQuantile(A more accurate histogram).
255    * @param name The name of the histogram
256    * @param desc Description of the data.
257    * @return a new MutableQuantile
258    */
259   public MetricMutableQuantiles newQuantile(String name, String desc) {
260     MetricMutableQuantiles histo = new MetricMutableQuantiles(name, desc);
261     return addNewMetricIfAbsent(name, histo, MetricMutableQuantiles.class);
262   }
263 
264   /**
265    * Set the metrics context tag
266    * @param name of the context
267    * @return the registry itself as a convenience
268    */
269   public DynamicMetricsRegistry setContext(String name) {
270     return tag(CONTEXT_KEY, CONTEXT_DESC, name);
271   }
272 
273   /**
274    * Add a tag to the metrics
275    * @param name  of the tag
276    * @param description of the tag
277    * @param value of the tag
278    * @return  the registry (for keep adding tags)
279    */
280   public DynamicMetricsRegistry tag(String name, String description, String value) {
281     return tag(name, description, value, false);
282   }
283 
284   /**
285    * Add a tag to the metrics
286    * @param name  of the tag
287    * @param description of the tag
288    * @param value of the tag
289    * @param override  existing tag if true
290    * @return  the registry (for keep adding tags)
291    */
292   public DynamicMetricsRegistry tag(String name, String description, String value,
293                              boolean override) {
294     MetricsTag tag = new MetricsTag(name, description, value);
295 
296     if (!override) {
297       MetricsTag existing = tagsMap.putIfAbsent(name, tag);
298       if (existing != null) {
299         throw new MetricsException("Tag "+ name +" already exists!");
300       }
301       return this;
302     }
303 
304     tagsMap.put(name, tag);
305 
306     return this;
307   }
308 
309   /**
310    * Get the tags
311    * @return  the tags set
312    */
313   public Set<Entry<String, MetricsTag>> tags() {
314     return tagsMap.entrySet();
315   }
316 
317   /**
318    * Get the metrics
319    * @return  the metrics set
320    */
321   public Set<Entry<String, MetricMutable>> metrics() {
322     return metricsMap.entrySet();
323   }
324 
325   /**
326    * Sample all the mutable metrics and put the snapshot in the builder
327    * @param builder to contain the metrics snapshot
328    * @param all get all the metrics even if the values are not changed.
329    */
330   public void snapshot(MetricsRecordBuilder builder, boolean all) {
331 
332     for (Entry<String, MetricsTag> entry : tags()) {
333       builder.add(entry.getValue());
334     }
335     for (Entry<String, MetricMutable> entry : metrics()) {
336       entry.getValue().snapshot(builder, all);
337     }
338   }
339 
340   /**
341    * Removes metric by name
342    * @param name name of the metric to remove
343    */
344   public void removeMetric(String name) {
345     metricsMap.remove(name);
346   }
347 
348   /**
349    * Get a MetricMutableGaugeLong from the storage.  If it is not there
350    * atomically put it.
351    *
352    * @param gaugeName              name of the gauge to create or get.
353    * @param potentialStartingValue value of the new counter if we have to create it.
354    * @return a metric object
355    */
356   public MetricMutableGaugeLong getLongGauge(String gaugeName,
357                                              long potentialStartingValue) {
358     //Try and get the guage.
359     MetricMutable metric = metricsMap.get(gaugeName);
360 
361     //If it's not there then try and put a new one in the storage.
362     if (metric == null) {
363 
364       //Create the potential new gauge.
365       MetricMutableGaugeLong newGauge = mf.newGauge(gaugeName, "",
366               potentialStartingValue);
367 
368         // Try and put the gauge in.  This is atomic.
369       metric = metricsMap.putIfAbsent(gaugeName, newGauge);
370 
371       //If the value we get back is null then the put was successful and we will
372       // return that. Otherwise gaugeLong should contain the thing that was in
373       // before the put could be completed.
374       if (metric == null) {
375         return newGauge;
376       }
377     }
378 
379     if (!(metric instanceof MetricMutableGaugeLong)) {
380       throw new MetricsException("Metric already exists in registry for metric name: " +
381               name + " and not of type MetricMutableGaugeLong");
382     }
383 
384     return (MetricMutableGaugeLong) metric;
385   }
386 
387   /**
388    * Get a MetricMutableCounterLong from the storage.  If it is not there
389    * atomically put it.
390    *
391    * @param counterName            Name of the counter to get
392    * @param potentialStartingValue starting value if we have to create a new counter
393    * @return a metric object
394    */
395   public MetricMutableCounterLong getLongCounter(String counterName,
396                                                  long potentialStartingValue) {
397     //See getLongGauge for description on how this works.
398     MetricMutable counter = metricsMap.get(counterName);
399     if (counter == null) {
400       MetricMutableCounterLong newCounter =
401               mf.newCounter(counterName, "", potentialStartingValue);
402       counter = metricsMap.putIfAbsent(counterName, newCounter);
403       if (counter == null) {
404         return newCounter;
405       }
406     }
407 
408     if (!(counter instanceof MetricMutableCounterLong)) {
409       throw new MetricsException("Metric already exists in registry for metric name: " +
410               name + "and not of type MetricMutableCounterLong");
411     }
412 
413     return (MetricMutableCounterLong) counter;
414   }
415 
416   public MetricMutableHistogram getHistogram(String histoName) {
417     //See getLongGauge for description on how this works.
418     MetricMutable histo = metricsMap.get(histoName);
419     if (histo == null) {
420       MetricMutableHistogram newHisto =
421           new MetricMutableHistogram(histoName, "");
422       histo = metricsMap.putIfAbsent(histoName, newHisto);
423       if (histo == null) {
424         return newHisto;
425       }
426     }
427 
428     if (!(histo instanceof MetricMutableHistogram)) {
429       throw new MetricsException("Metric already exists in registry for metric name: " +
430           name + "and not of type MetricMutableHistogram");
431     }
432 
433     return (MetricMutableHistogram) histo;
434   }
435 
436   public MetricMutableQuantiles getQuantile(String histoName) {
437     //See getLongGauge for description on how this works.
438     MetricMutable histo = metricsMap.get(histoName);
439     if (histo == null) {
440       MetricMutableQuantiles newHisto =
441           new MetricMutableQuantiles(histoName, "");
442       histo = metricsMap.putIfAbsent(histoName, newHisto);
443       if (histo == null) {
444         return newHisto;
445       }
446     }
447 
448     if (!(histo instanceof MetricMutableQuantiles)) {
449       throw new MetricsException("Metric already exists in registry for metric name: " +
450           name + "and not of type MetricMutableQuantiles");
451     }
452 
453     return (MetricMutableQuantiles) histo;
454   }
455 
456   private<T extends MetricMutable> T
457   addNewMetricIfAbsent(String name,
458                        T ret,
459                        Class<T> metricClass) {
460     //If the value we get back is null then the put was successful and we will
461     // return that. Otherwise metric should contain the thing that was in
462     // before the put could be completed.
463     MetricMutable metric = metricsMap.putIfAbsent(name, ret);
464     if (metric == null) {
465       return ret;
466     }
467 
468     return returnExistingWithCast(metric, metricClass, name);
469   }
470 
471   private<T> T returnExistingWithCast(MetricMutable metric,
472                                       Class<T> metricClass, String name) {
473     if (!metricClass.isAssignableFrom(metric.getClass())) {
474       throw new MetricsException("Metric already exists in registry for metric name: " +
475               name + " and not of type " + metricClass);
476     }
477 
478     return (T) metric;
479   }
480 
481   public void clearMetrics() {
482     metricsMap.clear();
483   }
484 }