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 MutableQuantile(A more accurate histogram).
206    * @param name The name of the histogram
207    * @return a new MutableQuantile
208    */
209   public MetricMutableQuantiles newQuantile(String name) {
210     return newQuantile(name, "");
211   }
212 
213   /**
214    * Create a new MutableQuantile(A more accurate histogram).
215    * @param name The name of the histogram
216    * @param desc Description of the data.
217    * @return a new MutableQuantile
218    */
219   public MetricMutableQuantiles newQuantile(String name, String desc) {
220     MetricMutableQuantiles histo = new MetricMutableQuantiles(name, desc);
221     return addNewMetricIfAbsent(name, histo, MetricMutableQuantiles.class);
222   }
223 
224   /**
225    * Set the metrics context tag
226    * @param name of the context
227    * @return the registry itself as a convenience
228    */
229   public DynamicMetricsRegistry setContext(String name) {
230     return tag(CONTEXT_KEY, CONTEXT_DESC, name);
231   }
232 
233   /**
234    * Add a tag to the metrics
235    * @param name  of the tag
236    * @param description of the tag
237    * @param value of the tag
238    * @return  the registry (for keep adding tags)
239    */
240   public DynamicMetricsRegistry tag(String name, String description, String value) {
241     return tag(name, description, value, false);
242   }
243 
244   /**
245    * Add a tag to the metrics
246    * @param name  of the tag
247    * @param description of the tag
248    * @param value of the tag
249    * @param override  existing tag if true
250    * @return  the registry (for keep adding tags)
251    */
252   public DynamicMetricsRegistry tag(String name, String description, String value,
253                              boolean override) {
254     MetricsTag tag = new MetricsTag(name, description, value);
255 
256     if (!override) {
257       MetricsTag existing = tagsMap.putIfAbsent(name, tag);
258       if (existing != null) {
259         throw new MetricsException("Tag "+ name +" already exists!");
260       }
261       return this;
262     }
263 
264     tagsMap.put(name, tag);
265 
266     return this;
267   }
268 
269   /**
270    * Get the tags
271    * @return  the tags set
272    */
273   public Set<Entry<String, MetricsTag>> tags() {
274     return tagsMap.entrySet();
275   }
276 
277   /**
278    * Get the metrics
279    * @return  the metrics set
280    */
281   public Set<Entry<String, MetricMutable>> metrics() {
282     return metricsMap.entrySet();
283   }
284 
285   /**
286    * Sample all the mutable metrics and put the snapshot in the builder
287    * @param builder to contain the metrics snapshot
288    * @param all get all the metrics even if the values are not changed.
289    */
290   public void snapshot(MetricsRecordBuilder builder, boolean all) {
291 
292     for (Entry<String, MetricsTag> entry : tags()) {
293       builder.add(entry.getValue());
294     }
295     for (Entry<String, MetricMutable> entry : metrics()) {
296       entry.getValue().snapshot(builder, all);
297     }
298   }
299 
300   /**
301    * Removes metric by name
302    * @param name name of the metric to remove
303    */
304   public void removeMetric(String name) {
305     metricsMap.remove(name);
306   }
307 
308   /**
309    * Get a MetricMutableGaugeLong from the storage.  If it is not there
310    * atomically put it.
311    *
312    * @param gaugeName              name of the gauge to create or get.
313    * @param potentialStartingValue value of the new counter if we have to create it.
314    * @return a metric object
315    */
316   public MetricMutableGaugeLong getLongGauge(String gaugeName,
317                                              long potentialStartingValue) {
318     //Try and get the guage.
319     MetricMutable metric = metricsMap.get(gaugeName);
320 
321     //If it's not there then try and put a new one in the storage.
322     if (metric == null) {
323 
324       //Create the potential new gauge.
325       MetricMutableGaugeLong newGauge = mf.newGauge(gaugeName, "",
326               potentialStartingValue);
327 
328         // Try and put the gauge in.  This is atomic.
329       metric = metricsMap.putIfAbsent(gaugeName, newGauge);
330 
331       //If the value we get back is null then the put was successful and we will
332       // return that. Otherwise gaugeLong should contain the thing that was in
333       // before the put could be completed.
334       if (metric == null) {
335         return newGauge;
336       }
337     }
338 
339     if (!(metric instanceof MetricMutableGaugeLong)) {
340       throw new MetricsException("Metric already exists in registry for metric name: " +
341               name + " and not of type MetricMutableGaugeLong");
342     }
343 
344     return (MetricMutableGaugeLong) metric;
345   }
346 
347   /**
348    * Get a MetricMutableCounterLong from the storage.  If it is not there
349    * atomically put it.
350    *
351    * @param counterName            Name of the counter to get
352    * @param potentialStartingValue starting value if we have to create a new counter
353    * @return a metric object
354    */
355   public MetricMutableCounterLong getLongCounter(String counterName,
356                                                  long potentialStartingValue) {
357     //See getLongGauge for description on how this works.
358     MetricMutable counter = metricsMap.get(counterName);
359     if (counter == null) {
360       MetricMutableCounterLong newCounter =
361               mf.newCounter(counterName, "", potentialStartingValue);
362       counter = metricsMap.putIfAbsent(counterName, newCounter);
363       if (counter == null) {
364         return newCounter;
365       }
366     }
367 
368     if (!(counter instanceof MetricMutableCounterLong)) {
369       throw new MetricsException("Metric already exists in registry for metric name: " +
370               name + "and not of type MetricMutableCounterLong");
371     }
372 
373     return (MetricMutableCounterLong) counter;
374   }
375 
376   public MetricMutableHistogram getHistogram(String histoName) {
377     //See getLongGauge for description on how this works.
378     MetricMutable histo = metricsMap.get(histoName);
379     if (histo == null) {
380       MetricMutableHistogram newHisto =
381           new MetricMutableHistogram(histoName, "");
382       histo = metricsMap.putIfAbsent(histoName, newHisto);
383       if (histo == null) {
384         return newHisto;
385       }
386     }
387 
388     if (!(histo instanceof MetricMutableHistogram)) {
389       throw new MetricsException("Metric already exists in registry for metric name: " +
390           name + "and not of type MetricMutableHistogram");
391     }
392 
393     return (MetricMutableHistogram) histo;
394   }
395 
396   public MetricMutableQuantiles getQuantile(String histoName) {
397     //See getLongGauge for description on how this works.
398     MetricMutable histo = metricsMap.get(histoName);
399     if (histo == null) {
400       MetricMutableQuantiles newHisto =
401           new MetricMutableQuantiles(histoName, "");
402       histo = metricsMap.putIfAbsent(histoName, newHisto);
403       if (histo == null) {
404         return newHisto;
405       }
406     }
407 
408     if (!(histo instanceof MetricMutableQuantiles)) {
409       throw new MetricsException("Metric already exists in registry for metric name: " +
410           name + "and not of type MetricMutableQuantiles");
411     }
412 
413     return (MetricMutableQuantiles) histo;
414   }
415 
416   private<T extends MetricMutable> T
417   addNewMetricIfAbsent(String name,
418                        T ret,
419                        Class<T> metricClass) {
420     //If the value we get back is null then the put was successful and we will
421     // return that. Otherwise metric should contain the thing that was in
422     // before the put could be completed.
423     MetricMutable metric = metricsMap.putIfAbsent(name, ret);
424     if (metric == null) {
425       return ret;
426     }
427 
428     return returnExistingWithCast(metric, metricClass, name);
429   }
430 
431   private<T> T returnExistingWithCast(MetricMutable metric,
432                                       Class<T> metricClass, String name) {
433     if (!metricClass.isAssignableFrom(metric.getClass())) {
434       throw new MetricsException("Metric already exists in registry for metric name: " +
435               name + " and not of type " + metricClass);
436     }
437 
438     return (T) metric;
439   }
440 
441   public void clearMetrics() {
442     metricsMap.clear();
443   }
444 }