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()