/** * 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.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.metrics2.MetricsException; import org.apache.hadoop.metrics2.MetricsRecordBuilder; import org.apache.hadoop.metrics2.MetricsTag; /** * 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 & tagsMap implementation is substituted with * concurrent map, as we allow dynamic metrics additions/removals. */ public class DynamicMetricsRegistry { private final Log LOG = LogFactory.getLog(this.getClass()); /** key for the context tag */ public static final String CONTEXT_KEY = "context"; /** description for the context tag */ public static final String CONTEXT_DESC = "Metrics context"; private final ConcurrentMap<String, MetricMutable> metricsMap = new ConcurrentHashMap<String, MetricMutable>(); private final ConcurrentMap<String, MetricsTag> tagsMap = new ConcurrentHashMap<String, MetricsTag>(); private final String name; private final MetricMutableFactory mf; /** * Construct the registry with a record name * @param name of the record of the metrics */ public DynamicMetricsRegistry(String name) { this.name = name; this.mf = new MetricMutableFactory(); } /** * Construct the registry with a name and a metric factory * @param name of the record of the metrics * @param factory for creating new mutable metrics */ public DynamicMetricsRegistry(String name, MetricMutableFactory factory) { this.name = name; this.mf = factory; } /** * @return the name of the metrics registry */ public String name() { return name; } /** * Get a metric by name * @param name of the metric * @return the metric object */ public MetricMutable get(String name) { return metricsMap.get(name); } /** * Create a mutable integer counter * @param name of the metric * @param description of the metric * @param initValue of the metric * @return a new counter object */ public MetricMutableCounterInt newCounter(String name, String description, int initValue) { MetricMutableCounterInt ret = mf.newCounter(name, description, initValue); return addNewMetricIfAbsent(name, ret, MetricMutableCounterInt.class); } /** * Create a mutable long integer counter * @param name of the metric * @param description of the metric * @param initValue of the metric * @return a new counter object */ public MetricMutableCounterLong newCounter(String name, String description, long initValue) { MetricMutableCounterLong ret = mf.newCounter(name, description, initValue); return addNewMetricIfAbsent(name, ret, MetricMutableCounterLong.class); } /** * Create a mutable integer gauge * @param name of the metric * @param description of the metric * @param initValue of the metric * @return a new gauge object */ public MetricMutableGaugeInt newGauge(String name, String description, int initValue) { MetricMutableGaugeInt ret = mf.newGauge(name, description, initValue); return addNewMetricIfAbsent(name, ret, MetricMutableGaugeInt.class); } /** * Create a mutable long integer gauge * @param name of the metric * @param description of the metric * @param initValue of the metric * @return a new gauge object */ public MetricMutableGaugeLong newGauge(String name, String description, long initValue) { MetricMutableGaugeLong ret = mf.newGauge(name, description, initValue); return addNewMetricIfAbsent(name, ret, MetricMutableGaugeLong.class); } /** * Create a mutable metric with stats * @param name of the metric * @param description of the metric * @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 metric object */ public MetricMutableStat newStat(String name, String description, String sampleName, String valueName, boolean extended) { MetricMutableStat ret = mf.newStat(name, description, sampleName, valueName, extended); return addNewMetricIfAbsent(name, ret, MetricMutableStat.class); } /** * Create a mutable metric with stats * @param name of the metric * @param description of the metric * @param sampleName of the metric (e.g., "ops") * @param valueName of the metric (e.g., "time" or "latency") * @return a new metric object */ public MetricMutableStat newStat(String name, String description, String sampleName, String valueName) { return newStat(name, description, sampleName, valueName, false); } /** * Create a mutable metric with stats using the name only * @param name of the metric * @return a new metric object */ public MetricMutableStat newStat(String name) { return newStat(name, "", "ops", "time", false); } /** * Create a new histogram. * @param name Name of the histogram. * @return A new MutableHistogram */ public MetricMutableHistogram 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 MetricMutableHistogram newHistogram(String name, String desc) { MetricMutableHistogram histo = new MetricMutableHistogram(name, desc); return addNewMetricIfAbsent(name, histo, MetricMutableHistogram.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, ""); } /** * Create a new MutableQuantile(A more accurate histogram). * @param name The name of the histogram * @param desc Description of the data. * @return a new MutableQuantile */ public MetricMutableQuantiles newQuantile(String name, String desc) { MetricMutableQuantiles histo = new MetricMutableQuantiles(name, desc); return addNewMetricIfAbsent(name, histo, MetricMutableQuantiles.class); } /** * Set the metrics context tag * @param name of the context * @return the registry itself as a convenience */ public DynamicMetricsRegistry setContext(String name) { return tag(CONTEXT_KEY, CONTEXT_DESC, name); } /** * 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) { MetricsTag tag = new MetricsTag(name, description, value); if (!override) { MetricsTag existing = tagsMap.putIfAbsent(name, tag); if (existing != null) { throw new MetricsException("Tag "+ name +" already exists!"); } return this; } tagsMap.put(name, tag); return this; } /** * Get the tags * @return the tags set */ public Set<Entry<String, MetricsTag>> tags() { return tagsMap.entrySet(); } /** * Get the metrics * @return the metrics set */ public Set<Entry<String, MetricMutable>> metrics() { return metricsMap.entrySet(); } /** * 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 (Entry<String, MetricsTag> entry : tags()) { builder.add(entry.getValue()); } for (Entry<String, MetricMutable> entry : metrics()) { entry.getValue().snapshot(builder, all); } } /** * 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 counter if we have to create it. * @return a metric object */ public MetricMutableGaugeLong getLongGauge(String gaugeName, long potentialStartingValue) { //Try and get the guage. MetricMutable 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. MetricMutableGaugeLong newGauge = mf.newGauge(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 MetricMutableGaugeLong)) { throw new MetricsException("Metric already exists in registry for metric name: " + name + " and not of type MetricMutableGaugeLong"); } return (MetricMutableGaugeLong) 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 * @return a metric object */ public MetricMutableCounterLong getLongCounter(String counterName, long potentialStartingValue) { //See getLongGauge for description on how this works. MetricMutable counter = metricsMap.get(counterName); if (counter == null) { MetricMutableCounterLong newCounter = mf.newCounter(counterName, "", potentialStartingValue); counter = metricsMap.putIfAbsent(counterName, newCounter); if (counter == null) { return newCounter; } } if (!(counter instanceof MetricMutableCounterLong)) { throw new MetricsException("Metric already exists in registry for metric name: " + name + "and not of type MetricMutableCounterLong"); } return (MetricMutableCounterLong) counter; } public MetricMutableHistogram getHistogram(String histoName) { //See getLongGauge for description on how this works. MetricMutable histo = metricsMap.get(histoName); if (histo == null) { MetricMutableHistogram newHisto = new MetricMutableHistogram(histoName, ""); histo = metricsMap.putIfAbsent(histoName, newHisto); if (histo == null) { return newHisto; } } if (!(histo instanceof MetricMutableHistogram)) { throw new MetricsException("Metric already exists in registry for metric name: " + name + "and not of type MetricMutableHistogram"); } return (MetricMutableHistogram) histo; } public MetricMutableQuantiles getQuantile(String histoName) { //See getLongGauge for description on how this works. MetricMutable histo = metricsMap.get(histoName); if (histo == null) { MetricMutableQuantiles newHisto = new MetricMutableQuantiles(histoName, ""); histo = metricsMap.putIfAbsent(histoName, newHisto); if (histo == null) { return newHisto; } } if (!(histo instanceof MetricMutableQuantiles)) { throw new MetricsException("Metric already exists in registry for metric name: " + name + "and not of type MetricMutableQuantiles"); } return (MetricMutableQuantiles) histo; } private<T extends MetricMutable> 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. MetricMutable metric = metricsMap.putIfAbsent(name, ret); if (metric == null) { return ret; } return returnExistingWithCast(metric, metricClass, name); } private<T> T returnExistingWithCast(MetricMutable 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); } return (T) metric; } public void clearMetrics() { metricsMap.clear(); } }