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