/**
* Copyright 2010 The Apache Software Foundation
*
* 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.hbase.regionserver.metrics;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.metrics.MetricsContext;
import org.apache.hadoop.metrics.MetricsRecord;
import org.apache.hadoop.metrics.MetricsUtil;
import org.apache.hadoop.metrics.Updater;
import org.apache.hadoop.metrics.util.MetricsBase;
import org.apache.hadoop.metrics.util.MetricsLongValue;
import org.apache.hadoop.metrics.util.MetricsRegistry;
import org.apache.hadoop.metrics.util.MetricsTimeVaryingRate;
/**
*
* This class is for maintaining the various RPC statistics
* and publishing them through the metrics interfaces.
* This also registers the JMX MBean for RPC.
* <p>
* This class has a number of metrics variables that are publicly accessible;
* these variables (objects) have methods to update their values;
* for example: rpcQueueTime.inc(time)
*
*/
public class RegionServerDynamicMetrics implements Updater {
private static final String UNABLE_TO_CLEAR = "Unable to clear RegionServerDynamicMetrics";
private MetricsRecord metricsRecord;
private MetricsContext context;
private final RegionServerDynamicStatistics rsDynamicStatistics;
private Method updateMbeanInfoIfMetricsListChanged = null;
private HRegionServer regionServer;
private static final Log LOG =
LogFactory.getLog(RegionServerDynamicStatistics.class);
private boolean reflectionInitialized = false;
private boolean needsUpdateMessage = false;
private Field recordMetricMapField;
private Field registryMetricMapField;
/**
* The metrics variables are public:
* - they can be set directly by calling their set/inc methods
* -they can also be read directly - e.g. JMX does this.
*/
public final MetricsRegistry registry = new MetricsRegistry();
private RegionServerDynamicMetrics(HRegionServer regionServer) {
this.context = MetricsUtil.getContext("hbase");
this.metricsRecord = MetricsUtil.createRecord(
this.context,
"RegionServerDynamicStatistics");
context.registerUpdater(this);
this.rsDynamicStatistics = new RegionServerDynamicStatistics(this.registry);
this.regionServer = regionServer;
try {
updateMbeanInfoIfMetricsListChanged =
this.rsDynamicStatistics.getClass().getSuperclass()
.getDeclaredMethod("updateMbeanInfoIfMetricsListChanged",
new Class[]{});
updateMbeanInfoIfMetricsListChanged.setAccessible(true);
} catch (Exception e) {
LOG.error(e);
}
}
public static RegionServerDynamicMetrics newInstance(HRegionServer regionServer) {
RegionServerDynamicMetrics metrics =
new RegionServerDynamicMetrics(regionServer);
return metrics;
}
public synchronized void setNumericMetric(String name, long amt) {
MetricsLongValue m = (MetricsLongValue)registry.get(name);
if (m == null) {
m = new MetricsLongValue(name, this.registry);
this.needsUpdateMessage = true;
}
m.set(amt);
}
public synchronized void incrTimeVaryingMetric(
String name,
long amt,
int numOps) {
MetricsTimeVaryingRate m = (MetricsTimeVaryingRate)registry.get(name);
if (m == null) {
m = new MetricsTimeVaryingRate(name, this.registry);
this.needsUpdateMessage = true;
}
if (numOps > 0) {
m.inc(numOps, amt);
}
}
/**
* Clear all metrics this exposes.
* Uses reflection to clear them from hadoop metrics side as well.
*/
@SuppressWarnings("rawtypes")
public void clear() {
this.needsUpdateMessage = true;
// If this is the first clear use reflection to get the two maps that hold copies of our
// metrics on the hadoop metrics side. We have to use reflection because there is not
// remove metrics on the hadoop side. If we can't get them then clearing old metrics
// is not possible and bailing out early is our best option.
if (!this.reflectionInitialized) {
this.reflectionInitialized = true;
try {
this.recordMetricMapField = this.metricsRecord.getClass().getDeclaredField("metricTable");
this.recordMetricMapField.setAccessible(true);
} catch (SecurityException e) {
LOG.debug(UNABLE_TO_CLEAR);
return;
} catch (NoSuchFieldException e) {
LOG.debug(UNABLE_TO_CLEAR);
return;
}
try {
this.registryMetricMapField = this.registry.getClass().getDeclaredField("metricsList");
this.registryMetricMapField.setAccessible(true);
} catch (SecurityException e) {
LOG.debug(UNABLE_TO_CLEAR);
return;
} catch (NoSuchFieldException e) {
LOG.debug(UNABLE_TO_CLEAR);
return;
}
}
//If we found both fields then try and clear the maps.
if (this.recordMetricMapField != null && this.registryMetricMapField != null) {
try {
Map recordMap = (Map) this.recordMetricMapField.get(this.metricsRecord);
recordMap.clear();
Map registryMap = (Map) this.registryMetricMapField.get(this.registry);
registryMap.clear();
} catch (IllegalArgumentException e) {
LOG.debug(UNABLE_TO_CLEAR);
} catch (IllegalAccessException e) {
LOG.debug(UNABLE_TO_CLEAR);
}
} else {
LOG.debug(UNABLE_TO_CLEAR);
}
}
/**
* Push the metrics to the monitoring subsystem on doUpdate() call.
* @param context ctx
*/
public void doUpdates(MetricsContext context) {
/* get dynamically created numeric metrics, and push the metrics */
for (Entry<String, AtomicLong> entry : RegionMetricsStorage.getNumericMetrics().entrySet()) {
this.setNumericMetric(entry.getKey(), entry.getValue().getAndSet(0));
}
/* export estimated size of all response queues */
if (regionServer != null) {
long responseQueueSize = regionServer.getResponseQueueSize();
this.setNumericMetric("responseQueuesSize", responseQueueSize);
}
/* get dynamically created numeric metrics, and push the metrics.
* These ones aren't to be reset; they are cumulative. */
for (Entry<String, AtomicLong> entry : RegionMetricsStorage.getNumericPersistentMetrics().entrySet()) {
this.setNumericMetric(entry.getKey(), entry.getValue().get());
}
/* get dynamically created time varying metrics, and push the metrics */
for (Entry<String, Pair<AtomicLong, AtomicInteger>> entry :
RegionMetricsStorage.getTimeVaryingMetrics().entrySet()) {
Pair<AtomicLong, AtomicInteger> value = entry.getValue();
this.incrTimeVaryingMetric(entry.getKey(),
value.getFirst().getAndSet(0),
value.getSecond().getAndSet(0));
}
// If there are new metrics sending this message to jmx tells it to update everything.
// This is not ideal we should just move to metrics2 that has full support for dynamic metrics.
if (needsUpdateMessage) {
try {
if (updateMbeanInfoIfMetricsListChanged != null) {
updateMbeanInfoIfMetricsListChanged.invoke(this.rsDynamicStatistics,
new Object[]{});
}
} catch (Exception e) {
LOG.error(e);
}
needsUpdateMessage = false;
}
synchronized (registry) {
// Iterate through the registry to propagate the different rpc metrics.
for (String metricName : registry.getKeyList() ) {
MetricsBase value = registry.get(metricName);
value.pushMetric(metricsRecord);
}
}
metricsRecord.update();
}
public void shutdown() {
if (rsDynamicStatistics != null)
rsDynamicStatistics.shutdown();
}
}