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.Collection;
22  import java.util.concurrent.ConcurrentMap;
23  
24  import com.google.common.base.Objects;
25  import com.google.common.collect.Maps;
26  import org.apache.hadoop.classification.InterfaceAudience;
27  import org.apache.hadoop.classification.InterfaceStability;
28  import org.apache.hadoop.metrics2.MetricsException;
29  import org.apache.hadoop.metrics2.MetricsInfo;
30  import org.apache.hadoop.metrics2.MetricsRecordBuilder;
31  import org.apache.hadoop.metrics2.MetricsTag;
32  import org.apache.hadoop.metrics2.impl.MsInfo;
33  
34  /**
35   * An optional metrics registry class for creating and maintaining a
36   * collection of MetricsMutables, making writing metrics source easier.
37   * NOTE: this is a copy of org.apache.hadoop.metrics2.lib.MetricsRegistry with added one
38   *       feature: metrics can be removed. When HADOOP-8313 is fixed, usages of this class
39   *       should be substituted with org.apache.hadoop.metrics2.lib.MetricsRegistry.
40   *       This implementation also provides handy methods for creating metrics
41   *       dynamically.
42   *       Another difference is that metricsMap implementation is substituted with
43   *       thread-safe map, as we allow dynamic metrics additions/removals.
44   */
45  @InterfaceAudience.Public
46  @InterfaceStability.Evolving
47  public class DynamicMetricsRegistry {
48    private final ConcurrentMap<String, MutableMetric> metricsMap =
49            Maps.newConcurrentMap();
50    private final ConcurrentMap<String, MetricsTag> tagsMap =
51            Maps.newConcurrentMap();
52    private final MetricsInfo metricsInfo;
53  
54    /**
55     * Construct the registry with a record name
56     * @param name  of the record of the metrics
57     */
58    public DynamicMetricsRegistry(String name) {
59      metricsInfo = Interns.info(name, name);
60    }
61  
62    /**
63     * Construct the registry with a metadata object
64     * @param info  the info object for the metrics record/group
65     */
66    public DynamicMetricsRegistry(MetricsInfo info) {
67      metricsInfo = info;
68    }
69  
70    /**
71     * @return the info object of the metrics registry
72     */
73    public MetricsInfo info() {
74      return metricsInfo;
75    }
76  
77    /**
78     * Get a metric by name
79     * @param name  of the metric
80     * @return the metric object
81     */
82    public MutableMetric get(String name) {
83      return metricsMap.get(name);
84    }
85  
86    /**
87     * Get a tag by name
88     * @param name  of the tag
89     * @return the tag object
90     */
91    public MetricsTag getTag(String name) {
92      return tagsMap.get(name);
93    }
94  
95    /**
96     * Create a mutable integer counter
97     * @param name  of the metric
98     * @param desc  metric description
99     * @param iVal  initial value
100    * @return a new counter object
101    */
102   public MutableCounterInt newCounter(String name, String desc, int iVal) {
103     return newCounter(Interns.info(name, desc), iVal);
104   }
105 
106   /**
107    * Create a mutable integer counter
108    * @param info  metadata of the metric
109    * @param iVal  initial value
110    * @return a new counter object
111    */
112   public MutableCounterInt newCounter(MetricsInfo info, int iVal) {
113     MutableCounterInt ret = new MutableCounterInt(info, iVal);
114     return addNewMetricIfAbsent(info.name(), ret, MutableCounterInt.class);
115   }
116 
117   /**
118    * Create a mutable long integer counter
119    * @param name  of the metric
120    * @param desc  metric description
121    * @param iVal  initial value
122    * @return a new counter object
123    */
124   public MutableCounterLong newCounter(String name, String desc, long iVal) {
125     return newCounter(Interns.info(name, desc), iVal);
126   }
127 
128   /**
129    * Create a mutable long integer counter
130    * @param info  metadata of the metric
131    * @param iVal  initial value
132    * @return a new counter object
133    */
134   public MutableCounterLong newCounter(MetricsInfo info, long iVal) {
135     MutableCounterLong ret = new MutableCounterLong(info, iVal);
136     return addNewMetricIfAbsent(info.name(), ret, MutableCounterLong.class);
137   }
138 
139   /**
140    * Create a mutable integer gauge
141    * @param name  of the metric
142    * @param desc  metric description
143    * @param iVal  initial value
144    * @return a new gauge object
145    */
146   public MutableGaugeInt newGauge(String name, String desc, int iVal) {
147     return newGauge(Interns.info(name, desc), iVal);
148   }
149   /**
150    * Create a mutable integer gauge
151    * @param info  metadata of the metric
152    * @param iVal  initial value
153    * @return a new gauge object
154    */
155   public MutableGaugeInt newGauge(MetricsInfo info, int iVal) {
156     MutableGaugeInt ret = new MutableGaugeInt(info, iVal);
157     return addNewMetricIfAbsent(info.name(), ret, MutableGaugeInt.class);
158   }
159 
160   /**
161    * Create a mutable long integer gauge
162    * @param name  of the metric
163    * @param desc  metric description
164    * @param iVal  initial value
165    * @return a new gauge object
166    */
167   public MutableGaugeLong newGauge(String name, String desc, long iVal) {
168     return newGauge(Interns.info(name, desc), iVal);
169   }
170 
171   /**
172    * Create a mutable long integer gauge
173    * @param info  metadata of the metric
174    * @param iVal  initial value
175    * @return a new gauge object
176    */
177   public MutableGaugeLong newGauge(MetricsInfo info, long iVal) {
178     MutableGaugeLong ret = new MutableGaugeLong(info, iVal);
179     return addNewMetricIfAbsent(info.name(), ret, MutableGaugeLong.class);
180   }
181 
182   /**
183    * Create a mutable metric with stats
184    * @param name  of the metric
185    * @param desc  metric description
186    * @param sampleName  of the metric (e.g., "Ops")
187    * @param valueName   of the metric (e.g., "Time" or "Latency")
188    * @param extended    produce extended stat (stdev, min/max etc.) if true.
189    * @return a new mutable stat metric object
190    */
191   public MutableStat newStat(String name, String desc,
192       String sampleName, String valueName, boolean extended) {
193     MutableStat ret =
194         new MutableStat(name, desc, sampleName, valueName, extended);
195     return addNewMetricIfAbsent(name, ret, MutableStat.class);
196   }
197 
198   /**
199    * Create a mutable metric with stats
200    * @param name  of the metric
201    * @param desc  metric description
202    * @param sampleName  of the metric (e.g., "Ops")
203    * @param valueName   of the metric (e.g., "Time" or "Latency")
204    * @return a new mutable metric object
205    */
206   public MutableStat newStat(String name, String desc,
207                              String sampleName, String valueName) {
208     return newStat(name, desc, sampleName, valueName, false);
209   }
210 
211   /**
212    * Create a mutable rate metric
213    * @param name  of the metric
214    * @return a new mutable metric object
215    */
216   public MutableRate newRate(String name) {
217     return newRate(name, name, false);
218   }
219 
220   /**
221    * Create a mutable rate metric
222    * @param name  of the metric
223    * @param description of the metric
224    * @return a new mutable rate metric object
225    */
226   public MutableRate newRate(String name, String description) {
227     return newRate(name, description, false);
228   }
229 
230   /**
231    * Create a mutable rate metric (for throughput measurement)
232    * @param name  of the metric
233    * @param desc  description
234    * @param extended  produce extended stat (stdev/min/max etc.) if true
235    * @return a new mutable rate metric object
236    */
237   public MutableRate newRate(String name, String desc, boolean extended) {
238     return newRate(name, desc, extended, true);
239   }
240 
241   @InterfaceAudience.Private
242   public MutableRate newRate(String name, String desc,
243       boolean extended, boolean returnExisting) {
244     if (returnExisting) {
245       MutableMetric rate = metricsMap.get(name);
246       if (rate != null) {
247         if (rate instanceof MutableRate) return (MutableRate) rate;
248         throw new MetricsException("Unexpected metrics type "+ rate.getClass()
249                                    +" for "+ name);
250       }
251     }
252     MutableRate ret = new MutableRate(name, desc, extended);
253     return addNewMetricIfAbsent(name, ret, MutableRate.class);
254   }
255 
256   /**
257    * Create a new histogram.
258    * @param name Name of the histogram.
259    * @return A new MutableHistogram
260    */
261   public MutableHistogram newHistogram(String name) {
262      return newHistogram(name, "");
263   }
264 
265   /**
266    * Create a new histogram.
267    * @param name The name of the histogram
268    * @param desc The description of the data in the histogram.
269    * @return A new MutableHistogram
270    */
271   public MutableHistogram newHistogram(String name, String desc) {
272     MutableHistogram histo = new MutableHistogram(name, desc);
273     return addNewMetricIfAbsent(name, histo, MutableHistogram.class);
274   }
275 
276   /**
277    * Create a new MutableQuantile(A more accurate histogram).
278    * @param name The name of the histogram
279    * @return a new MutableQuantile
280    */
281   public MetricMutableQuantiles newQuantile(String name) {
282     return newQuantile(name, "");
283   }
284 
285   public MetricMutableQuantiles newQuantile(String name, String desc) {
286     MetricMutableQuantiles histo = new MetricMutableQuantiles(name, desc, "Ops", "", 60);
287     return addNewMetricIfAbsent(name, histo, MetricMutableQuantiles.class);
288   }
289 
290   synchronized void add(String name, MutableMetric metric) {
291     addNewMetricIfAbsent(name, metric, MutableMetric.class);
292   }
293 
294   /**
295    * Add sample to a stat metric by name.
296    * @param name  of the metric
297    * @param value of the snapshot to add
298    */
299   public void add(String name, long value) {
300     MutableMetric m = metricsMap.get(name);
301 
302     if (m != null) {
303       if (m instanceof MutableStat) {
304         ((MutableStat) m).add(value);
305       }
306       else {
307         throw new MetricsException("Unsupported add(value) for metric "+ name);
308       }
309     }
310     else {
311       metricsMap.put(name, newRate(name)); // default is a rate metric
312       add(name, value);
313     }
314   }
315 
316   /**
317    * Set the metrics context tag
318    * @param name of the context
319    * @return the registry itself as a convenience
320    */
321   public DynamicMetricsRegistry setContext(String name) {
322     return tag(MsInfo.Context, name, true);
323   }
324 
325   /**
326    * Add a tag to the metrics
327    * @param name  of the tag
328    * @param description of the tag
329    * @param value of the tag
330    * @return the registry (for keep adding tags)
331    */
332   public DynamicMetricsRegistry tag(String name, String description, String value) {
333     return tag(name, description, value, false);
334   }
335 
336   /**
337    * Add a tag to the metrics
338    * @param name  of the tag
339    * @param description of the tag
340    * @param value of the tag
341    * @param override  existing tag if true
342    * @return the registry (for keep adding tags)
343    */
344   public DynamicMetricsRegistry tag(String name, String description, String value,
345                              boolean override) {
346     return tag(Interns.info(name, description), value, override);
347   }
348 
349   /**
350    * Add a tag to the metrics
351    * @param info  metadata of the tag
352    * @param value of the tag
353    * @param override existing tag if true
354    * @return the registry (for keep adding tags etc.)
355    */
356   public DynamicMetricsRegistry tag(MetricsInfo info, String value, boolean override) {
357     MetricsTag tag = Interns.tag(info, value);
358 
359     if (!override) {
360       MetricsTag existing = tagsMap.putIfAbsent(info.name(), tag);
361       if (existing != null) {
362         throw new MetricsException("Tag "+ info.name() +" already exists!");
363       }
364       return this;
365     }
366 
367     tagsMap.put(info.name(), tag);
368 
369     return this;
370   }
371 
372   public DynamicMetricsRegistry tag(MetricsInfo info, String value) {
373     return tag(info, value, false);
374   }
375 
376   Collection<MetricsTag> tags() {
377     return tagsMap.values();
378   }
379 
380   Collection<MutableMetric> metrics() {
381     return metricsMap.values();
382   }
383 
384   /**
385    * Sample all the mutable metrics and put the snapshot in the builder
386    * @param builder to contain the metrics snapshot
387    * @param all get all the metrics even if the values are not changed.
388    */
389   public void snapshot(MetricsRecordBuilder builder, boolean all) {
390     for (MetricsTag tag : tags()) {
391       builder.add(tag);
392     }
393     for (MutableMetric metric : metrics()) {
394       metric.snapshot(builder, all);
395     }
396   }
397 
398   @Override public String toString() {
399     return Objects.toStringHelper(this)
400         .add("info", metricsInfo).add("tags", tags()).add("metrics", metrics())
401         .toString();
402   }
403 
404   /**
405    * Removes metric by name
406    * @param name name of the metric to remove
407    */
408   public void removeMetric(String name) {
409     metricsMap.remove(name);
410   }
411 
412   /**
413    * Get a MetricMutableGaugeLong from the storage.  If it is not there atomically put it.
414    *
415    * @param gaugeName              name of the gauge to create or get.
416    * @param potentialStartingValue value of the new gauge if we have to create it.
417    */
418   public MutableGaugeLong getLongGauge(String gaugeName, long potentialStartingValue) {
419     //Try and get the guage.
420     MutableMetric metric = metricsMap.get(gaugeName);
421 
422     //If it's not there then try and put a new one in the storage.
423     if (metric == null) {
424 
425       //Create the potential new gauge.
426       MutableGaugeLong newGauge = new MutableGaugeLong(Interns.info(gaugeName, ""),
427               potentialStartingValue);
428 
429       // Try and put the gauge in.  This is atomic.
430       metric = metricsMap.putIfAbsent(gaugeName, newGauge);
431 
432       //If the value we get back is null then the put was successful and we will return that.
433       //otherwise gaugeLong should contain the thing that was in before the put could be completed.
434       if (metric == null) {
435         return newGauge;
436       }
437     }
438 
439     if (!(metric instanceof MutableGaugeLong)) {
440       throw new MetricsException("Metric already exists in registry for metric name: " + gaugeName +
441               " and not of type MetricMutableGaugeLong");
442     }
443 
444     return (MutableGaugeLong) metric;
445   }
446 
447   /**
448    * Get a MetricMutableCounterLong from the storage.  If it is not there atomically put it.
449    *
450    * @param counterName            Name of the counter to get
451    * @param potentialStartingValue starting value if we have to create a new counter
452    */
453   public MutableCounterLong getLongCounter(String counterName, long potentialStartingValue) {
454     //See getLongGauge for description on how this works.
455     MutableMetric counter = metricsMap.get(counterName);
456     if (counter == null) {
457       MutableCounterLong newCounter =
458               new MutableCounterLong(Interns.info(counterName, ""), potentialStartingValue);
459       counter = metricsMap.putIfAbsent(counterName, newCounter);
460       if (counter == null) {
461         return newCounter;
462       }
463     }
464 
465 
466     if (!(counter instanceof MutableCounterLong)) {
467       throw new MetricsException("Metric already exists in registry for metric name: " +
468               counterName + " and not of type MetricMutableCounterLong");
469     }
470 
471     return (MutableCounterLong) counter;
472   }
473 
474   public MutableHistogram getHistogram(String histoName) {
475     //See getLongGauge for description on how this works.
476     MutableMetric histo = metricsMap.get(histoName);
477     if (histo == null) {
478       MutableHistogram newCounter =
479           new MutableHistogram(Interns.info(histoName, ""));
480       histo = metricsMap.putIfAbsent(histoName, newCounter);
481       if (histo == null) {
482         return newCounter;
483       }
484     }
485 
486 
487     if (!(histo instanceof MutableHistogram)) {
488       throw new MetricsException("Metric already exists in registry for metric name: " +
489           histoName + " and not of type MutableHistogram");
490     }
491 
492     return (MutableHistogram) histo;
493   }
494 
495   public MetricMutableQuantiles getQuantile(String histoName) {
496     //See getLongGauge for description on how this works.
497     MutableMetric histo = metricsMap.get(histoName);
498     if (histo == null) {
499       MetricMutableQuantiles newCounter =
500           new MetricMutableQuantiles(histoName, "", "Ops", "", 60);
501       histo = metricsMap.putIfAbsent(histoName, newCounter);
502       if (histo == null) {
503         return newCounter;
504       }
505     }
506 
507 
508     if (!(histo instanceof MetricMutableQuantiles)) {
509       throw new MetricsException("Metric already exists in registry for metric name: " +
510           histoName + " and not of type MutableHistogram");
511     }
512 
513     return (MetricMutableQuantiles) histo;
514   }
515 
516   private<T extends MutableMetric> T
517   addNewMetricIfAbsent(String name,
518                        T ret,
519                        Class<T> metricClass) {
520     //If the value we get back is null then the put was successful and we will
521     // return that. Otherwise metric should contain the thing that was in
522     // before the put could be completed.
523     MutableMetric metric = metricsMap.putIfAbsent(name, ret);
524     if (metric == null) {
525       return ret;
526     }
527 
528     return returnExistingWithCast(metric, metricClass, name);
529   }
530 
531   @SuppressWarnings("unchecked")
532   private<T> T returnExistingWithCast(MutableMetric metric,
533                                       Class<T> metricClass, String name) {
534     if (!metricClass.isAssignableFrom(metric.getClass())) {
535       throw new MetricsException("Metric already exists in registry for metric name: " +
536               name + " and not of type " + metricClass +
537               " but instead of type " + metric.getClass());
538     }
539 
540     return (T) metric;
541   }
542 
543   public void clearMetrics() {
544     metricsMap.clear();
545   }
546 }