HDFS-2083 Query JMX statistics over http via JMXJsonServlet. (tanping)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1138645 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Tanping Wang 2011-06-22 21:42:46 +00:00
parent c7c8f2fe48
commit 15e68cb374
2 changed files with 108 additions and 102 deletions

View File

@ -292,6 +292,8 @@ Trunk (unreleased changes)
HDFS-2055. Add hflush support to libhdfs. (Travis Crawford via eli) HDFS-2055. Add hflush support to libhdfs. (Travis Crawford via eli)
HDFS-2083. Query JMX statistics over http via JMXJsonServlet. (tanping)
IMPROVEMENTS IMPROVEMENTS
HDFS-1875. MiniDFSCluster hard-codes dfs.datanode.address to localhost HDFS-1875. MiniDFSCluster hard-codes dfs.datanode.address to localhost

View File

@ -17,8 +17,12 @@
*/ */
package org.apache.hadoop.hdfs.server.namenode; package org.apache.hadoop.hdfs.server.namenode;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -27,13 +31,7 @@
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -43,24 +41,27 @@
import org.apache.hadoop.hdfs.DFSUtil; import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates; import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates;
import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.util.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference; import org.codehaus.jackson.type.TypeReference;
import org.znerd.xmlenc.XMLOutputter; import org.znerd.xmlenc.XMLOutputter;
/** /**
* This class generates the data that is needed to be displayed on cluster web * This class generates the data that is needed to be displayed on cluster web
* console by connecting to each namenode through JMX. * console.
*/ */
@InterfaceAudience.Private @InterfaceAudience.Private
class ClusterJspHelper { class ClusterJspHelper {
private static final Log LOG = LogFactory.getLog(ClusterJspHelper.class); private static final Log LOG = LogFactory.getLog(ClusterJspHelper.class);
public static final String OVERALL_STATUS = "overall-status"; public static final String OVERALL_STATUS = "overall-status";
public static final String DEAD = "Dead"; public static final String DEAD = "Dead";
private static final String JMX_QRY =
"/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo";
/** /**
* JSP helper function that generates cluster health report. When * JSP helper function that generates cluster health report. When
* encountering exception while getting Namenode status, the exception will * encountering exception while getting Namenode status, the exception will
* be listed in the page with corresponding stack trace. * be listed on the page with corresponding stack trace.
*/ */
ClusterStatus generateClusterHealthReport() { ClusterStatus generateClusterHealthReport() {
ClusterStatus cs = new ClusterStatus(); ClusterStatus cs = new ClusterStatus();
@ -79,26 +80,24 @@ ClusterStatus generateClusterHealthReport() {
NamenodeMXBeanHelper nnHelper = null; NamenodeMXBeanHelper nnHelper = null;
try { try {
nnHelper = new NamenodeMXBeanHelper(isa, conf); nnHelper = new NamenodeMXBeanHelper(isa, conf);
NamenodeStatus nn = nnHelper.getNamenodeStatus(); String mbeanProps= queryMbean(nnHelper.httpAddress, conf);
NamenodeStatus nn = nnHelper.getNamenodeStatus(mbeanProps);
if (cs.clusterid.isEmpty() || cs.clusterid.equals("")) { // Set clusterid only once if (cs.clusterid.isEmpty() || cs.clusterid.equals("")) { // Set clusterid only once
cs.clusterid = nnHelper.getClusterId(); cs.clusterid = nnHelper.getClusterId(mbeanProps);
} }
cs.addNamenodeStatus(nn); cs.addNamenodeStatus(nn);
} catch ( Exception e ) { } catch ( Exception e ) {
// track exceptions encountered when connecting to namenodes // track exceptions encountered when connecting to namenodes
cs.addException(isa.getHostName(), e); cs.addException(isa.getHostName(), e);
continue; continue;
} finally {
if (nnHelper != null) {
nnHelper.cleanup();
}
} }
} }
return cs; return cs;
} }
/** /**
* Helper function that generates the decommissioning report. * Helper function that generates the decommissioning report. Connect to each
* Namenode over http via JmxJsonServlet to collect the data nodes status.
*/ */
DecommissionStatus generateDecommissioningReport() { DecommissionStatus generateDecommissioningReport() {
String clusterid = ""; String clusterid = "";
@ -127,20 +126,17 @@ DecommissionStatus generateDecommissioningReport() {
NamenodeMXBeanHelper nnHelper = null; NamenodeMXBeanHelper nnHelper = null;
try { try {
nnHelper = new NamenodeMXBeanHelper(isa, conf); nnHelper = new NamenodeMXBeanHelper(isa, conf);
String mbeanProps= queryMbean(nnHelper.httpAddress, conf);
if (clusterid.equals("")) { if (clusterid.equals("")) {
clusterid = nnHelper.getClusterId(); clusterid = nnHelper.getClusterId(mbeanProps);
} }
nnHelper.getDecomNodeInfoForReport(statusMap); nnHelper.getDecomNodeInfoForReport(statusMap, mbeanProps);
} catch (Exception e) { } catch (Exception e) {
// catch exceptions encountered while connecting to namenodes // catch exceptions encountered while connecting to namenodes
String nnHost = isa.getHostName(); String nnHost = isa.getHostName();
decommissionExceptions.put(nnHost, e); decommissionExceptions.put(nnHost, e);
unreportedNamenode.add(nnHost); unreportedNamenode.add(nnHost);
continue; continue;
} finally {
if (nnHelper != null) {
nnHelper.cleanup();
}
} }
} }
updateUnknownStatus(statusMap, unreportedNamenode); updateUnknownStatus(statusMap, unreportedNamenode);
@ -262,38 +258,18 @@ private int getDatanodeHttpPort(Configuration conf) {
} }
/** /**
* Class for connecting to Namenode over JMX and get attributes * Class for connecting to Namenode over http via JmxJsonServlet
* exposed by the MXBean. * to get JMX attributes exposed by the MXBean.
*/ */
static class NamenodeMXBeanHelper { static class NamenodeMXBeanHelper {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
private final InetSocketAddress rpcAddress;
private final String host; private final String host;
private final Configuration conf; private final String httpAddress;
private final JMXConnector connector;
private final NameNodeMXBean mxbeanProxy;
NamenodeMXBeanHelper(InetSocketAddress addr, Configuration conf) NamenodeMXBeanHelper(InetSocketAddress addr, Configuration conf)
throws IOException, MalformedObjectNameException { throws IOException, MalformedObjectNameException {
this.rpcAddress = addr;
this.host = addr.getHostName(); this.host = addr.getHostName();
this.conf = conf; this.httpAddress = DFSUtil.getInfoServer(addr, conf, false);
int port = conf.getInt("dfs.namenode.jmxport", -1);
JMXServiceURL jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://"
+ host + ":" + port + "/jmxrmi");
connector = JMXConnectorFactory.connect(jmxURL);
mxbeanProxy = getNamenodeMxBean();
}
private NameNodeMXBean getNamenodeMxBean()
throws IOException, MalformedObjectNameException {
// Get an MBeanServerConnection on the remote VM.
MBeanServerConnection remote = connector.getMBeanServerConnection();
ObjectName mxbeanName = new ObjectName(
"Hadoop:service=NameNode,name=NameNodeInfo");
return JMX.newMXBeanProxy(remote, mxbeanName, NameNodeMXBean.class);
} }
/** Get the map corresponding to the JSON string */ /** Get the map corresponding to the JSON string */
@ -305,10 +281,9 @@ private static Map<String, Map<String, Object>> getNodeMap(String json)
} }
/** /**
* Process JSON string returned from JMX connection to get the number of * Get the number of live datanodes.
* live datanodes.
* *
* @param json JSON output from JMX call that contains live node status. * @param json JSON string that contains live node status.
* @param nn namenode status to return information in * @param nn namenode status to return information in
*/ */
private static void getLiveNodeCount(String json, NamenodeStatus nn) private static void getLiveNodeCount(String json, NamenodeStatus nn)
@ -333,11 +308,10 @@ private static void getLiveNodeCount(String json, NamenodeStatus nn)
} }
/** /**
* Count the number of dead datanode based on the JSON string returned from * Count the number of dead datanode.
* JMX call.
* *
* @param nn namenode * @param nn namenode
* @param json JSON string returned from JMX call * @param json JSON string
*/ */
private static void getDeadNodeCount(String json, NamenodeStatus nn) private static void getDeadNodeCount(String json, NamenodeStatus nn)
throws IOException { throws IOException {
@ -358,51 +332,53 @@ private static void getDeadNodeCount(String json, NamenodeStatus nn)
} }
} }
public String getClusterId() { public String getClusterId(String props) throws IOException {
return mxbeanProxy.getClusterId(); return getProperty(props, "ClusterId").getTextValue();
} }
public NamenodeStatus getNamenodeStatus() public NamenodeStatus getNamenodeStatus(String props) throws IOException,
throws IOException, MalformedObjectNameException { MalformedObjectNameException, NumberFormatException {
NamenodeStatus nn = new NamenodeStatus(); NamenodeStatus nn = new NamenodeStatus();
nn.host = host; nn.host = host;
nn.filesAndDirectories = mxbeanProxy.getTotalFiles(); nn.filesAndDirectories = getProperty(props, "TotalFiles").getLongValue();
nn.capacity = mxbeanProxy.getTotal(); nn.capacity = getProperty(props, "Total").getLongValue();
nn.free = mxbeanProxy.getFree(); nn.free = getProperty(props, "Free").getLongValue();
nn.bpUsed = mxbeanProxy.getBlockPoolUsedSpace(); nn.bpUsed = getProperty(props, "BlockPoolUsedSpace").getLongValue();
nn.nonDfsUsed = mxbeanProxy.getNonDfsUsedSpace(); nn.nonDfsUsed = getProperty(props, "NonDfsUsedSpace").getLongValue();
nn.blocksCount = mxbeanProxy.getTotalBlocks(); nn.blocksCount = getProperty(props, "TotalBlocks").getLongValue();
nn.missingBlocksCount = mxbeanProxy.getNumberOfMissingBlocks(); nn.missingBlocksCount = getProperty(props, "NumberOfMissingBlocks")
nn.free = mxbeanProxy.getFree(); .getLongValue();
nn.httpAddress = DFSUtil.getInfoServer(rpcAddress, conf, false); nn.httpAddress = httpAddress;
getLiveNodeCount(mxbeanProxy.getLiveNodes(), nn); getLiveNodeCount(getProperty(props, "LiveNodes").getValueAsText(), nn);
getDeadNodeCount(mxbeanProxy.getDeadNodes(), nn); getDeadNodeCount(getProperty(props, "DeadNodes").getValueAsText(), nn);
return nn; return nn;
} }
/** /**
* Connect to namenode to get decommission node information. * Get the decommission node information.
* @param statusMap data node status map * @param statusMap data node status map
* @param connector JMXConnector * @param props string
*/ */
private void getDecomNodeInfoForReport( private void getDecomNodeInfoForReport(
Map<String, Map<String, String>> statusMap) throws IOException, Map<String, Map<String, String>> statusMap, String props)
MalformedObjectNameException { throws IOException, MalformedObjectNameException {
getLiveNodeStatus(statusMap, host, mxbeanProxy.getLiveNodes()); getLiveNodeStatus(statusMap, host, getProperty(props, "LiveNodes")
getDeadNodeStatus(statusMap, host, mxbeanProxy.getDeadNodes()); .getValueAsText());
getDecommissionNodeStatus(statusMap, host, mxbeanProxy.getDecomNodes()); getDeadNodeStatus(statusMap, host, getProperty(props, "DeadNodes")
.getValueAsText());
getDecommissionNodeStatus(statusMap, host,
getProperty(props, "DecomNodes").getValueAsText());
} }
/** /**
* Process the JSON string returned from JMX call to get live datanode * Store the live datanode status information into datanode status map and
* status. Store the information into datanode status map and * DecommissionNode.
* Decommissionnode.
* *
* @param statusMap Map of datanode status. Key is datanode, value * @param statusMap Map of datanode status. Key is datanode, value
* is an inner map whose key is namenode, value is datanode status. * is an inner map whose key is namenode, value is datanode status.
* reported by each namenode. * reported by each namenode.
* @param namenodeHost host name of the namenode * @param namenodeHost host name of the namenode
* @param decomnode update Decommissionnode with alive node status * @param decomnode update DecommissionNode with alive node status
* @param json JSON string contains datanode status * @param json JSON string contains datanode status
* @throws IOException * @throws IOException
*/ */
@ -434,15 +410,14 @@ private static void getLiveNodeStatus(
} }
/** /**
* Process the JSON string returned from JMX connection to get the dead * Store the dead datanode information into datanode status map and
* datanode information. Store the information into datanode status map and * DecommissionNode.
* Decommissionnode.
* *
* @param statusMap map with key being datanode, value being an * @param statusMap map with key being datanode, value being an
* inner map (key:namenode, value:decommisionning state). * inner map (key:namenode, value:decommisionning state).
* @param host datanode hostname * @param host datanode hostname
* @param decomnode * @param decomnode DecommissionNode
* @param json * @param json String
* @throws IOException * @throws IOException
*/ */
private static void getDeadNodeStatus( private static void getDeadNodeStatus(
@ -478,14 +453,13 @@ private static void getDeadNodeStatus(
} }
/** /**
* We process the JSON string returned from JMX connection to get the * Get the decommisioning datanode information.
* decommisioning datanode information.
* *
* @param dataNodeStatusMap map with key being datanode, value being an * @param dataNodeStatusMap map with key being datanode, value being an
* inner map (key:namenode, value:decommisionning state). * inner map (key:namenode, value:decommisionning state).
* @param host datanode * @param host datanode
* @param decomnode Decommissionnode * @param decomnode DecommissionNode
* @param json JSON string returned from JMX connection * @param json String
*/ */
private static void getDecommissionNodeStatus( private static void getDecommissionNodeStatus(
Map<String, Map<String, String>> dataNodeStatusMap, String host, Map<String, Map<String, String>> dataNodeStatusMap, String host,
@ -508,19 +482,6 @@ private static void getDecommissionNodeStatus(
dataNodeStatusMap.put(dn, nnStatus); dataNodeStatusMap.put(dn, nnStatus);
} }
} }
public void cleanup() {
if (connector != null) {
try {
connector.close();
} catch (Exception e) {
// log failure of close jmx connection
LOG.warn("Unable to close JMX connection. "
+ StringUtils.stringifyException(e));
}
}
}
} }
/** /**
@ -893,4 +854,47 @@ private static void createGeneralException(XMLOutputter doc,
doc.endTag(); // message doc.endTag(); // message
doc.endTag(); // cluster doc.endTag(); // cluster
} }
/**
* Read in the content from a URL
* @param url URL To read
* @return the text from the output
* @throws IOException if something went wrong
*/
private static String readOutput(URL url) throws IOException {
StringBuilder out = new StringBuilder();
URLConnection connection = url.openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
out.append(inputLine);
}
in.close();
return out.toString();
}
private static String queryMbean(String httpAddress, Configuration conf)
throws IOException {
URL url = new URL("http://"+httpAddress+JMX_QRY);
return readOutput(url);
}
/**
* In order to query a namenode mxbean, a http connection in the form of
* "http://hostname/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo"
* is sent to namenode. JMX attributes are exposed via JmxJsonServelet on
* the namenode side.
*/
private static JsonNode getProperty(String props, String propertyname)
throws IOException {
if (props == null || props.equals("") || propertyname == null
|| propertyname.equals("")) {
return null;
}
ObjectMapper m = new ObjectMapper();
JsonNode rootNode = m.readValue(props, JsonNode.class);
JsonNode jn = rootNode.get("beans").get(0).get(propertyname);
return jn;
}
} }