/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.metrics2.lib; import java.util.Collection; import java.util.concurrent.ConcurrentMap; import com.google.common.base.Objects; import com.google.common.collect.Maps; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.metrics2.MetricsException; import org.apache.hadoop.metrics2.MetricsInfo; import org.apache.hadoop.metrics2.MetricsRecordBuilder; import org.apache.hadoop.metrics2.MetricsTag; import org.apache.hadoop.metrics2.impl.MsInfo; /** * An optional metrics registry class for creating and maintaining a * collection of MetricsMutables, making writing metrics source easier. * NOTE: this is a copy of org.apache.hadoop.metrics2.lib.MetricsRegistry with added one * feature: metrics can be removed. When HADOOP-8313 is fixed, usages of this class * should be substituted with org.apache.hadoop.metrics2.lib.MetricsRegistry. * This implementation also provides handy methods for creating metrics * dynamically. * Another difference is that metricsMap implementation is substituted with * thread-safe map, as we allow dynamic metrics additions/removals. */ @InterfaceAudience.Public @InterfaceStability.Evolving public class DynamicMetricsRegistry { private final ConcurrentMap<String, MutableMetric> metricsMap = Maps.newConcurrentMap(); private final ConcurrentMap<String, MetricsTag> tagsMap = Maps.newConcurrentMap(); private final MetricsInfo metricsInfo; /** * Construct the registry with a record name * @param name of the record of the metrics */ public DynamicMetricsRegistry(String name) { metricsInfo = Interns.info(name, name); } /** * Construct the registry with a metadata object * @param info the info object for the metrics record/group */ public DynamicMetricsRegistry(MetricsInfo info) { metricsInfo = info; } /** * @return the info object of the metrics registry */ public MetricsInfo info() { return metricsInfo; } /** * Get a metric by name * @param name of the metric * @return the metric object */ public MutableMetric get(String name) { return metricsMap.get(name); } /** * Get a tag by name * @param name of the tag * @return the tag object */ public MetricsTag getTag(String name) { return tagsMap.get(name); } /** * Create a mutable integer counter * @param name of the metric * @param desc metric description * @param iVal initial value * @return a new counter object */ public MutableCounterInt newCounter(String name, String desc, int iVal) { return newCounter(Interns.info(name, desc), iVal); } /** * Create a mutable integer counter * @param info metadata of the metric * @param iVal initial value * @return a new counter object */ public MutableCounterInt newCounter(MetricsInfo info, int iVal) { MutableCounterInt ret = new MutableCounterInt(info, iVal); return addNewMetricIfAbsent(info.name(), ret, MutableCounterInt.class); } /** * Create a mutable long integer counter * @param name of the metric * @param desc metric description * @param iVal initial value * @return a new counter object */ public MutableCounterLong newCounter(String name, String desc, long iVal) { return newCounter(Interns.info(name, desc), iVal); } /** * Create a mutable long integer counter * @param info metadata of the metric * @param iVal initial value * @return a new counter object */ public MutableCounterLong newCounter(MetricsInfo info, long iVal) { MutableCounterLong ret = new MutableCounterLong(info, iVal); return addNewMetricIfAbsent(info.name(), ret, MutableCounterLong.class); } /** * Create a mutable integer gauge * @param name of the metric * @param desc metric description * @param iVal initial value * @return a new gauge object */ public MutableGaugeInt newGauge(String name, String desc, int iVal) { return newGauge(Interns.info(name, desc), iVal); } /** * Create a mutable integer gauge * @param info metadata of the metric * @param iVal initial value * @return a new gauge object */ public MutableGaugeInt newGauge(MetricsInfo info, int iVal) { MutableGaugeInt ret = new MutableGaugeInt(info, iVal); return addNewMetricIfAbsent(info.name(), ret, MutableGaugeInt.class); } /** * Create a mutable long integer gauge * @param name of the metric * @param desc metric description * @param iVal initial value * @return a new gauge object */ public MutableGaugeLong newGauge(String name, String desc, long iVal) { return newGauge(Interns.info(name, desc), iVal); } /** * Create a mutable long integer gauge * @param info metadata of the metric * @param iVal initial value * @return a new gauge object */ public MutableGaugeLong newGauge(MetricsInfo info, long iVal) { MutableGaugeLong ret = new MutableGaugeLong(info, iVal); return addNewMetricIfAbsent(info.name(), ret, MutableGaugeLong.class); } /** * Create a mutable metric with stats * @param name of the metric * @param desc metric description * @param sampleName of the metric (e.g., "Ops") * @param valueName of the metric (e.g., "Time" or "Latency") * @param extended produce extended stat (stdev, min/max etc.) if true. * @return a new mutable stat metric object */ public MutableStat newStat(String name, String desc, String sampleName, String valueName, boolean extended) { MutableStat ret = new MutableStat(name, desc, sampleName, valueName, extended); return addNewMetricIfAbsent(name, ret, MutableStat.class); } /** * Create a mutable metric with stats * @param name of the metric * @param desc metric description * @param sampleName of the metric (e.g., "Ops") * @param valueName of the metric (e.g., "Time" or "Latency") * @return a new mutable metric object */ public MutableStat newStat(String name, String desc, String sampleName, String valueName) { return newStat(name, desc, sampleName, valueName, false); } /** * Create a mutable rate metric * @param name of the metric * @return a new mutable metric object */ public MutableRate newRate(String name) { return newRate(name, name, false); } /** * Create a mutable rate metric * @param name of the metric * @param description of the metric * @return a new mutable rate metric object */ public MutableRate newRate(String name, String description) { return newRate(name, description, false); } /** * Create a mutable rate metric (for throughput measurement) * @param name of the metric * @param desc description * @param extended produce extended stat (stdev/min/max etc.) if true * @return a new mutable rate metric object */ public MutableRate newRate(String name, String desc, boolean extended) { return newRate(name, desc, extended, true); } @InterfaceAudience.Private public MutableRate newRate(String name, String desc, boolean extended, boolean returnExisting) { if (returnExisting) { MutableMetric rate = metricsMap.get(name); if (rate != null) { if (rate instanceof MutableRate) return (MutableRate) rate; throw new MetricsException("Unexpected metrics type "+ rate.getClass() +" for "+ name); } } MutableRate ret = new MutableRate(name, desc, extended); return addNewMetricIfAbsent(name, ret, MutableRate.class); } /** * Create a new histogram. * @param name Name of the histogram. * @return A new MutableHistogram */ public MutableHistogram newHistogram(String name) { return newHistogram(name, ""); } /** * Create a new histogram. * @param name The name of the histogram * @param desc The description of the data in the histogram. * @return A new MutableHistogram */ public MutableHistogram newHistogram(String name, String desc) { MutableHistogram histo = new MutableHistogram(name, desc); return addNewMetricIfAbsent(name, histo, MutableHistogram.class); } /** * Create a new MutableQuantile(A more accurate histogram). * @param name The name of the histogram * @return a new MutableQuantile */ public MetricMutableQuantiles newQuantile(String name) { return newQuantile(name, ""); } public MetricMutableQuantiles newQuantile(String name, String desc) { MetricMutableQuantiles histo = new MetricMutableQuantiles(name, desc, "Ops", "", 60); return addNewMetricIfAbsent(name, histo, MetricMutableQuantiles.class); } synchronized void add(String name, MutableMetric metric) { addNewMetricIfAbsent(name, metric, MutableMetric.class); } /** * Add sample to a stat metric by name. * @param name of the metric * @param value of the snapshot to add */ public void add(String name, long value) { MutableMetric m = metricsMap.get(name); if (m != null) { if (m instanceof MutableStat) { ((MutableStat) m).add(value); } else { throw new MetricsException("Unsupported add(value) for metric "+ name); } } else { metricsMap.put(name, newRate(name)); // default is a rate metric add(name, value); } } /** * Set the metrics context tag * @param name of the context * @return the registry itself as a convenience */ public DynamicMetricsRegistry setContext(String name) { return tag(MsInfo.Context, name, true); } /** * Add a tag to the metrics * @param name of the tag * @param description of the tag * @param value of the tag * @return the registry (for keep adding tags) */ public DynamicMetricsRegistry tag(String name, String description, String value) { return tag(name, description, value, false); } /** * Add a tag to the metrics * @param name of the tag * @param description of the tag * @param value of the tag * @param override existing tag if true * @return the registry (for keep adding tags) */ public DynamicMetricsRegistry tag(String name, String description, String value, boolean override) { return tag(Interns.info(name, description), value, override); } /** * Add a tag to the metrics * @param info metadata of the tag * @param value of the tag * @param override existing tag if true * @return the registry (for keep adding tags etc.) */ public DynamicMetricsRegistry tag(MetricsInfo info, String value, boolean override) { MetricsTag tag = Interns.tag(info, value); if (!override) { MetricsTag existing = tagsMap.putIfAbsent(info.name(), tag); if (existing != null) { throw new MetricsException("Tag "+ info.name() +" already exists!"); } return this; } tagsMap.put(info.name(), tag); return this; } public DynamicMetricsRegistry tag(MetricsInfo info, String value) { return tag(info, value, false); } Collection<MetricsTag> tags() { return tagsMap.values(); } Collection<MutableMetric> metrics() { return metricsMap.values(); } /** * Sample all the mutable metrics and put the snapshot in the builder * @param builder to contain the metrics snapshot * @param all get all the metrics even if the values are not changed. */ public void snapshot(MetricsRecordBuilder builder, boolean all) { for (MetricsTag tag : tags()) { builder.add(tag); } for (MutableMetric metric : metrics()) { metric.snapshot(builder, all); } } @Override public String toString() { return Objects.toStringHelper(this) .add("info", metricsInfo).add("tags", tags()).add("metrics", metrics()) .toString(); } /** * Removes metric by name * @param name name of the metric to remove */ public void removeMetric(String name) { metricsMap.remove(name); } /** * Get a MetricMutableGaugeLong from the storage. If it is not there atomically put it. * * @param gaugeName name of the gauge to create or get. * @param potentialStartingValue value of the new gauge if we have to create it. */ public MutableGaugeLong getLongGauge(String gaugeName, long potentialStartingValue) { //Try and get the guage. MutableMetric metric = metricsMap.get(gaugeName); //If it's not there then try and put a new one in the storage. if (metric == null) { //Create the potential new gauge. MutableGaugeLong newGauge = new MutableGaugeLong(Interns.info(gaugeName, ""), potentialStartingValue); // Try and put the gauge in. This is atomic. metric = metricsMap.putIfAbsent(gaugeName, newGauge); //If the value we get back is null then the put was successful and we will return that. //otherwise gaugeLong should contain the thing that was in before the put could be completed. if (metric == null) { return newGauge; } } if (!(metric instanceof MutableGaugeLong)) { throw new MetricsException("Metric already exists in registry for metric name: " + gaugeName + " and not of type MetricMutableGaugeLong"); } return (MutableGaugeLong) metric; } /** * Get a MetricMutableCounterLong from the storage. If it is not there atomically put it. * * @param counterName Name of the counter to get * @param potentialStartingValue starting value if we have to create a new counter */ public MutableCounterLong getLongCounter(String counterName, long potentialStartingValue) { //See getLongGauge for description on how this works. MutableMetric counter = metricsMap.get(counterName); if (counter == null) { MutableCounterLong newCounter = new MutableCounterLong(Interns.info(counterName, ""), potentialStartingValue); counter = metricsMap.putIfAbsent(counterName, newCounter); if (counter == null) { return newCounter; } } if (!(counter instanceof MutableCounterLong)) { throw new MetricsException("Metric already exists in registry for metric name: " + counterName + " and not of type MetricMutableCounterLong"); } return (MutableCounterLong) counter; } public MutableHistogram getHistogram(String histoName) { //See getLongGauge for description on how this works. MutableMetric histo = metricsMap.get(histoName); if (histo == null) { MutableHistogram newCounter = new MutableHistogram(Interns.info(histoName, "")); histo = metricsMap.putIfAbsent(histoName, newCounter); if (histo == null) { return newCounter; } } if (!(histo instanceof MutableHistogram)) { throw new MetricsException("Metric already exists in registry for metric name: " + histoName + " and not of type MutableHistogram"); } return (MutableHistogram) histo; } public MetricMutableQuantiles getQuantile(String histoName) { //See getLongGauge for description on how this works. MutableMetric histo = metricsMap.get(histoName); if (histo == null) { MetricMutableQuantiles newCounter = new MetricMutableQuantiles(histoName, "", "Ops", "", 60); histo = metricsMap.putIfAbsent(histoName, newCounter); if (histo == null) { return newCounter; } } if (!(histo instanceof MetricMutableQuantiles)) { throw new MetricsException("Metric already exists in registry for metric name: " + histoName + " and not of type MutableHistogram"); } return (MetricMutableQuantiles) histo; } private<T extends MutableMetric> T addNewMetricIfAbsent(String name, T ret, Class<T> metricClass) { //If the value we get back is null then the put was successful and we will // return that. Otherwise metric should contain the thing that was in // before the put could be completed. MutableMetric metric = metricsMap.putIfAbsent(name, ret); if (metric == null) { return ret; } return returnExistingWithCast(metric, metricClass, name); } @SuppressWarnings("unchecked") private<T> T returnExistingWithCast(MutableMetric metric, Class<T> metricClass, String name) { if (!metricClass.isAssignableFrom(metric.getClass())) { throw new MetricsException("Metric already exists in registry for metric name: " + name + " and not of type " + metricClass + " but instead of type " + metric.getClass()); } return (T) metric; } public void clearMetrics() { metricsMap.clear(); } }