001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.metrics2.lib;
020
021import java.util.Collection;
022import java.util.concurrent.ConcurrentMap;
023
024import org.apache.hadoop.hbase.metrics.Interns;
025import org.apache.hadoop.metrics2.MetricsException;
026import org.apache.hadoop.metrics2.MetricsInfo;
027import org.apache.hadoop.metrics2.MetricsRecordBuilder;
028import org.apache.hadoop.metrics2.MetricsTag;
029import org.apache.hadoop.metrics2.impl.MsInfo;
030import org.apache.yetus.audience.InterfaceAudience;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import org.apache.hbase.thirdparty.com.google.common.base.MoreObjects;
035import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
036
037/**
038 * An optional metrics registry class for creating and maintaining a
039 * collection of MetricsMutables, making writing metrics source easier.
040 * NOTE: this is a copy of org.apache.hadoop.metrics2.lib.MetricsRegistry with added one
041 *       feature: metrics can be removed. When HADOOP-8313 is fixed, usages of this class
042 *       should be substituted with org.apache.hadoop.metrics2.lib.MetricsRegistry.
043 *       This implementation also provides handy methods for creating metrics
044 *       dynamically.
045 *       Another difference is that metricsMap implementation is substituted with
046 *       thread-safe map, as we allow dynamic metrics additions/removals.
047 */
048@InterfaceAudience.Private
049public class DynamicMetricsRegistry {
050  private static final Logger LOG = LoggerFactory.getLogger(DynamicMetricsRegistry.class);
051
052  private final ConcurrentMap<String, MutableMetric> metricsMap =
053      Maps.newConcurrentMap();
054  private final ConcurrentMap<String, MetricsTag> tagsMap =
055      Maps.newConcurrentMap();
056  private final MetricsInfo metricsInfo;
057  private final DefaultMetricsSystemHelper helper = new DefaultMetricsSystemHelper();
058  private final static String[] histogramSuffixes = new String[]{
059    "_num_ops",
060    "_min",
061    "_max",
062    "_median",
063    "_75th_percentile",
064    "_90th_percentile",
065    "_95th_percentile",
066    "_99th_percentile"};
067
068  /**
069   * Construct the registry with a record name
070   * @param name  of the record of the metrics
071   */
072  public DynamicMetricsRegistry(String name) {
073    this(Interns.info(name,name));
074  }
075
076  /**
077   * Construct the registry with a metadata object
078   * @param info  the info object for the metrics record/group
079   */
080  public DynamicMetricsRegistry(MetricsInfo info) {
081    metricsInfo = info;
082  }
083
084  /**
085   * @return the info object of the metrics registry
086   */
087  public MetricsInfo info() {
088    return metricsInfo;
089  }
090
091  /**
092   * Get a metric by name
093   * @param name  of the metric
094   * @return the metric object
095   */
096  public MutableMetric get(String name) {
097    return metricsMap.get(name);
098  }
099
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) {
219          return (MutableRate) rate;
220        }
221
222        throw new MetricsException("Unexpected metrics type "+ rate.getClass()
223                                   +" for "+ name);
224      }
225    }
226    MutableRate ret = new MutableRate(name, desc, extended);
227    return addNewMetricIfAbsent(name, ret, MutableRate.class);
228  }
229
230  /**
231   * Create a new histogram.
232   * @param name Name of the histogram.
233   * @return A new MutableHistogram
234   */
235  public MutableHistogram newHistogram(String name) {
236    return newHistogram(name, "");
237  }
238
239  /**
240   * Create a new histogram.
241   * @param name The name of the histogram
242   * @param desc The description of the data in the histogram.
243   * @return A new MutableHistogram
244   */
245  public MutableHistogram newHistogram(String name, String desc) {
246    MutableHistogram histo = new MutableHistogram(name, desc);
247    return addNewMetricIfAbsent(name, histo, MutableHistogram.class);
248  }
249  
250  /**
251   * Create a new histogram with time range counts.
252   * @param name Name of the histogram.
253   * @return A new MutableTimeHistogram
254   */
255  public MutableTimeHistogram newTimeHistogram(String name) {
256    return newTimeHistogram(name, "");
257  }
258
259  /**
260   * Create a new histogram with time range counts.
261   * @param name The name of the histogram
262   * @param desc The description of the data in the histogram.
263   * @return A new MutableTimeHistogram
264   */
265  public MutableTimeHistogram newTimeHistogram(String name, String desc) {
266    MutableTimeHistogram histo = new MutableTimeHistogram(name, desc);
267    return addNewMetricIfAbsent(name, histo, MutableTimeHistogram.class);
268  }
269  
270  /**
271   * Create a new histogram with size range counts.
272   * @param name Name of the histogram.
273   * @return A new MutableSizeHistogram
274   */
275  public MutableSizeHistogram newSizeHistogram(String name) {
276    return newSizeHistogram(name, "");
277  }
278
279  /**
280   * Create a new histogram with size range counts.
281   * @param name The name of the histogram
282   * @param desc The description of the data in the histogram.
283   * @return A new MutableSizeHistogram
284   */
285  public MutableSizeHistogram newSizeHistogram(String name, String desc) {
286    MutableSizeHistogram histo = new MutableSizeHistogram(name, desc);
287    return addNewMetricIfAbsent(name, histo, MutableSizeHistogram.class);
288  }
289
290
291  synchronized void add(String name, MutableMetric metric) {
292    addNewMetricIfAbsent(name, metric, MutableMetric.class);
293  }
294
295  /**
296   * Add sample to a stat metric by name.
297   * @param name  of the metric
298   * @param value of the snapshot to add
299   */
300  public void add(String name, long value) {
301    MutableMetric m = metricsMap.get(name);
302
303    if (m != null) {
304      if (m instanceof MutableStat) {
305        ((MutableStat) m).add(value);
306      }
307      else {
308        throw new MetricsException("Unsupported add(value) for metric "+ name);
309      }
310    }
311    else {
312      metricsMap.put(name, newRate(name)); // default is a rate metric
313      add(name, value);
314    }
315  }
316
317  /**
318   * Set the metrics context tag
319   * @param name of the context
320   * @return the registry itself as a convenience
321   */
322  public DynamicMetricsRegistry setContext(String name) {
323    return tag(MsInfo.Context, name, true);
324  }
325
326  /**
327   * Add a tag to the metrics
328   * @param name  of the tag
329   * @param description of the tag
330   * @param value of the tag
331   * @return the registry (for keep adding tags)
332   */
333  public DynamicMetricsRegistry tag(String name, String description, String value) {
334    return tag(name, description, value, false);
335  }
336
337  /**
338   * Add a tag to the metrics
339   * @param name  of the tag
340   * @param description of the tag
341   * @param value of the tag
342   * @param override  existing tag if true
343   * @return the registry (for keep adding tags)
344   */
345  public DynamicMetricsRegistry tag(String name, String description, String value,
346                             boolean override) {
347    return tag(new MetricsInfoImpl(name, description), value, override);
348  }
349
350  /**
351   * Add a tag to the metrics
352   * @param info  metadata of the tag
353   * @param value of the tag
354   * @param override existing tag if true
355   * @return the registry (for keep adding tags etc.)
356   */
357  public DynamicMetricsRegistry tag(MetricsInfo info, String value, boolean override) {
358    MetricsTag tag = Interns.tag(info, value);
359
360    if (!override) {
361      MetricsTag existing = tagsMap.putIfAbsent(info.name(), tag);
362      if (existing != null) {
363        throw new MetricsException("Tag "+ info.name() +" already exists!");
364      }
365      return this;
366    }
367
368    tagsMap.put(info.name(), tag);
369
370    return this;
371  }
372
373  public DynamicMetricsRegistry tag(MetricsInfo info, String value) {
374    return tag(info, value, false);
375  }
376
377  Collection<MetricsTag> tags() {
378    return tagsMap.values();
379  }
380
381  Collection<MutableMetric> metrics() {
382    return metricsMap.values();
383  }
384
385  /**
386   * Sample all the mutable metrics and put the snapshot in the builder
387   * @param builder to contain the metrics snapshot
388   * @param all get all the metrics even if the values are not changed.
389   */
390  public void snapshot(MetricsRecordBuilder builder, boolean all) {
391    for (MetricsTag tag : tags()) {
392      builder.add(tag);
393    }
394    for (MutableMetric metric : metrics()) {
395      metric.snapshot(builder, all);
396    }
397  }
398
399  @Override public String toString() {
400    return MoreObjects.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 addNewMetricIfAbsent(String name, T ret,
504      Class<T> metricClass) {
505    //If the value we get back is null then the put was successful and we will
506    // return that. Otherwise metric should contain the thing that was in
507    // before the put could be completed.
508    MutableMetric metric = metricsMap.putIfAbsent(name, ret);
509    if (metric == null) {
510      return ret;
511    }
512
513    return returnExistingWithCast(metric, metricClass, name);
514  }
515
516  @SuppressWarnings("unchecked")
517  private<T> T returnExistingWithCast(MutableMetric metric,
518                                      Class<T> metricClass, String name) {
519    if (!metricClass.isAssignableFrom(metric.getClass())) {
520      throw new MetricsException("Metric already exists in registry for metric name: " +
521              name + " and not of type " + metricClass +
522              " but instead of type " + metric.getClass());
523    }
524
525    return (T) metric;
526  }
527
528  public void clearMetrics() {
529    for (String name:metricsMap.keySet()) {
530      helper.removeObjectName(name);
531    }
532    metricsMap.clear();
533  }
534}