azkaban-memoizeit

Using interprocess communication between Webserver and Exec

12/16/2014 7:14:39 AM

Details

diff --git a/azkaban-common/src/main/java/azkaban/executor/ConnectorParams.java b/azkaban-common/src/main/java/azkaban/executor/ConnectorParams.java
index ddd8bba..d6a9ab4 100644
--- a/azkaban-common/src/main/java/azkaban/executor/ConnectorParams.java
+++ b/azkaban-common/src/main/java/azkaban/executor/ConnectorParams.java
@@ -88,4 +88,13 @@ public interface ConnectorParams {
   public static final String JMX_GET_ALL_EXECUTOR_ATTRIBUTES =
       "getAllExecutorAttributes";
   public static final String JMX_HOSTPORT = "hostPort";
+
+  public static final String STATS_GET_ALLMETRICSNAME = "getAllMetricNames";
+  public static final String STATS_GET_METRICHISTORY = "metricHistory";
+  public static final String STATS_SET_REPORTINGINTERVAL = "changeMetricInterval";
+  public static final String STATS_MAP_METRICNAMEPARAM =  "metricName";
+  public static final String STATS_MAP_STARTDATE =  "from";
+  public static final String STATS_MAP_ENDDATE =  "to";
+    public static final String STATS_MAP_REPORTINGINTERVAL =  "interval";
+
 }
diff --git a/azkaban-common/src/main/java/azkaban/executor/ExecutorManager.java b/azkaban-common/src/main/java/azkaban/executor/ExecutorManager.java
index e3521db..8cdccbc 100644
--- a/azkaban-common/src/main/java/azkaban/executor/ExecutorManager.java
+++ b/azkaban-common/src/main/java/azkaban/executor/ExecutorManager.java
@@ -38,7 +38,6 @@ import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.log4j.Logger;
-
 import org.joda.time.DateTime;
 
 import azkaban.alert.Alerter;
@@ -719,6 +718,52 @@ public class ExecutorManager extends EventHandler implements
   }
 
   @Override
+  public Map<String, Object> callExecutorStats(String action, Pair<String, String>... params) throws IOException {
+
+    URIBuilder builder = new URIBuilder();
+    builder.setScheme("http").setHost(executorHost).setPort(executorPort).setPath("/stats");
+
+    builder.setParameter(ConnectorParams.ACTION_PARAM, action);
+
+    if (params != null) {
+      for (Pair<String, String> pair : params) {
+        builder.setParameter(pair.getFirst(), pair.getSecond());
+      }
+    }
+
+    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();
+    }
+
+    @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;
+  }
+
+
+  @Override
   public Map<String, Object> callExecutorJMX(String hostPort, String action,
       String mBean) throws IOException {
     URIBuilder builder = new URIBuilder();
diff --git a/azkaban-common/src/main/java/azkaban/executor/ExecutorManagerAdapter.java b/azkaban-common/src/main/java/azkaban/executor/ExecutorManagerAdapter.java
index c7e8913..dec133e 100644
--- a/azkaban-common/src/main/java/azkaban/executor/ExecutorManagerAdapter.java
+++ b/azkaban-common/src/main/java/azkaban/executor/ExecutorManagerAdapter.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import azkaban.project.Project;
 import azkaban.utils.FileIOUtils.JobMetaData;
 import azkaban.utils.FileIOUtils.LogData;
+import azkaban.utils.Pair;
 
 public interface ExecutorManagerAdapter {
 
@@ -164,6 +165,9 @@ public interface ExecutorManagerAdapter {
   public String submitExecutableFlow(ExecutableFlow exflow, String userId)
       throws ExecutorManagerException;
 
+  public Map<String, Object> callExecutorStats(String action,
+      Pair<String, String>... params) throws IOException;
+
   public Map<String, Object> callExecutorJMX(String hostPort, String action,
       String mBean) throws IOException;
 
diff --git a/azkaban-execserver/src/main/java/azkaban/execapp/AzkabanExecutorServer.java b/azkaban-execserver/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
index 493d745..2d08ed2 100644
--- a/azkaban-execserver/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
+++ b/azkaban-execserver/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
@@ -42,7 +42,6 @@ import azkaban.execapp.jmx.JmxFlowRunnerManager;
 import azkaban.execapp.metric.NumRunningFlowMetric;
 import azkaban.execapp.metric.NumRunningJobMetric;
 import azkaban.jmx.JmxJettyServer;
-import azkaban.metric.GangliaMetricEmitter;
 import azkaban.metric.IMetricEmitter;
 import azkaban.metric.InMemoryMetricEmitter;
 import azkaban.metric.MetricReportManager;
@@ -53,16 +52,15 @@ import azkaban.server.ServerConstants;
 import azkaban.utils.Props;
 import azkaban.utils.Utils;
 
+
 public class AzkabanExecutorServer {
-  private static final Logger logger = Logger
-      .getLogger(AzkabanExecutorServer.class);
+  private static final Logger logger = Logger.getLogger(AzkabanExecutorServer.class);
   private static final int MAX_FORM_CONTENT_SIZE = 10 * 1024 * 1024;
 
   public static final String AZKABAN_HOME = "AZKABAN_HOME";
   public static final String DEFAULT_CONF_PATH = "conf";
   public static final String AZKABAN_PROPERTIES_FILE = "azkaban.properties";
-  public static final String AZKABAN_PRIVATE_PROPERTIES_FILE =
-      "azkaban.private.properties";
+  public static final String AZKABAN_PRIVATE_PROPERTIES_FILE = "azkaban.private.properties";
   public static final String JOBTYPE_PLUGIN_DIR = "azkaban.jobtype.plugin.dir";
   public static final int DEFAULT_PORT_NUMBER = 12321;
 
@@ -108,17 +106,16 @@ public class AzkabanExecutorServer {
 
     root.addServlet(new ServletHolder(new ExecutorServlet()), "/executor");
     root.addServlet(new ServletHolder(new JMXHttpServlet()), "/jmx");
-    root.setAttribute(
-        ServerConstants.AZKABAN_SERVLET_CONTEXT_KEY, this);
+    root.addServlet(new ServletHolder(new StatsServlet()), "/stats");
+
+    root.setAttribute(ServerConstants.AZKABAN_SERVLET_CONTEXT_KEY, this);
 
     executionLoader = createExecLoader(props);
     projectLoader = createProjectLoader(props);
-    runnerManager =
-        new FlowRunnerManager(props, executionLoader, projectLoader, this
-            .getClass().getClassLoader());
+    runnerManager = new FlowRunnerManager(props, executionLoader, projectLoader, this.getClass().getClassLoader());
 
     configureMBeanServer();
-    configureMetricReports(runnerManager, props);
+    configureMetricReports();
 
     try {
       server.start();
@@ -134,8 +131,9 @@ public class AzkabanExecutorServer {
    * Configure Metric Reporting as per azkaban.properties settings
    *
    */
-  private void configureMetricReports(FlowRunnerManager runnerManager, Props props) {
-    if (props.getBoolean("executor.metric.reports", false)) {
+  private void configureMetricReports() {
+    Props props = getAzkabanProps();
+    if (props != null && props.getBoolean("executor.metric.reports", false)) {
       logger.info("Starting to configure Metric Reports");
       MetricReportManager metricManager = MetricReportManager.getInstance();
       IMetricEmitter metricEmitter = new InMemoryMetricEmitter(props);
@@ -253,16 +251,14 @@ public class AzkabanExecutorServer {
       return null;
     }
 
-    if (!new File(azkabanHome).isDirectory()
-        || !new File(azkabanHome).canRead()) {
+    if (!new File(azkabanHome).isDirectory() || !new File(azkabanHome).canRead()) {
       logger.error(azkabanHome + " is not a readable directory.");
       return null;
     }
 
     File confPath = new File(azkabanHome, DEFAULT_CONF_PATH);
     if (!confPath.exists() || !confPath.isDirectory() || !confPath.canRead()) {
-      logger
-          .error(azkabanHome + " does not contain a readable conf directory.");
+      logger.error(azkabanHome + " does not contain a readable conf directory.");
       return null;
     }
 
@@ -280,8 +276,7 @@ public class AzkabanExecutorServer {
    * @return
    */
   private static Props loadAzkabanConfigurationFromDirectory(File dir) {
-    File azkabanPrivatePropsFile =
-        new File(dir, AZKABAN_PRIVATE_PROPERTIES_FILE);
+    File azkabanPrivatePropsFile = new File(dir, AZKABAN_PRIVATE_PROPERTIES_FILE);
     File azkabanPropsFile = new File(dir, AZKABAN_PROPERTIES_FILE);
 
     Props props = null;
@@ -299,9 +294,7 @@ public class AzkabanExecutorServer {
     } catch (FileNotFoundException e) {
       logger.error("File not found. Could not load azkaban config file", e);
     } catch (IOException e) {
-      logger.error(
-          "File found, but error reading. Could not load azkaban config file",
-          e);
+      logger.error("File found, but error reading. Could not load azkaban config file", e);
     }
 
     return props;
@@ -335,8 +328,7 @@ public class AzkabanExecutorServer {
       logger.info("Bean " + mbeanClass.getCanonicalName() + " registered.");
       registeredMBeans.add(mbeanName);
     } catch (Exception e) {
-      logger.error("Error registering mbean " + mbeanClass.getCanonicalName(),
-          e);
+      logger.error("Error registering mbean " + mbeanClass.getCanonicalName(), e);
     }
 
   }
diff --git a/azkaban-execserver/src/main/java/azkaban/execapp/StatsServlet.java b/azkaban-execserver/src/main/java/azkaban/execapp/StatsServlet.java
new file mode 100644
index 0000000..1d1a569
--- /dev/null
+++ b/azkaban-execserver/src/main/java/azkaban/execapp/StatsServlet.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014 LinkedIn Corp.
+ *
+ * Licensed 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 azkaban.execapp;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.metric.IMetric;
+import azkaban.metric.IMetricEmitter;
+import azkaban.metric.InMemoryHistoryNode;
+import azkaban.metric.InMemoryMetricEmitter;
+import azkaban.metric.MetricReportManager;
+import azkaban.metric.TimeBasedReportingMetric;
+import azkaban.server.HttpRequestUtils;
+import azkaban.server.ServerConstants;
+import azkaban.utils.JSONUtils;
+
+
+public class StatsServlet extends HttpServlet implements ConnectorParams {
+  private static final long serialVersionUID = 2L;
+  private static final Logger logger = Logger.getLogger(StatsServlet.class);
+  private AzkabanExecutorServer server;
+
+  public void init(ServletConfig config) throws ServletException {
+    server =
+        (AzkabanExecutorServer) config.getServletContext().getAttribute(ServerConstants.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);
+  }
+
+  public long getLongParam(HttpServletRequest request, String name) throws ServletException {
+    return HttpRequestUtils.getLongParam(request, name);
+  }
+
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    Map<String, Object> ret = new HashMap<String, Object>();
+
+    if (hasParam(req, ACTION_PARAM)) {
+      String action = getParam(req, ACTION_PARAM);
+      if (action.equals(STATS_SET_REPORTINGINTERVAL)) {
+        handleChangeMetricInterval(req, ret);
+      } else if (action.equals(STATS_GET_ALLMETRICSNAME)) {
+        handleGetAllMMetricsName(req, ret);
+      } else if (action.equals(STATS_GET_METRICHISTORY)) {
+        handleGetMetricHistory(req, ret);
+      }
+    }
+
+    JSONUtils.toJSON(ret, resp.getOutputStream(), true);
+  }
+
+  private void handleGetMetricHistory(HttpServletRequest req, Map<String, Object> ret) throws ServletException {
+    if (MetricReportManager.isInstantiated()) {
+      MetricReportManager metricManager = MetricReportManager.getInstance();
+      InMemoryMetricEmitter memoryEmitter = null;
+
+      for (IMetricEmitter emitter : metricManager.getMetricEmitters()) {
+        if (emitter instanceof InMemoryMetricEmitter) {
+          memoryEmitter = (InMemoryMetricEmitter) emitter;
+          break;
+        }
+      }
+
+      // if we have a memory emitter
+      if (memoryEmitter != null) {
+        try {
+          List<InMemoryHistoryNode> result =
+              memoryEmitter.getDrawMetric(getParam(req, STATS_MAP_METRICNAMEPARAM),
+                  parseDate(getParam(req, STATS_MAP_STARTDATE)), parseDate(getParam(req, STATS_MAP_ENDDATE)));
+
+          if (result != null && result.size() > 0) {
+            ret.put("data", result);
+          } else {
+            ret.put(RESPONSE_ERROR, "No metric stats available");
+          }
+
+        } catch (ParseException ex) {
+          ret.put(RESPONSE_ERROR, "Invalid Date filter");
+        }
+      } else {
+        ret.put(RESPONSE_ERROR, "InMemoryMetricEmitter not instantiated");
+      }
+    } else {
+      ret.put(RESPONSE_ERROR, "MetricReportManager not instantiated");
+    }
+  }
+
+  private void handleGetAllMMetricsName(HttpServletRequest req, Map<String, Object> ret) {
+    if (MetricReportManager.isInstantiated()) {
+      MetricReportManager metricManager = MetricReportManager.getInstance();
+      List<IMetric<?>> result = metricManager.getAllMetrics();
+      if(result.size() == 0) {
+        ret.put(RESPONSE_ERROR, "No Metric being tracked");
+      } else {
+        ret.put("data", result);
+      }
+    } else {
+      ret.put(RESPONSE_ERROR, "MetricReportManager not instantiated");
+    }
+  }
+
+  private void handleChangeMetricInterval(HttpServletRequest req, Map<String, Object> ret) throws ServletException {
+    try {
+      String metricName = getParam(req, STATS_MAP_METRICNAMEPARAM);
+      long newInterval = getLongParam(req, STATS_MAP_REPORTINGINTERVAL);
+      if (MetricReportManager.isInstantiated()) {
+        MetricReportManager metricManager = MetricReportManager.getInstance();
+        TimeBasedReportingMetric<?> metric = (TimeBasedReportingMetric<?>) metricManager.getMetricFromName(metricName);
+        metric.updateInterval(newInterval);
+      }
+      ret.put(STATUS_PARAM, RESPONSE_SUCCESS);
+    } catch (Exception e) {
+      logger.error(e);
+      ret.put(RESPONSE_ERROR, e.getMessage());
+    }
+  }
+
+  private Date parseDate(String date) throws ParseException {
+    DateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm a");
+    return format.parse(date);
+  }
+}
diff --git a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/StatsServlet.java b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/StatsServlet.java
index 7a33a20..bacdf4c 100644
--- a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/StatsServlet.java
+++ b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/StatsServlet.java
@@ -1,125 +1,114 @@
 package azkaban.webapp.servlet;
 
 import java.io.IOException;
-import java.security.InvalidParameterException;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import azkaban.metric.IMetric;
-import azkaban.metric.IMetricEmitter;
-import azkaban.metric.InMemoryHistoryNode;
-import azkaban.metric.InMemoryMetricEmitter;
-import azkaban.metric.MetricReportManager;
-import azkaban.metric.TimeBasedReportingMetric;
+import azkaban.executor.ConnectorParams;
+import azkaban.executor.ExecutorManager;
 import azkaban.server.session.Session;
 import azkaban.user.Permission;
 import azkaban.user.Role;
 import azkaban.user.User;
 import azkaban.user.UserManager;
+import azkaban.utils.Pair;
 import azkaban.webapp.AzkabanWebServer;
 
 
 public class StatsServlet extends LoginAbstractAzkabanServlet {
+  private static final long serialVersionUID = 1L;
   private UserManager userManager;
+  private ExecutorManager execManager;
 
   @Override
   public void init(ServletConfig config) throws ServletException {
     super.init(config);
     AzkabanWebServer server = (AzkabanWebServer) getApplication();
     userManager = server.getUserManager();
+    execManager = server.getExecutorManager();
   }
 
   @Override
   protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException,
       IOException {
-    if (hasParam(req, "ajax")) {
+    if (hasParam(req, ConnectorParams.ACTION_PARAM)) {
       handleAJAXAction(req, resp, session);
-    } else if (hasParam(req, "action")) {
-      String action = getParam(req, "action");
-      if (action.equals("changeMetricInterval")) {
-        handleChangeMetricInterval(req, resp, session);
-      }
     } else {
       handleStatePageLoad(req, resp, session);
     }
   }
 
-  private void handleChangeMetricInterval(HttpServletRequest req, HttpServletResponse resp, Session session)
-      throws ServletException {
-    String metricName = getParam(req, "metricName");
-    long newInterval = getLongParam(req, "interval");
-    if(MetricReportManager.isInstantiated()) {
-      MetricReportManager metricManager = MetricReportManager.getInstance();
-      TimeBasedReportingMetric<?> metric = (TimeBasedReportingMetric<?>) metricManager.getMetricFromName(metricName);
-      metric.updateInterval(newInterval);
-    }
-  }
-
   private void handleAJAXAction(HttpServletRequest req, HttpServletResponse resp, Session session)
       throws ServletException, IOException {
     HashMap<String, Object> ret = new HashMap<String, Object>();
-    String ajaxName = getParam(req, "ajax");
+    String actionName = getParam(req, ConnectorParams.ACTION_PARAM);
 
-    if (ajaxName.equals("metricHistory")) {
-      getMetricHistory(req, ret, session.getUser());
+    if (actionName.equals(ConnectorParams.STATS_GET_METRICHISTORY)) {
+      handleGetMetricHistory(req, ret, session.getUser());
+    } else if (actionName.equals(ConnectorParams.STATS_SET_REPORTINGINTERVAL)) {
+      handleChangeMetricInterval(req, ret, session);
     }
 
-    if (ret != null) {
-      this.writeJSON(resp, ret);
-    }
+    writeJSON(resp, ret);
   }
 
-  private void getMetricHistory(HttpServletRequest req, HashMap<String, Object> ret, User user) throws ServletException {
-    if (MetricReportManager.isInstantiated()) {
-      MetricReportManager metricManager = MetricReportManager.getInstance();
-      InMemoryMetricEmitter memoryEmitter = null;
+  private void handleChangeMetricInterval(HttpServletRequest req, HashMap<String, Object> ret, Session session)
+      throws ServletException, IOException {
+    try {
+      Map<String, Object> result =
+          execManager.callExecutorStats(ConnectorParams.STATS_GET_ALLMETRICSNAME, getAllParams(req));
 
-      for (IMetricEmitter emitter : metricManager.getMetricEmitters()) {
-        if (emitter instanceof InMemoryMetricEmitter) {
-          memoryEmitter = (InMemoryMetricEmitter) emitter;
-          break;
-        }
+      if (result.containsKey(ConnectorParams.RESPONSE_ERROR)) {
+        throw new Exception(result.get(ConnectorParams.RESPONSE_ERROR).toString());
       }
+      ret.put(ConnectorParams.STATUS_PARAM, result.get(ConnectorParams.STATUS_PARAM));
+    } catch (Exception e) {
+      ret.put(ConnectorParams.RESPONSE_ERROR, e.toString());
+    }
+  }
 
-      // if we have a memory emitter
-      if (memoryEmitter != null) {
-        try {
-          List<InMemoryHistoryNode> result =
-              memoryEmitter.getDrawMetric(getParam(req, "metricName"), parseDate(getParam(req, "from")),
-                  parseDate(getParam(req, "to")));
-          if (result.size() > 0) {
-            ret.put("data", result);
-          } else {
-            ret.put("error", "No metric stats available");
-          }
-
-        } catch (ParseException ex) {
-          ret.put("error", "Invalid Date filter");
-        }
+  private void handleGetMetricHistory(HttpServletRequest req, HashMap<String, Object> ret, User user)
+      throws IOException, ServletException {
+    try {
+      Map<String, Object> result =
+          execManager.callExecutorStats(ConnectorParams.STATS_GET_METRICHISTORY, getAllParams(req));
+      if (result.containsKey(ConnectorParams.RESPONSE_ERROR)) {
+        throw new Exception(result.get(ConnectorParams.RESPONSE_ERROR).toString());
       }
+      ret.put("data", result.get("data"));
+    } catch (Exception e) {
+      ret.put(ConnectorParams.RESPONSE_ERROR, e.toString());
     }
   }
 
   private void handleStatePageLoad(HttpServletRequest req, HttpServletResponse resp, Session session)
       throws ServletException {
     Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/statsPage.vm");
-    MetricReportManager metricManager = MetricReportManager.getInstance();
     if (!hasPermission(session.getUser(), Permission.Type.METRICS)) {
       page.add("errorMsg", "User " + session.getUser().getUserId() + " has no permission.");
       page.render();
       return;
     }
-    page.add("metricList", metricManager.getAllMetrics());
+    try {
+      Map<String, Object> result =
+          execManager.callExecutorStats(ConnectorParams.STATS_GET_ALLMETRICSNAME, (Pair<String, String>[]) null);
+      if (result.containsKey(ConnectorParams.RESPONSE_ERROR)) {
+        throw new Exception(result.get(ConnectorParams.RESPONSE_ERROR).toString());
+      }
+      page.add("metricList", result.get("data"));
+    } catch (Exception e) {
+      page.add("errorMsg", e.toString());
+    }
     page.render();
   }
 
@@ -139,8 +128,18 @@ public class StatsServlet extends LoginAbstractAzkabanServlet {
     return false;
   }
 
-  private Date parseDate(String date) throws ParseException {
-    DateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm a");
-    return format.parse(date);
+  @SuppressWarnings("unchecked")
+  private Pair<String, String>[] getAllParams(HttpServletRequest req) {
+    List<Pair<String, String>> allParams = new LinkedList<Pair<String, String>>();
+
+    Iterator it = req.getParameterMap().entrySet().iterator();
+    while (it.hasNext()) {
+        Map.Entry pairs = (Map.Entry)it.next();
+        for(Object value : (String [])pairs.getValue()) {
+          allParams.add(new Pair<String, String>((String) pairs.getKey(), (String) value));
+        }
+    }
+
+    return allParams.toArray(new Pair[allParams.size()]);
   }
 }
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/statsPage.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/statsPage.vm
index 1ca3a73..8ab8b72 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/statsPage.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/statsPage.vm
@@ -35,7 +35,7 @@
       function refreshMetricChart() {
             var requestURL = '/stats';
             var requestData = {
-              'ajax': 'metricHistory',
+              'action': 'metricHistory',
               'from': $('#datetimebegin').val(),
               'to'  : $('#datetimeend').val(),
               'metricName': $('#metricName').val()