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 CompatibilityRegistry compatibilityRegistry;
57    private final MetricsInfo metricsInfo;
58    private final DefaultMetricsSystemHelper helper = new DefaultMetricsSystemHelper();
59    private final static String[] histogramSuffixes = new String[]{
60        "_num_ops",
61        "_min",
62        "_max",
63        "_median",
64        "_75th_percentile",
65        "_90th_percentile",
66        "_95th_percentile",
67        "_99th_percentile"};
68  
69    /**
70     * Construct the registry with a record name
71     * @param name  of the record of the metrics
72     */
73    public DynamicMetricsRegistry(String name) {
74      this(Interns.info(name,name));
75    }
76  
77    /**
78     * Construct the registry with a metadata object
79     * @param info  the info object for the metrics record/group
80     */
81    public DynamicMetricsRegistry(MetricsInfo info) {
82      metricsInfo = info;
83      compatibilityRegistry = new CompatibilityRegistry();
84    }
85  
86    /**
87     * @return the info object of the metrics registry
88     */
89    public MetricsInfo info() {
90      return metricsInfo;
91    }
92  
93    /**
94     * Get a metric by name
95     * @param name  of the metric
96     * @return the metric object
97     */
98    public MutableMetric get(String name) {
99      return metricsMap.get(name);
100   }
101 
102   /**
103    * Get a tag by name
104    * @param name  of the tag
105    * @return the tag object
106    */
107   public MetricsTag getTag(String name) {
108     return tagsMap.get(name);
109   }
110 
111   /**
112    * Create a mutable long integer counter
113    * @param name  of the metric
114    * @param desc  metric description
115    * @param iVal  initial value
116    * @return a new counter object
117    */
118   public MutableFastCounter newCounter(String name, String desc, long iVal) {
119     return newCounter(new MetricsInfoImpl(name, desc), iVal);
120   }
121 
122   /**
123    * Create a mutable long integer counter
124    * @param info  metadata of the metric
125    * @param iVal  initial value
126    * @return a new counter object
127    */
128   public MutableFastCounter newCounter(MetricsInfo info, long iVal) {
129     MutableFastCounter ret = new MutableFastCounter(info, iVal);
130     return addNewMetricIfAbsent(info.name(), ret, MutableFastCounter.class);
131   }
132 
133   /**
134    * Create a mutable long integer gauge
135    * @param name  of the metric
136    * @param desc  metric description
137    * @param iVal  initial value
138    * @return a new gauge object
139    */
140   public MutableGaugeLong newGauge(String name, String desc, long iVal) {
141     return newGauge(new MetricsInfoImpl(name, desc), iVal);
142   }
143 
144   /**
145    * Create a mutable long integer gauge
146    * @param info  metadata of the metric
147    * @param iVal  initial value
148    * @return a new gauge object
149    */
150   public MutableGaugeLong newGauge(MetricsInfo info, long iVal) {
151     MutableGaugeLong ret = new MutableGaugeLong(info, iVal);
152     return addNewMetricIfAbsent(info.name(), ret, MutableGaugeLong.class);
153   }
154 
155   /**
156    * Create a mutable metric with stats
157    * @param name  of the metric
158    * @param desc  metric description
159    * @param sampleName  of the metric (e.g., "Ops")
160    * @param valueName   of the metric (e.g., "Time" or "Latency")
161    * @param extended    produce extended stat (stdev, min/max etc.) if true.
162    * @return a new mutable stat metric object
163    */
164   public MutableStat newStat(String name, String desc,
165       String sampleName, String valueName, boolean extended) {
166     MutableStat ret =
167         new MutableStat(name, desc, sampleName, valueName, extended);
168     return addNewMetricIfAbsent(name, ret, MutableStat.class);
169   }
170 
171   /**
172    * Create a mutable metric with stats
173    * @param name  of the metric
174    * @param desc  metric description
175    * @param sampleName  of the metric (e.g., "Ops")
176    * @param valueName   of the metric (e.g., "Time" or "Latency")
177    * @return a new mutable metric object
178    */
179   public MutableStat newStat(String name, String desc,
180                              String sampleName, String valueName) {
181     return newStat(name, desc, sampleName, valueName, false);
182   }
183 
184   /**
185    * Create a mutable rate metric
186    * @param name  of the metric
187    * @return a new mutable metric object
188    */
189   public MutableRate newRate(String name) {
190     return newRate(name, name, false);
191   }
192 
193   /**
194    * Create a mutable rate metric
195    * @param name  of the metric
196    * @param description of the metric
197    * @return a new mutable rate metric object
198    */
199   public MutableRate newRate(String name, String description) {
200     return newRate(name, description, false);
201   }
202 
203   /**
204    * Create a mutable rate metric (for throughput measurement)
205    * @param name  of the metric
206    * @param desc  description
207    * @param extended  produce extended stat (stdev/min/max etc.) if true
208    * @return a new mutable rate metric object
209    */
210   public MutableRate newRate(String name, String desc, boolean extended) {
211     return newRate(name, desc, extended, true);
212   }
213 
214   @InterfaceAudience.Private
215   public MutableRate newRate(String name, String desc,
216       boolean extended, boolean returnExisting) {
217     if (returnExisting) {
218       MutableMetric rate = metricsMap.get(name);
219       if (rate != null) {
220         if (rate instanceof MutableRate) return (MutableRate) rate;
221         throw new MetricsException("Unexpected metrics type "+ rate.getClass()
222                                    +" for "+ name);
223       }
224     }
225     MutableRate ret = new MutableRate(name, desc, extended);
226     return addNewMetricIfAbsent(name, ret, MutableRate.class);
227   }
228 
229   /**
230    * Create a new histogram.
231    * @param name Name of the histogram.
232    * @return A new MutableHistogram
233    */
234   public MutableHistogram newHistogram(String name) {
235      return newHistogram(name, "");
236   }
237 
238   /**
239    * Create a new histogram.
240    * @param name The name of the histogram
241    * @param desc The description of the data in the histogram.
242    * @return A new MutableHistogram
243    */
244   public MutableHistogram newHistogram(String name, String desc) {
245     MutableHistogram histo = new MutableHistogram(name, desc);
246     return addNewMetricIfAbsent(name, histo, MutableHistogram.class);
247   }
248   
249   /**
250    * Create a new histogram with time range counts.
251    * @param name Name of the histogram.
252    * @return A new MutableTimeHistogram
253    */
254   public MutableTimeHistogram newTimeHistogram(String name) {
255      return newTimeHistogram(name, "");
256   }
257 
258   /**
259    * Create a new histogram with time range counts.
260    * @param name The name of the histogram
261    * @param desc The description of the data in the histogram.
262    * @return A new MutableTimeHistogram
263    */
264   public MutableTimeHistogram newTimeHistogram(String name, String desc) {
265     MutableTimeHistogram histo = new MutableTimeHistogram(name, desc);
266     return addNewMetricIfAbsent(name, histo, MutableTimeHistogram.class);
267   }
268   
269   /**
270    * Create a new histogram with size range counts.
271    * @param name Name of the histogram.
272    * @return A new MutableSizeHistogram
273    */
274   public MutableSizeHistogram newSizeHistogram(String name) {
275      return newSizeHistogram(name, "");
276   }
277 
278   /**
279    * Create a new histogram with size range counts.
280    * @param name The name of the histogram
281    * @param desc The description of the data in the histogram.
282    * @return A new MutableSizeHistogram
283    */
284   public MutableSizeHistogram newSizeHistogram(String name, String desc) {
285     MutableSizeHistogram histo = new MutableSizeHistogram(name, desc);
286     return addNewMetricIfAbsent(name, histo, MutableSizeHistogram.class);
287   }
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(new MetricsInfoImpl(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     compatibilityRegistry.snapshot(builder, all);
397   }
398 
399   @Override public String toString() {
400     return Objects.toStringHelper(this)
401         .add("info", metricsInfo).add("tags", tags()).add("metrics", metrics())
402         .toString();
403   }
404 
405   /**
406    * Removes metric by name
407    * @param name name of the metric to remove
408    */
409   public void removeMetric(String name) {
410     helper.removeObjectName(name);
411     metricsMap.remove(name);
412   }
413 
414   public void removeHistogramMetrics(String baseName) {
415     for (String suffix:histogramSuffixes) {
416       removeMetric(baseName+suffix);
417     }
418   }
419 
420   /**
421    * Get a MetricMutableGaugeLong from the storage.  If it is not there atomically put it.
422    *
423    * @param gaugeName              name of the gauge to create or get.
424    * @param potentialStartingValue value of the new gauge if we have to create it.
425    */
426   public MutableGaugeLong getGauge(String gaugeName, long potentialStartingValue) {
427     //Try and get the guage.
428     MutableMetric metric = metricsMap.get(gaugeName);
429 
430     //If it's not there then try and put a new one in the storage.
431     if (metric == null) {
432 
433       //Create the potential new gauge.
434       MutableGaugeLong newGauge = new MutableGaugeLong(new MetricsInfoImpl(gaugeName, ""),
435               potentialStartingValue);
436 
437       // Try and put the gauge in.  This is atomic.
438       metric = metricsMap.putIfAbsent(gaugeName, newGauge);
439 
440       //If the value we get back is null then the put was successful and we will return that.
441       //otherwise gaugeLong should contain the thing that was in before the put could be completed.
442       if (metric == null) {
443         return newGauge;
444       }
445     }
446 
447     if (!(metric instanceof MutableGaugeLong)) {
448       throw new MetricsException("Metric already exists in registry for metric name: " + gaugeName +
449               " and not of type MetricMutableGaugeLong");
450     }
451 
452     return (MutableGaugeLong) metric;
453   }
454 
455   /**
456    * Get a MetricMutableCounterLong from the storage.  If it is not there atomically put it.
457    *
458    * @param counterName            Name of the counter to get
459    * @param potentialStartingValue starting value if we have to create a new counter
460    */
461   public MutableFastCounter getCounter(String counterName, long potentialStartingValue) {
462     //See getGauge for description on how this works.
463     MutableMetric counter = metricsMap.get(counterName);
464     if (counter == null) {
465       MutableFastCounter newCounter =
466               new MutableFastCounter(new MetricsInfoImpl(counterName, ""), potentialStartingValue);
467       counter = metricsMap.putIfAbsent(counterName, newCounter);
468       if (counter == null) {
469         return newCounter;
470       }
471     }
472 
473 
474     if (!(counter instanceof MutableCounter)) {
475       throw new MetricsException("Metric already exists in registry for metric name: " +
476               counterName + " and not of type MutableCounter");
477     }
478 
479     return (MutableFastCounter) counter;
480   }
481 
482   public MutableHistogram getHistogram(String histoName) {
483     //See getGauge for description on how this works.
484     MutableMetric histo = metricsMap.get(histoName);
485     if (histo == null) {
486       MutableHistogram newCounter =
487           new MutableHistogram(new MetricsInfoImpl(histoName, ""));
488       histo = metricsMap.putIfAbsent(histoName, newCounter);
489       if (histo == null) {
490         return newCounter;
491       }
492     }
493 
494 
495     if (!(histo instanceof MutableHistogram)) {
496       throw new MetricsException("Metric already exists in registry for metric name: " +
497           histoName + " and not of type MutableHistogram");
498     }
499 
500     return (MutableHistogram) histo;
501   }
502 
503   private<T extends MutableMetric> T
504   addNewMetricIfAbsent(String name,
505                        T ret,
506                        Class<T> metricClass) {
507     //If the value we get back is null then the put was successful and we will
508     // return that. Otherwise metric should contain the thing that was in
509     // before the put could be completed.
510     MutableMetric metric = metricsMap.putIfAbsent(name, ret);
511     if (metric == null) {
512       return ret;
513     }
514 
515     return returnExistingWithCast(metric, metricClass, name);
516   }
517 
518   @SuppressWarnings("unchecked")
519   private<T> T returnExistingWithCast(MutableMetric metric,
520                                       Class<T> metricClass, String name) {
521     if (!metricClass.isAssignableFrom(metric.getClass())) {
522       throw new MetricsException("Metric already exists in registry for metric name: " +
523               name + " and not of type " + metricClass +
524               " but instead of type " + metric.getClass());
525     }
526 
527     return (T) metric;
528   }
529 
530   public void clearMetrics() {
531     for (String name:metricsMap.keySet()) {
532       helper.removeObjectName(name);
533     }
534     metricsMap.clear();
535   }
536 
537   public CompatibilityRegistry getCompatibilityRegistry() {
538     return this.compatibilityRegistry;
539   }
540 
541   /**
542    * Provides compatibility interface for metrics type changes across HBase releases.
543    */
544   public class CompatibilityRegistry {
545 
546     private ConcurrentMap<String, MutableGaugeLong> gaugesMap = Maps.newConcurrentMap();
547 
548     public MutableGaugeLong getGauge(final String gaugeName, long potentialStartingValue) {
549       // Check if it's there already in the main registry
550       MutableMetric metric = metricsMap.get(gaugeName);
551       if (metric != null && metric instanceof MutableGaugeLong) {
552         return (MutableGaugeLong)metric;
553       } else {
554         // If it's not present in the main registry, reference the compatibility registry
555         MutableGaugeLong gauge = gaugesMap.get(gaugeName);
556         if (gauge == null) {
557           MutableGaugeLong newGauge =
558               new MutableGaugeLong(new MetricsInfoImpl(gaugeName, ""), potentialStartingValue);
559           gauge = gaugesMap.putIfAbsent(gaugeName, newGauge);
560 
561           if (gauge == null) {
562             return newGauge;
563           }
564         }
565         return gauge;
566       }
567     }
568 
569     public void snapshot(MetricsRecordBuilder builder, boolean all) {
570       for (MutableGaugeLong gauge: gaugesMap.values()) {
571         gauge.snapshot(builder, all);
572       }
573     }
574   }
575 }