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