azkaban-uncached

Http that calls JMX including the remote executor services.

4/5/2013 3:50:30 AM

Details

diff --git a/src/java/azkaban/execapp/AzkabanExecutorServer.java b/src/java/azkaban/execapp/AzkabanExecutorServer.java
index 7c25028..4f5fd02 100644
--- a/src/java/azkaban/execapp/AzkabanExecutorServer.java
+++ b/src/java/azkaban/execapp/AzkabanExecutorServer.java
@@ -22,8 +22,10 @@ import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.TimeZone;
 
+import javax.management.MBeanInfo;
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
 
@@ -92,8 +94,8 @@ public class AzkabanExecutorServer {
 		Context root = new Context(server, "/", Context.SESSIONS);
 		root.setMaxFormContentSize(MAX_FORM_CONTENT_SIZE);
 		
-		ServletHolder executorHolder = new ServletHolder(new ExecutorServlet());
-		root.addServlet(executorHolder, "/executor");
+		root.addServlet(new ServletHolder(new ExecutorServlet()), "/executor");
+		root.addServlet(new ServletHolder(new JMXHttpServlet()), "/jmx");
 		root.setAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY, this);
 		
 		
@@ -322,4 +324,26 @@ public class AzkabanExecutorServer {
 		}
 
 	}
+	
+	public List<ObjectName> getMbeanNames() {
+		return registeredMBeans;
+	}
+	
+	public MBeanInfo getMBeanInfo(ObjectName name) {
+		try {
+			return mbeanServer.getMBeanInfo(name);
+		} catch (Exception e) {
+			logger.error(e);
+			return null;
+		}
+	}
+	
+	public Object getMBeanAttribute(ObjectName name, String attribute) {
+		 try {
+			return mbeanServer.getAttribute(name, attribute);
+		} catch (Exception e) {
+			logger.error(e);
+			return null;
+		}
+	}
 }
\ No newline at end of file
diff --git a/src/java/azkaban/execapp/JMXHttpServlet.java b/src/java/azkaban/execapp/JMXHttpServlet.java
new file mode 100644
index 0000000..0ee2ebc
--- /dev/null
+++ b/src/java/azkaban/execapp/JMXHttpServlet.java
@@ -0,0 +1,73 @@
+package azkaban.execapp;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.ObjectName;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import azkaban.executor.ConnectorParams;
+import azkaban.utils.JSONUtils;
+import azkaban.webapp.servlet.AzkabanServletContextListener;
+import azkaban.webapp.servlet.HttpRequestUtils;
+
+public class JMXHttpServlet extends HttpServlet implements ConnectorParams {
+	private static final long serialVersionUID = -3085603824826446270L;
+	private static final Logger logger = Logger.getLogger(JMXHttpServlet.class);
+	private AzkabanExecutorServer server;
+	
+	public void init(ServletConfig config) throws ServletException {
+		server = (AzkabanExecutorServer) config.getServletContext().getAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY);
+	}
+	
+	public boolean hasParam(HttpServletRequest request, String param) {
+		return HttpRequestUtils.hasParam(request, param);
+	}
+	
+	public String getParam(HttpServletRequest request, String name) throws ServletException {
+		return HttpRequestUtils.getParam(request, name);
+	}
+	
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+		HashMap<String,Object> ret = new HashMap<String,Object>();
+
+		if (hasParam(req, JMX_GET_MBEANS)) {
+			ret.put("mbeans", server.getMbeanNames());
+		}
+		else if (hasParam(req, JMX_GET_ALL_MBEAN_ATTRIBUTES)) {
+			if (!hasParam(req, JMX_MBEAN)) {
+				ret.put("error", "Parameters 'mbean' must be set");
+			}
+			else {
+				String mbeanName = getParam(req, JMX_MBEAN);
+				try {
+					ObjectName name = new ObjectName(mbeanName);
+					MBeanInfo info = server.getMBeanInfo(name);
+					
+					MBeanAttributeInfo[] mbeanAttrs = info.getAttributes();
+					HashMap<String, Object> attributes = new HashMap<String,Object>();
+
+					for (MBeanAttributeInfo attrInfo: mbeanAttrs) {
+						Object obj = server.getMBeanAttribute(name, attrInfo.getName());
+						attributes.put(attrInfo.getName(), obj);
+					}
+					
+					ret.put("attributes", attributes);
+				} catch (Exception e) {
+					logger.error(e);
+					ret.put("error", "'" + mbeanName + "' is not a valid mBean name");
+				}
+			}
+		}
+
+		JSONUtils.toJSON(ret, resp.getOutputStream(), true);
+	}
+}
diff --git a/src/java/azkaban/executor/ConnectorParams.java b/src/java/azkaban/executor/ConnectorParams.java
index 7bac5d1..8ee9250 100644
--- a/src/java/azkaban/executor/ConnectorParams.java
+++ b/src/java/azkaban/executor/ConnectorParams.java
@@ -72,4 +72,14 @@ public interface ConnectorParams {
 	public static final String UPDATE_MAP_START_TIME = "startTime";
 	public static final String UPDATE_MAP_END_TIME = "endTime";
 	public static final String UPDATE_MAP_NODES = "nodes";
+	
+	public static final String JMX_GET_MBEANS = "getMBeans";
+	public static final String JMX_GET_MBEAN_INFO = "getMBeanInfo";
+	public static final String JMX_GET_MBEAN_ATTRIBUTE = "getAttribute";
+	public static final String JMX_GET_ALL_MBEAN_ATTRIBUTES = "getAllMBeanAttributes";
+	public static final String JMX_ATTRIBUTE = "attribute";
+	public static final String JMX_MBEAN = "mBean";
+	
+	public static final String JMX_GET_ALL_EXECUTOR_ATTRIBUTES = "getAllExecutorAttributes";
+	public static final String JMX_HOSTPORT = "hostPort";
 }
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index 76f76ec..c724792 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -23,8 +23,10 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.commons.lang.StringUtils;
@@ -105,6 +107,25 @@ public class ExecutorManager {
 		return this.lastCleanerThreadCheckTime;
 	}
 	
+	public Set<String> getPrimaryServerHosts() {
+		// Only one for now. More probably later.
+		HashSet<String> ports = new HashSet<String>();
+		ports.add(executorHost + ":" + executorPort);
+		return ports;
+	}
+	
+	public Set<String> getAllActiveExecutorServerHosts() {
+		// Includes non primary server/hosts
+		HashSet<String> ports = new HashSet<String>();
+		ports.add(executorHost + ":" + executorPort);
+		for(Pair<ExecutionReference, ExecutableFlow> running: runningFlows.values()) {
+			ExecutionReference ref = running.getFirst();
+			ports.add(ref.getHost() + ":" + ref.getPort());
+		}
+		
+		return ports;
+	}
+	
 	private void loadRunningFlows() throws ExecutorManagerException {
 		runningFlows.putAll(executorLoader.fetchActiveFlows());
 	}
@@ -472,6 +493,51 @@ public class ExecutorManager {
 		return jsonResponse;
 	}
 	
+	public Map<String, Object> callExecutorJMX(String hostPort, String action, String mBean) throws IOException {
+		URIBuilder builder = new URIBuilder();
+		
+		String[] hostPortSplit = hostPort.split(":");
+		builder.setScheme("http")
+			.setHost(hostPortSplit[0])
+			.setPort(Integer.parseInt(hostPortSplit[1]))
+			.setPath("/jmx");
+
+		builder.setParameter(action, "");
+		if (mBean != null) {
+			builder.setParameter(ConnectorParams.JMX_MBEAN, mBean);
+		}
+
+		URI uri = null;
+		try {
+			uri = builder.build();
+		} catch (URISyntaxException e) {
+			throw new IOException(e);
+		}
+		
+		ResponseHandler<String> responseHandler = new BasicResponseHandler();
+		
+		HttpClient httpclient = new DefaultHttpClient();
+		HttpGet httpget = new HttpGet(uri);
+		String response = null;
+		try {
+			response = httpclient.execute(httpget, responseHandler);
+		} catch (IOException e) {
+			throw e;
+		}
+		finally {
+			httpclient.getConnectionManager().shutdown();
+		}
+		
+		System.out.println(response);
+		@SuppressWarnings("unchecked")
+		Map<String, Object> jsonResponse = (Map<String, Object>)JSONUtils.parseJSONFromString(response);
+		String error = (String)jsonResponse.get(ConnectorParams.RESPONSE_ERROR);
+		if (error != null) {
+			throw new IOException(error);
+		}
+		return jsonResponse;
+	}
+	
 	public void shutdown() {
 		executingManager.shutdown();
 	}
diff --git a/src/java/azkaban/jmx/JmxExecutorManager.java b/src/java/azkaban/jmx/JmxExecutorManager.java
index 35b18a1..123340d 100644
--- a/src/java/azkaban/jmx/JmxExecutorManager.java
+++ b/src/java/azkaban/jmx/JmxExecutorManager.java
@@ -1,5 +1,8 @@
 package azkaban.jmx;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import azkaban.executor.ExecutorManager;
 
 public class JmxExecutorManager implements JmxExecutorManagerMBean {
@@ -28,4 +31,9 @@ public class JmxExecutorManager implements JmxExecutorManagerMBean {
 	public Long getLastThreadCheckTime() {
 		return manager.getLastThreadCheckTime();
 	}
+	
+	@Override 
+	public List<String> getPrimaryExecutorHostPorts() {
+		return new ArrayList<String>(manager.getPrimaryServerHosts());
+	}
 }
diff --git a/src/java/azkaban/jmx/JmxExecutorManagerMBean.java b/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
index d5649a4..b4a3888 100644
--- a/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
+++ b/src/java/azkaban/jmx/JmxExecutorManagerMBean.java
@@ -1,5 +1,7 @@
 package azkaban.jmx;
 
+import java.util.List;
+
 public interface JmxExecutorManagerMBean {
 	@DisplayName("OPERATION: getNumRunningFlows")
 	public int getNumRunningFlows();
@@ -12,4 +14,7 @@ public interface JmxExecutorManagerMBean {
 
 	@DisplayName("OPERATION: getLastThreadCheckTime")
 	public Long getLastThreadCheckTime();
+
+	@DisplayName("OPERATION: getPrimaryExecutorHostPorts")
+	public List<String> getPrimaryExecutorHostPorts();
 }
diff --git a/src/java/azkaban/webapp/servlet/JMXHttpServlet.java b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
index 6bd1e94..27bbb3f 100644
--- a/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
+++ b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
@@ -1,10 +1,11 @@
 package azkaban.webapp.servlet;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
+import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanInfo;
 import javax.management.ObjectName;
 import javax.servlet.ServletConfig;
@@ -14,17 +15,20 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.log4j.Logger;
 
+import azkaban.executor.ConnectorParams;
+import azkaban.executor.ExecutorManager;
 import azkaban.user.Permission;
 import azkaban.user.Role;
 import azkaban.user.User;
 import azkaban.user.UserManager;
+import azkaban.utils.Pair;
 import azkaban.webapp.AzkabanWebServer;
 import azkaban.webapp.session.Session;
 
 /**
  * Limited set of jmx calls for when you cannot attach to the jvm
  */
-public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
+public class JMXHttpServlet extends LoginAbstractAzkabanServlet implements ConnectorParams {
 	/**
 	 * 
 	 */
@@ -34,12 +38,7 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 
 	private UserManager userManager;
 	private AzkabanWebServer server;
-	
-	private static final String GET_MBEANS = "getMBeans";
-	private static final String GET_MBEAN_INFO = "getMBeanInfo";
-	private static final String GET_MBEAN_ATTRIBUTE = "getAttribute";
-	private static final String ATTRIBUTE = "attribute";
-	private static final String MBEAN = "mBean";
+	private ExecutorManager executorManager;
 	
 	@Override
 	public void init(ServletConfig config) throws ServletException {
@@ -47,23 +46,38 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 		
 		server = (AzkabanWebServer)getApplication();
 		userManager = server.getUserManager();
+		executorManager = server.getExecutorManager();
 	}
 	
 	@Override
 	protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
 		if (hasParam(req, "ajax")){
-			HashMap<String,Object> ret = new HashMap<String,Object>();
+			Map<String,Object> ret = new HashMap<String,Object>();
 
 			if(!hasAdminRole(session.getUser())) {
 				ret.put("error", "User " + session.getUser().getUserId() + " has no permission.");
+				this.writeJSON(resp, ret, true);
+				return;
 			}
 			String ajax = getParam(req, "ajax");
-			if (GET_MBEANS.equals(ajax)) {
+			if (JMX_GET_ALL_EXECUTOR_ATTRIBUTES.equals(ajax)) {
+				if (!hasParam(req, JMX_MBEAN) || !hasParam(req, JMX_HOSTPORT)) {
+					ret.put("error", "Parameters '" + JMX_MBEAN + "' and '"+ JMX_HOSTPORT +"' must be set");
+					this.writeJSON(resp, ret, true);
+					return;
+				}
+				
+				String hostPort = getParam(req, JMX_HOSTPORT);
+				String mbean = getParam(req, JMX_MBEAN);
+				Map<String, Object> result = executorManager.callExecutorJMX(hostPort, JMX_GET_ALL_MBEAN_ATTRIBUTES, mbean);
+				ret = result;
+			}
+			else if (JMX_GET_MBEANS.equals(ajax)) {
 				ret.put("mbeans", server.getMbeanNames());
 			}
-			else if (GET_MBEAN_INFO.equals(ajax)) {
-				if (hasParam(req, MBEAN)) {
-					String mbeanName = getParam(req, MBEAN);
+			else if (JMX_GET_MBEAN_INFO.equals(ajax)) {
+				if (hasParam(req, JMX_MBEAN)) {
+					String mbeanName = getParam(req, JMX_MBEAN);
 					try {
 						ObjectName name = new ObjectName(mbeanName);
 						MBeanInfo info = server.getMBeanInfo(name);
@@ -78,13 +92,13 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 					ret.put("error", "No 'mbean' name parameter specified" );
 				}
 			}
-			else if (GET_MBEAN_ATTRIBUTE.equals(ajax)) {
-				if (!hasParam(req, MBEAN) || !hasParam(req, ATTRIBUTE)) {
+			else if (JMX_GET_MBEAN_ATTRIBUTE.equals(ajax)) {
+				if (!hasParam(req, JMX_MBEAN) || !hasParam(req, JMX_ATTRIBUTE)) {
 					ret.put("error", "Parameters 'mbean' and 'attribute' must be set");
 				}
 				else {
-					String mbeanName = getParam(req, MBEAN);
-					String attribute = getParam(req, ATTRIBUTE);
+					String mbeanName = getParam(req, JMX_MBEAN);
+					String attribute = getParam(req, JMX_ATTRIBUTE);
 					
 					try {
 						ObjectName name = new ObjectName(mbeanName);
@@ -96,11 +110,36 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 					}
 				}
 			}
+			else if (JMX_GET_ALL_MBEAN_ATTRIBUTES.equals(ajax)) {
+				if (!hasParam(req, JMX_MBEAN)) {
+					ret.put("error", "Parameters 'mbean' must be set");
+				}
+				else {
+					String mbeanName = getParam(req, JMX_MBEAN);
+					try {
+						ObjectName name = new ObjectName(mbeanName);
+						MBeanInfo info = server.getMBeanInfo(name);
+						
+						MBeanAttributeInfo[] mbeanAttrs = info.getAttributes();
+						HashMap<String, Object> attributes = new HashMap<String,Object>();
+
+						for (MBeanAttributeInfo attrInfo: mbeanAttrs) {
+							Object obj = server.getMBeanAttribute(name, attrInfo.getName());
+							attributes.put(attrInfo.getName(), obj);
+						}
+						
+						ret.put("attributes", attributes);
+					} catch (Exception e) {
+						logger.error(e);
+						ret.put("error", "'" + mbeanName + "' is not a valid mBean name");
+					}
+				}
+			}
 			else {
 				ret.put("commands", new String[] {
-						GET_MBEANS, 
-						GET_MBEAN_INFO+"&"+MBEAN+"=<name>", 
-						GET_MBEAN_ATTRIBUTE+"&"+MBEAN+"=<name>&"+ATTRIBUTE+"=<attributename>"}
+						JMX_GET_MBEANS, 
+						JMX_GET_MBEAN_INFO+"&"+JMX_MBEAN+"=<name>", 
+						JMX_GET_MBEAN_ATTRIBUTE+"&"+JMX_MBEAN+"=<name>&"+JMX_ATTRIBUTE+"=<attributename>"}
 				);
 			}
 			this.writeJSON(resp, ret, true);
@@ -110,7 +149,7 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 		}
 	}
 
-	private void handleJMXPage(HttpServletRequest req, HttpServletResponse resp, Session session) {
+	private void handleJMXPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws IOException {
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/jmxpage.vm");
 		
 		if(!hasAdminRole(session.getUser())) {
@@ -120,7 +159,21 @@ public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
 		}
 
 		page.add("mbeans", server.getMbeanNames());
+		
+		Map<String, Object> executorMBeans = new HashMap<String,Object>();
+		Set<String> primaryServerHosts = executorManager.getPrimaryServerHosts();
+		for (String hostPort: executorManager.getAllActiveExecutorServerHosts()) {
+			Map<String, Object> mbeans = executorManager.callExecutorJMX(hostPort, JMX_GET_MBEANS, null);
 
+			if (primaryServerHosts.contains(hostPort)) {
+				executorMBeans.put(hostPort, mbeans.get("mbeans"));
+			}
+			else {
+				executorMBeans.put(hostPort, mbeans.get("mbeans"));
+			}
+		}
+		
+		page.add("remoteMBeans", executorMBeans);
 		page.render();
 	}
 	
diff --git a/src/java/azkaban/webapp/servlet/velocity/index.vm b/src/java/azkaban/webapp/servlet/velocity/index.vm
index d884bb7..46e2ed0 100644
--- a/src/java/azkaban/webapp/servlet/velocity/index.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/index.vm
@@ -92,7 +92,7 @@
 						<td class="tb-owner">$project.lastModifiedUser</td>
 					</tr>
 					<tr class="childrow" id="${project.name}-child" style="display: none;">
-						<td class="expandedFlow">
+						<td class="expandedFlow" colspan="3">
 							<table class="innerTable">
 								<thead>
 									<tr><th class="tb-name">Flows</th></tr>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
index 64fcc9c..2a0e945 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
@@ -23,6 +23,7 @@
 		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
 		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.jmx.view.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -33,6 +34,7 @@
 	</head>
 	<body>
 #set($current_page="all")
+#set($counter=0)
 #parse( "azkaban/webapp/servlet/velocity/nav.vm" )
 #if($errorMsg)
 <div class="box-error-message"><pre>$errorMsg</pre></div>
@@ -40,47 +42,95 @@
 
 		<div class="content">
 			<div id="all-jobs-content">
-				<div class="section-hd flow-header">
+				<div class="section-hd">
 					<h2><a href="${context}/jmx">Admin JMX Http Page</span></a></h2>
 				</div>
 			</div>
-			
-			<div id="flow-tabs">
-				<table id="all-jobs" class="all-jobs job-table">
-					<thead>
-						<tr>
-							<th>Name</th>
-							<th>Domain</th>
-							<th>Canonical Name</th>
-							<th></th>
-						</tr>
-					</thead>
-					<tbody>
+			<h3 class="subhead">Web Client JMX</h3>
+
+			<table id="all-jobs" class="all-jobs job-table">
+				<thead>
+					<tr>
+						<th>Name</th>
+						<th>Domain</th>
+						<th>Canonical Name</th>
+						<th></th>
+					</tr>
+				</thead>
+				<tbody>
 #foreach($bean in $mbeans)
+				<tr>
+					<td>${bean.keyPropertyList.get("name")}</td>
+					<td>${bean.domain}</td>
+					<td>${bean.canonicalName}</td>
+					<td><div class="btn4 querybtn" id="expandBtn-$counter" domain="${bean.domain}" name="${bean.keyPropertyList.get("name")}">Query</div></td>
+				</tr>
+
+				<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
+					<td class="expandedFlow" colspan="3">
+						<table class="innerTable">
+							<thead>
+								<tr>
+									<th>Attribute Name</th>
+									<th>Value</th>
+								</tr>
+							</thead>
+							<tbody id="expandBtn-${counter}-tbody">
+							</tbody>
+						</table>
+					</td>
+
+					<td>
+						<div class="btn4 collapse">Collapse</div>
+					</td>
+				</tr>
+#set($counter=$counter + 1)
+#end
+				</tbody>
+			</table>
+
+#foreach($executor in $remoteMBeans.entrySet())
+			<h3 class="subhead">Remote Executor JMX $executor.key</h3>
+			<table class="all-jobs job-table remoteJMX">
+				<thead>
 					<tr>
-						<td>${bean.keyPropertyList.get("name")}</td>
-						<td>${bean.domain}</td>
-						<td>${bean.canonicalName}</td>
-						<td><div class="btn4 querybtn" id="${bean.canonicalName}">Query</div></td>
+						<th>Name</th>
+						<th>Domain</th>
+						<th>Canonical Name</th>
+						<th></th>
 					</tr>
-					<tr class="childrow" id="${bean.canonicalName}-child" style="display: none;">
-						<td class="expandedFlow">
+				</thead>
+				<tbody>
+					#foreach($bean in $executor.value)
+						<tr>
+							<td>${bean.get("keyPropertyList").get("name")}</td>
+							<td>${bean.get("domain")}</td>
+							<td>${bean.get("canonicalName")}</td>
+							<td><div class="btn4 querybtn" id="expandBtn-$counter" domain="${bean.get("domain")}" name="${bean.get("keyPropertyList").get("name")}" hostport="$executor.key">Query</div></td>
+						</tr>
+					<tr class="childrow" id="expandBtn-${counter}-child"  style="display: none;">
+						<td class="expandedFlow" colspan="3">
 							<table class="innerTable">
 								<thead>
 									<tr>
-										<th class="tb-name">Attribute Name</th>
+										<th>Attribute Name</th>
 										<th>Value</th>
 									</tr>
 								</thead>
-								<tbody id="${bean.canonicalName}-tbody">
+								<tbody id="expandBtn-${counter}-tbody">
 								</tbody>
 							</table>
 						</td>
-					</tr>
+
+						<td>
+							<div class="btn4 collapse">Collapse</div>
+						</td>
+				</tr>
+				#set($counter=$counter + 1)
+					#end 
+				</tbody>
+			</table>
 #end
-					</tbody>
-				</table>
-			</div>
 		</div>
 	</body>
 </html>
diff --git a/src/web/js/azkaban.jmx.view.js b/src/web/js/azkaban.jmx.view.js
index 5c9193a..7309c07 100644
--- a/src/web/js/azkaban.jmx.view.js
+++ b/src/web/js/azkaban.jmx.view.js
@@ -1,189 +1,108 @@
 $.namespace('azkaban');
 
-var projectTableView;
-azkaban.ProjectTableView= Backbone.View.extend({
+var jmxTableView;
+azkaban.JMXTableView= Backbone.View.extend({
   events : {
-    "click .project-expand": "expandProject"
+    "click .querybtn": "queryJMX",
+    "click .collapse": "collapseRow"
   },
   initialize : function(settings) {
 
   },
-  expandProject : function(evt) {
-    if (evt.target.tagName!="SPAN") {
-    	return;
-    }
-    
-    var target = evt.currentTarget;
-    var targetId = target.id;
-    var requestURL = contextURL + "/manager";
-    
-    var targetExpanded = $('#' + targetId + '-child');
-    var targetTBody = $('#' + targetId + '-tbody');
-    
-    var createFlowListFunction = this.createFlowListTable;
-    
-    if (target.loading) {
-    	console.log("Still loading.");
-    }
-    else if (target.loaded) {
-    	if($(targetExpanded).is(':visible')) {
-    		$(target).addClass('expand').removeClass('collapse');
-    		$(targetExpanded).fadeOut("fast");
-    	}
-    	else {
-    	    $(target).addClass('collapse').removeClass('expand');
-    		$(targetExpanded).fadeIn();
-    	}
-    }
-    else {
-	    // projectId is available
-	    $(target).addClass('wait').removeClass('collapse').removeClass('expand');
-	    target.loading = true;
-	    
-	    $.get(
-	      requestURL,
-	      {"project": targetId, "ajax":"fetchprojectflows"},
-	      function(data) {
-	        console.log("Success");
-	        target.loaded = true;
-	        target.loading = false;
-	        
-	        createFlowListFunction(data, targetTBody);
-	        
-			$(target).addClass('collapse').removeClass('wait');
-	    	$(targetExpanded).fadeIn("fast");
-	      },
-	      "json"
-	    );
-    }
-  },
-  render: function() {
-  },
-  createFlowListTable : function(data, innerTable) {
-  	var flows = data.flows;
-  	flows.sort(function(a,b){return a.flowId.localeCompare(b.flowId);});
-  	
-  	var requestURL = contextURL + "/manager?project=" + data.project + "&flow=";
-  	for (var i = 0; i < flows.length; ++i) {
-  		var id = flows[i].flowId;
-  		
-  		var tr = document.createElement("tr");
-		var idtd = document.createElement("td");
-		$(idtd).addClass("tb-name");
-  		
-  		var ida = document.createElement("a");
-		ida.project = data.project;
-		$(ida).text(id);
-		$(ida).attr("href", requestURL + id);
-		
-		$(idtd).append(ida);
-		$(tr).append(idtd);
-		$(innerTable).append(tr);
-  	}
-  }
-});
-
-var projectHeaderView;
-azkaban.ProjectHeaderView= Backbone.View.extend({
-  events : {
-    "click #create-project-btn":"handleCreateProjectJob"
-  },
-  initialize : function(settings) {
-    if (settings.errorMsg && settings.errorMsg != "null") {
-      // Chrome bug in displaying placeholder text. Need to hide the box.
-      $('#searchtextbox').hide();
-      $('.messaging').addClass("error");
-      $('.messaging').removeClass("success");
-      $('.messaging').html(settings.errorMsg);
-    }
-    else if (settings.successMsg && settings.successMsg != "null") {
-      $('#searchtextbox').hide();
-      $('.messaging').addClass("success");
-      $('.messaging').removeClass("error");
-      $('#message').html(settings.successMsg);
-    }
-    else {
-      $('#searchtextbox').show();
-      $('.messaging').removeClass("success");
-      $('.messaging').removeClass("error");
-    }
-    
-    $('#messageClose').click(function() {
-      $('#searchtextbox').show();
-      
-      $('.messaging').slideUp('fast', function() {
-        $('.messaging').removeClass("success");
-        $('.messaging').removeClass("error");
-      });
-    });
-  },
-  handleCreateProjectJob : function(evt) {
-    console.log("click create project");
-      $('#create-project').modal({
-          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-          position: ["20%",],
-          containerId: 'confirm-container',
-          containerCss: {
-            'height': '220px',
-            'width': '565px'
-          },
-          onShow: function (dialog) {
-            var modal = this;
-            $("#errorMsg").hide();
-          }
-        });
-  },
-  render: function() {
-  }
-});
+  queryJMX : function(evt) {
+	var target = evt.currentTarget;
+	var id = target.id;
+	
+	var childID = id + "-child";
+	var tbody = id + "-tbody";
+	
+	var requestURL = contextURL + "/jmx";
+	var canonicalName=$(target).attr("domain") + ":name=" + $(target).attr("name");
 
-var createProjectView;
-azkaban.CreateProjectView= Backbone.View.extend({
-  events : {
-    "click #create-btn": "handleCreateProject"
+	var data = {"ajax":"getAllMBeanAttributes", "mBean":canonicalName};
+	if ($(target).attr("hostPort")) {
+		data.ajax = "getAllExecutorAttributes";
+		data.hostPort = $(target).attr("hostPort");
+	}
+	$.get(
+		requestURL,
+		data,
+		function(data) {
+			var table = $('#' + tbody);
+			$(table).empty();
+			
+			for(var key in data.attributes) {
+				var value = data.attributes[key];
+				
+				var tr = document.createElement("tr");
+				var tdName = document.createElement("td");
+				var tdVal = document.createElement("td");
+				
+				$(tdName).text(key);
+				$(tdVal).text(value);
+				
+				$(tr).append(tdName);
+				$(tr).append(tdVal);
+				
+				$('#' + tbody).append(tr);
+			}
+			
+			var child = $("#" + childID);
+	    	$(child).fadeIn();
+		}
+	);
   },
-  initialize : function(settings) {
-    $("#errorMsg").hide();
+  queryRemote : function(evt) {
+	var target = evt.currentTarget;
+	var id = target.id;
+	
+	var childID = id + "-child";
+	var tbody = id + "-tbody";
+	
+	var requestURL = contextURL + "/jmx";
+	var canonicalName=$(target).attr("domain") + ":name=" + $(target).attr("name");
+	var hostPort = $(target).attr("hostport");
+	$.get(
+		requestURL,
+		{"ajax":"getAllExecutorAttributes", "mBean":canonicalName, "hostPort": hostPort},
+		function(data) {
+			var table = $('#' + tbody);
+			$(table).empty();
+			
+			for(var key in data.attributes) {
+				var value = data.attributes[key];
+				
+				var tr = document.createElement("tr");
+				var tdName = document.createElement("td");
+				var tdVal = document.createElement("td");
+				
+				$(tdName).text(key);
+				$(tdVal).text(value);
+				
+				$(tr).append(tdName);
+				$(tr).append(tdVal);
+				
+				$('#' + tbody).append(tr);
+			}
+			
+			var child = $("#" + childID);
+	    	$(child).fadeIn();
+		}
+	);
   },
-  handleCreateProject : function(evt) {
-	 // First make sure we can upload
-	 var projectName = $('#path').val();
-	 var description = $('#description').val();
-
-     console.log("Creating");
-     $.ajax({
-     	async: "false",
-     	url: "manager",
-     	dataType: "json",
-     	type: "POST",
-     	data: {action:"create", name:projectName, description:description},
-     	success: function(data) {
-     		if (data.status == "success") {
-     			if (data.action == "redirect") {
-     				window.location = data.path;
-     			}
-     		}
-     		else {
-     			if (data.action == "login") {
- 					window.location = "";
-     			}
-     			else {
-	     			$("#errorMsg").text("ERROR: " + data.message);
-	    			$("#errorMsg").slideDown("fast");
-    			}
-     		}
-     	}
-     });
-
+  collapseRow: function(evt) {
+  	$(evt.currentTarget).parent().parent().fadeOut();
   },
   render: function() {
   }
 });
 
-var tableSorterView;
+var remoteTables = new Array();
 $(function() {
-	projectHeaderView = new azkaban.ProjectHeaderView({el:$( '#all-jobs-content'), successMsg: successMessage, errorMsg: errorMessage });
-	projectTableView = new azkaban.ProjectTableView({el:$('#all-jobs')});
-	tableSorterView = new azkaban.TableSorter({el:$('#all-jobs'), initialSort: $('.tb-name')});
-	uploadView = new azkaban.CreateProjectView({el:$('#create-project')});
+	jmxTableView = new azkaban.JMXTableView({el:$('#all-jobs')});
+	
+	$(".remoteJMX").each(function(item) {
+		var newTableView = new azkaban.JMXTableView({el:$(this)});
+		remoteTables.push(newTables);
+	});
 });