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 */
018package org.apache.hadoop.metrics2.lib;
019
020import java.util.Collection;
021import java.util.concurrent.ConcurrentMap;
022import org.apache.hadoop.hbase.metrics.Interns;
023import org.apache.hadoop.metrics2.MetricsException;
024import org.apache.hadoop.metrics2.MetricsInfo;
025import org.apache.hadoop.metrics2.MetricsRecordBuilder;
026import org.apache.hadoop.metrics2.MetricsTag;
027import org.apache.hadoop.metrics2.impl.MsInfo;
028import org.apache.yetus.audience.InterfaceAudience;
029
030import org.apache.hbase.thirdparty.com.google.common.base.MoreObjects;
031import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
032
033/**
034 * An optional metrics registry class for creating and maintaining a collection of MetricsMutables,
035 * making writing metrics source easier. NOTE: this is a copy of
036 * org.apache.hadoop.metrics2.lib.MetricsRegistry with added one feature: metrics can be removed.
037 * When HADOOP-8313 is fixed, usages of this class should be substituted with
038 * org.apache.hadoop.metrics2.lib.MetricsRegistry. This implementation also provides handy methods
039 * for creating metrics dynamically. Another difference is that metricsMap implementation is
040 * substituted with thread-safe map, as we allow dynamic metrics additions/removals.
041 */
042@InterfaceAudience.Private
043public class DynamicMetricsRegistry {
044
045  private final ConcurrentMap<String, MutableMetric> metricsMap = Maps.newConcurrentMap();
046  private final ConcurrentMap<String, MetricsTag> tagsMap = Maps.newConcurrentMap();
047  private final MetricsInfo metricsInfo;
048  private final DefaultMetricsSystemHelper helper = new DefaultMetricsSystemHelper();
049  private final static String[] histogramSuffixes = new String[] { "_num_ops", "_min", "_max",
050    "_median", "_75th_percentile", "_90th_percentile", "_95th_percentile", "_99th_percentile" };
051
052  /**
053   * Construct the registry with a record name
054   * @param name of the record of the metrics
055   */
056  public DynamicMetricsRegistry(String name) {
057    this(Interns.info(name, name));
058  }
059
060  /**
061   * Construct the registry with a metadata object
062   * @param info the info object for the metrics record/group
063   */
064  public DynamicMetricsRegistry(MetricsInfo info) {
065    metricsInfo = info;
066  }
067
068  /** Returns the info object of the metrics registry */
069  public MetricsInfo info() {
070    return metricsInfo;
071  }
072
073  /**
074   * Get a metric by name
075   * @param name of the metric
076   * @return the metric object
077   */
078  public MutableMetric get(String name) {
079    return metricsMap.get(name);
080  }
081
082  /**
083   * Get a tag by name
084   * @param name of the tag
085   * @return the tag object
086   */
087  public MetricsTag getTag(String name) {
088    return tagsMap.get(name);
089  }
090
091  /**
092   * Create a mutable long integer counter
093   * @param name of the metric
094   * @param desc metric description
095   * @param iVal initial value
096   * @return a new counter object
097   */
098  public MutableFastCounter newCounter(String name, String desc, long iVal) {
099    return newCounter(new MetricsInfoImpl(name, desc), iVal);
100  }
101
102  /**
103   * Create a mutable long integer counter
104   * @param info metadata of the metric
105   * @param iVal initial value
106   * @return a new counter object
107   */
108  public MutableFastCounter newCounter(MetricsInfo info, long iVal) {
109    MutableFastCounter ret = new MutableFastCounter(info, iVal);
110    return addNewMetricIfAbsent(info.name(), ret, MutableFastCounter.class);
111  }
112
113  /**
114   * Create a mutable long integer gauge
115   * @param name of the metric
116   * @param desc metric description
117   * @param iVal initial value
118   * @return a new gauge object
119   */
120  public MutableGaugeLong newGauge(String name, String desc, long iVal) {
121    return newGauge(new MetricsInfoImpl(name, desc), iVal);
122  }
123
124  /**
125   * Create a mutable long integer gauge
126   * @param info metadata of the metric
127   * @param iVal initial value
128   * @return a new gauge object
129   */
130  public MutableGaugeLong newGauge(MetricsInfo info, long iVal) {
131    MutableGaugeLong ret = new MutableGaugeLong(info, iVal);
132    return addNewMetricIfAbsent(info.name(), ret, MutableGaugeLong.class);
133  }
134
135  /**
136   * Create a mutable metric with stats
137   * @param name       of the metric
138   * @param desc       metric description
139   * @param sampleName of the metric (e.g., "Ops")
140   * @param valueName  of the metric (e.g., "Time" or "Latency")
141   * @param extended   produce extended stat (stdev, min/max etc.) if true.
142   * @return a new mutable stat metric object
143   */
144  public MutableStat newStat(String name, String desc, String sampleName, String valueName,
145    boolean extended) {
146    MutableStat ret = new MutableStat(name, desc, sampleName, valueName, extended);
147    return addNewMetricIfAbsent(name, ret, MutableStat.class);
148  }
149
150  /**
151   * Create a mutable metric with stats
152   * @param name       of the metric
153   * @param desc       metric description
154   * @param sampleName of the metric (e.g., "Ops")
155   * @param valueName  of the metric (e.g., "Time" or "Latency")
156   * @return a new mutable metric object
157   */
158  public MutableStat newStat(String name, String desc, String sampleName, String valueName) {
159    return newStat(name, desc, sampleName, valueName, false);
160  }
161
162  /**
163   * Create a mutable rate metric
164   * @param name of the metric
165   * @return a new mutable metric object
166   */
167  public MutableRate newRate(String name) {
168    return newRate(name, name, false);
169  }
170
171  /**
172   * Create a mutable rate metric
173   * @param name        of the metric
174   * @param description of the metric
175   * @return a new mutable rate metric object
176   */
177  public MutableRate newRate(String name, String description) {
178    return newRate(name, description, false);
179  }
180
181  /**
182   * Create a mutable rate metric (for throughput measurement)
183   * @param name     of the metric
184   * @param desc     description
185   * @param extended produce extended stat (stdev/min/max etc.) if true
186   * @return a new mutable rate metric object
187   */
188  public MutableRate newRate(String name, String desc, boolean extended) {
189    return newRate(name, desc, extended, true);
190  }
191
192  @InterfaceAudience.Private
193  public MutableRate newRate(String name, String desc, boolean extended, boolean returnExisting) {
194    if (returnExisting) {
195      MutableMetric rate = metricsMap.get(name);
196      if (rate != null) {
197        if (rate instanceof MutableRate) {
198          return (MutableRate) rate;
199        }
200
201        throw new MetricsException("Unexpected metrics type " + rate.getClass() + " for " + name);
202      }
203    }
204    MutableRate ret = new MutableRate(name, desc, extended);
205    return addNewMetricIfAbsent(name, ret, MutableRate.class);
206  }
207
208  /**
209   * Create a new histogram.
210   * @param name Name of the histogram.
211   * @return A new MutableHistogram
212   */
213  public MutableHistogram newHistogram(String name) {
214    return newHistogram(name, "");
215  }
216
217  /**
218   * Create a new histogram.
219   * @param name The name of the histogram
220   * @param desc The description of the data in the histogram.
221   * @return A new MutableHistogram
222   */
223  public MutableHistogram newHistogram(String name, String desc) {
224    MutableHistogram histo = new MutableHistogram(name, desc);
225    return addNewMetricIfAbsent(name, histo, MutableHistogram.class);
226  }
227
228  /**
229   * Create a new histogram with time range counts.
230   * @param name Name of the histogram.
231   * @return A new MutableTimeHistogram
232   */
233  public MutableTimeHistogram newTimeHistogram(String name) {
234    return newTimeHistogram(name, "");
235  }
236
237  /**
238   * Create a new histogram with time range counts.
239   * @param name The name of the histogram
240   * @param desc The description of the data in the histogram.
241   * @return A new MutableTimeHistogram
242   */
243  public MutableTimeHistogram newTimeHistogram(String name, String desc) {
244    MutableTimeHistogram histo = new MutableTimeHistogram(name, desc);
245    return addNewMetricIfAbsent(name, histo, MutableTimeHistogram.class);
246  }
247
248  /**
249   * Create a new histogram with size range counts.
250   * @param name Name of the histogram.
251   * @return A new MutableSizeHistogram
252   */
253  public MutableSizeHistogram newSizeHistogram(String name) {
254    return newSizeHistogram(name, "");
255  }
256
257  /**
258   * Create a new histogram with size range counts.
259   * @param name The name of the histogram
260   * @param desc The description of the data in the histogram.
261   * @return A new MutableSizeHistogram
262   */
263  public MutableSizeHistogram newSizeHistogram(String name, String desc) {
264    MutableSizeHistogram histo = new MutableSizeHistogram(name, desc);
265    return addNewMetricIfAbsent(name, histo, MutableSizeHistogram.class);
266  }
267
268  synchronized void add(String name, MutableMetric metric) {
269    addNewMetricIfAbsent(name, metric, MutableMetric.class);
270  }
271
272  /**
273   * Add sample to a stat metric by name.
274   * @param name  of the metric
275   * @param value of the snapshot to add
276   */
277  public void add(String name, long value) {
278    MutableMetric m = metricsMap.get(name);
279
280    if (m != null) {
281      if (m instanceof MutableStat) {
282        ((MutableStat) m).add(value);
283      } else {
284        throw new MetricsException("Unsupported add(value) for metric " + name);
285      }
286    } else {
287      metricsMap.put(name, newRate(name)); // default is a rate metric
288      add(name, value);
289    }
290  }
291
292  /**
293   * Set the metrics context tag
294   * @param name of the context
295   * @return the registry itself as a convenience
296   */
297  public DynamicMetricsRegistry setContext(String name) {
298    return tag(MsInfo.Context, name, true);
299  }
300
301  /**
302   * Add a tag to the metrics
303   * @param name        of the tag
304   * @param description of the tag
305   * @param value       of the tag
306   * @return the registry (for keep adding tags)
307   */
308  public DynamicMetricsRegistry tag(String name, String description, String value) {
309    return tag(name, description, value, false);
310  }
311
312  /**
313   * Add a tag to the metrics
314   * @param name        of the tag
315   * @param description of the tag
316   * @param value       of the tag
317   * @param override    existing tag if true
318   * @return the registry (for keep adding tags)
319   */
320  public DynamicMetricsRegistry tag(String name, String description, String value,
321    boolean override) {
322    return tag(new MetricsInfoImpl(name, description), value, override);
323  }
324
325  /**
326   * Add a tag to the metrics
327   * @param info     metadata of the tag
328   * @param value    of the tag
329   * @param override existing tag if true
330   * @return the registry (for keep adding tags etc.)
331   */
332  public DynamicMetricsRegistry tag(MetricsInfo info, String value, boolean override) {
333    MetricsTag tag = Interns.tag(info, value);
334
335    if (!override) {
336      MetricsTag existing = tagsMap.putIfAbsent(info.name(), tag);
337      if (existing != null) {
338        throw new MetricsException("Tag " + info.name() + " already exists!");
339      }
340      return this;
341    }
342
343    tagsMap.put(info.name(), tag);
344
345    return this;
346  }
347
348  public DynamicMetricsRegistry tag(MetricsInfo info, String value) {
349    return tag(info, value, false);
350  }
351
352  Collection<MetricsTag> tags() {
353    return tagsMap.values();
354  }
355
356  Collection<MutableMetric> metrics() {
357    return metricsMap.values();
358  }
359
360  /**
361   * Sample all the mutable metrics and put the snapshot in the builder
362   * @param builder to contain the metrics snapshot
363   * @param all     get all the metrics even if the values are not changed.
364   */
365  public void snapshot(MetricsRecordBuilder builder, boolean all) {
366    for (MetricsTag tag : tags()) {
367      builder.add(tag);
368    }
369    for (MutableMetric metric : metrics()) {
370      metric.snapshot(builder, all);
371    }
372  }
373
374  @Override
375  public String toString() {
376    return MoreObjects.toStringHelper(this).add("info", metricsInfo).add("tags", tags())
377      .add("metrics", metrics()).toString();
378  }
379
380  /**
381   * Removes metric by name
382   * @param name name of the metric to remove
383   */
384  public void removeMetric(String name) {
385    helper.removeObjectName(name);
386    metricsMap.remove(name);
387  }
388
389  public void removeHistogramMetrics(String baseName) {
390    for (String suffix : histogramSuffixes) {
391      removeMetric(baseName + suffix);
392    }
393  }
394
395  /**
396   * Get a MetricMutableGaugeLong from the storage. If it is not there atomically put it.
397   * @param gaugeName              name of the gauge to create or get.
398   * @param potentialStartingValue value of the new gauge if we have to create it.
399   */
400  public MutableGaugeLong getGauge(String gaugeName, long potentialStartingValue) {
401    // Try and get the guage.
402    MutableMetric metric = metricsMap.get(gaugeName);
403
404    // If it's not there then try and put a new one in the storage.
405    if (metric == null) {
406
407      // Create the potential new gauge.
408      MutableGaugeLong newGauge =
409        new MutableGaugeLong(new MetricsInfoImpl(gaugeName, ""), potentialStartingValue);
410
411      // Try and put the gauge in. This is atomic.
412      metric = metricsMap.putIfAbsent(gaugeName, newGauge);
413
414      // If the value we get back is null then the put was successful and we will return that.
415      // otherwise gaugeLong should contain the thing that was in before the put could be completed.
416      if (metric == null) {
417        return newGauge;
418      }
419    }
420
421    if (!(metric instanceof MutableGaugeLong)) {
422      throw new MetricsException("Metric already exists in registry for metric name: " + gaugeName
423        + " and not of type MetricMutableGaugeLong");
424    }
425
426    return (MutableGaugeLong) metric;
427  }
428
429  /**
430   * Get a MetricMutableGaugeInt from the storage. If it is not there atomically put it.
431   * @param gaugeName              name of the gauge to create or get.
432   * @param potentialStartingValue value of the new gauge if we have to create it.
433   */
434  public MutableGaugeInt getGaugeInt(String gaugeName, int potentialStartingValue) {
435    // Try and get the guage.
436    MutableMetric metric = metricsMap.get(gaugeName);
437
438    // If it's not there then try and put a new one in the storage.
439    if (metric == null) {
440      // Create the potential new gauge.
441      MutableGaugeInt newGauge =
442        new MutableGaugeInt(new MetricsInfoImpl(gaugeName, ""), potentialStartingValue);
443
444      // Try and put the gauge in. This is atomic.
445      metric = metricsMap.putIfAbsent(gaugeName, newGauge);
446
447      // If the value we get back is null then the put was successful and we will return that.
448      // otherwise gaugeInt should contain the thing that was in before the put could be completed.
449      if (metric == null) {
450        return newGauge;
451      }
452    }
453
454    if (!(metric instanceof MutableGaugeInt)) {
455      throw new MetricsException("Metric already exists in registry for metric name: " + gaugeName
456        + " and not of type MetricMutableGaugeInr");
457    }
458
459    return (MutableGaugeInt) metric;
460  }
461
462  /**
463   * Get a MetricMutableCounterLong from the storage. If it is not there atomically put it.
464   * @param counterName            Name of the counter to get
465   * @param potentialStartingValue starting value if we have to create a new counter
466   */
467  public MutableFastCounter getCounter(String counterName, long potentialStartingValue) {
468    // See getGauge for description on how this works.
469    MutableMetric counter = metricsMap.get(counterName);
470    if (counter == null) {
471      MutableFastCounter newCounter =
472        new MutableFastCounter(new MetricsInfoImpl(counterName, ""), potentialStartingValue);
473      counter = metricsMap.putIfAbsent(counterName, newCounter);
474      if (counter == null) {
475        return newCounter;
476      }
477    }
478
479    if (!(counter instanceof MutableCounter)) {
480      throw new MetricsException("Metric already exists in registry for metric name: " + counterName
481        + " and not of type MutableCounter");
482    }
483
484    return (MutableFastCounter) counter;
485  }
486
487  public MutableHistogram getHistogram(String histoName) {
488    // See getGauge for description on how this works.
489    MutableMetric histo = metricsMap.get(histoName);
490    if (histo == null) {
491      MutableHistogram newCounter = new MutableHistogram(new MetricsInfoImpl(histoName, ""));
492      histo = metricsMap.putIfAbsent(histoName, newCounter);
493      if (histo == null) {
494        return newCounter;
495      }
496    }
497
498    if (!(histo instanceof MutableHistogram)) {
499      throw new MetricsException("Metric already exists in registry for metric name: " + histoName
500        + " and not of type MutableHistogram");
501    }
502
503    return (MutableHistogram) histo;
504  }
505
506  private <T extends MutableMetric> T addNewMetricIfAbsent(String name, T ret,
507    Class<T> metricClass) {
508    // If the value we get back is null then the put was successful and we will
509    // return that. Otherwise metric should contain the thing that was in
510    // before the put could be completed.
511    MutableMetric metric = metricsMap.putIfAbsent(name, ret);
512    if (metric == null) {
513      return ret;
514    }
515
516    return returnExistingWithCast(metric, metricClass, name);
517  }
518
519  @SuppressWarnings("unchecked")
520  private <T> T returnExistingWithCast(MutableMetric metric, Class<T> metricClass, String name) {
521    if (!metricClass.isAssignableFrom(metric.getClass())) {
522      throw new MetricsException("Metric already exists in registry for metric name: " + name
523        + " and not of type " + metricClass + " but instead of type " + metric.getClass());
524    }
525
526    return (T) metric;
527  }
528
529  public void clearMetrics() {
530    for (String name : metricsMap.keySet()) {
531      helper.removeObjectName(name);
532    }
533    metricsMap.clear();
534  }
535}