azkaban-aplcache
Changes
az-jobsummary/build.gradle 33(+33 -0)
az-jobsummary/conf/plugin.properties 7(+7 -0)
az-jobsummary/src/web/js/azkaban/model/log-data.js 346(+346 -0)
az-jobsummary/src/web/js/azkaban/view/job-summary.js 245(+245 -0)
settings.gradle 1(+1 -0)
Details
az-jobsummary/build.gradle 33(+33 -0)
diff --git a/az-jobsummary/build.gradle b/az-jobsummary/build.gradle
new file mode 100644
index 0000000..f87fe52
--- /dev/null
+++ b/az-jobsummary/build.gradle
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 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.
+ */
+
+apply plugin: 'distribution'
+
+dependencies {
+ compile project(':az-core')
+ compile project(":azkaban-common")
+ compile project(":azkaban-web-server")
+}
+
+distributions {
+ main {
+ contents {
+ from(jar) {
+ into 'lib'
+ }
+ }
+ }
+}
az-jobsummary/conf/plugin.properties 7(+7 -0)
diff --git a/az-jobsummary/conf/plugin.properties b/az-jobsummary/conf/plugin.properties
new file mode 100644
index 0000000..f592c6a
--- /dev/null
+++ b/az-jobsummary/conf/plugin.properties
@@ -0,0 +1,7 @@
+viewer.name=Summary
+viewer.path=jobsummary
+viewer.order=1
+viewer.hidden=true
+viewer.external.classpaths=extlib/*
+viewer.servlet.class=azkaban.viewer.jobsummary.JobSummaryServlet
+viewer.jobtypes=java,hadoopJava,pig,hive
diff --git a/az-jobsummary/src/azkaban/viewer/jobsummary/JobSummaryServlet.java b/az-jobsummary/src/azkaban/viewer/jobsummary/JobSummaryServlet.java
new file mode 100644
index 0000000..94e19d8
--- /dev/null
+++ b/az-jobsummary/src/azkaban/viewer/jobsummary/JobSummaryServlet.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2018 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.viewer.jobsummary;
+
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutableNode;
+import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.executor.ExecutorManagerException;
+import azkaban.project.Project;
+import azkaban.project.ProjectManager;
+import azkaban.server.session.Session;
+import azkaban.user.Permission;
+import azkaban.user.Permission.Type;
+import azkaban.user.User;
+import azkaban.utils.Props;
+import azkaban.webapp.AzkabanWebServer;
+import azkaban.webapp.plugin.PluginRegistry;
+import azkaban.webapp.plugin.ViewerPlugin;
+import azkaban.webapp.servlet.LoginAbstractAzkabanServlet;
+import azkaban.webapp.servlet.Page;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
+
+public class JobSummaryServlet extends LoginAbstractAzkabanServlet {
+ private static final String PROXY_USER_SESSION_KEY =
+ "hdfs.browser.proxy.user";
+ private static final String HADOOP_SECURITY_MANAGER_CLASS_PARAM =
+ "hadoop.security.manager.class";
+ private static final Logger logger = Logger.getLogger(JobSummaryServlet.class);
+
+ private final Props props;
+ private final File webResourcesPath;
+
+ private final String viewerName;
+ private final String viewerPath;
+
+ private ExecutorManagerAdapter executorManager;
+ private ProjectManager projectManager;
+
+ private String outputDir;
+
+ public JobSummaryServlet(final Props props) {
+ this.props = props;
+ this.viewerName = props.getString("viewer.name");
+ this.viewerPath = props.getString("viewer.path");
+
+ this.webResourcesPath =
+ new File(new File(props.getSource()).getParentFile().getParentFile(),
+ "web");
+ this.webResourcesPath.mkdirs();
+ setResourceDirectory(this.webResourcesPath);
+ }
+
+ private Project getProjectByPermission(final int projectId, final User user,
+ final Permission.Type type) {
+ final Project project = this.projectManager.getProject(projectId);
+ if (project == null) {
+ return null;
+ }
+ if (!hasPermission(project, user, type)) {
+ return null;
+ }
+ return project;
+ }
+
+ @Override
+ public void init(final ServletConfig config) throws ServletException {
+ super.init(config);
+ final AzkabanWebServer server = (AzkabanWebServer) getApplication();
+ this.executorManager = server.getExecutorManager();
+ this.projectManager = server.getProjectManager();
+ }
+
+ private void handleViewer(final HttpServletRequest req, final HttpServletResponse resp,
+ final Session session) throws ServletException, IOException {
+
+ final Page page =
+ newPage(req, resp, session,
+ "azkaban/viewer/jobsummary/velocity/jobsummary.vm");
+ page.add("viewerPath", this.viewerPath);
+ page.add("viewerName", this.viewerName);
+
+ final User user = session.getUser();
+ final int execId = getIntParam(req, "execid");
+ final String jobId = getParam(req, "jobid");
+ final int attempt = getIntParam(req, "attempt", 0);
+
+ page.add("execid", execId);
+ page.add("jobid", jobId);
+ page.add("attempt", attempt);
+
+ ExecutableFlow flow = null;
+ ExecutableNode node = null;
+ try {
+ flow = this.executorManager.getExecutableFlow(execId);
+ if (flow == null) {
+ page.add("errorMsg", "Error loading executing flow " + execId
+ + ": not found.");
+ page.render();
+ return;
+ }
+
+ node = flow.getExecutableNodePath(jobId);
+ if (node == null) {
+ page.add("errorMsg",
+ "Job " + jobId + " doesn't exist in " + flow.getExecutionId());
+ return;
+ }
+
+ final List<ViewerPlugin> jobViewerPlugins =
+ PluginRegistry.getRegistry().getViewerPluginsForJobType(
+ node.getType());
+ page.add("jobViewerPlugins", jobViewerPlugins);
+ } catch (final ExecutorManagerException e) {
+ page.add("errorMsg", "Error loading executing flow: " + e.getMessage());
+ page.render();
+ return;
+ }
+
+ final int projectId = flow.getProjectId();
+ final Project project = getProjectByPermission(projectId, user, Type.READ);
+ if (project == null) {
+ page.render();
+ return;
+ }
+
+ page.add("projectName", project.getName());
+ page.add("flowid", flow.getId());
+ page.add("parentflowid", node.getParentFlow().getFlowId());
+ page.add("jobname", node.getId());
+
+ page.render();
+ }
+
+ private void handleDefault(final HttpServletRequest request,
+ final HttpServletResponse response, final Session session) throws ServletException,
+ IOException {
+ final Page page =
+ newPage(request, response, session,
+ "azkaban/viewer/jobsummary/velocity/jobsummary.vm");
+ page.add("viewerPath", this.viewerPath);
+ page.add("viewerName", this.viewerName);
+ page.add("errorMsg", "No job execution specified.");
+ page.render();
+ }
+
+ @Override
+ protected void handleGet(final HttpServletRequest request,
+ final HttpServletResponse response, final Session session) throws ServletException,
+ IOException {
+ if (hasParam(request, "execid") && hasParam(request, "jobid")) {
+ handleViewer(request, response, session);
+ } else {
+ handleDefault(request, response, session);
+ }
+ }
+
+ @Override
+ protected void handlePost(final HttpServletRequest request,
+ final HttpServletResponse response, final Session session) throws ServletException,
+ IOException {
+ }
+}
diff --git a/az-jobsummary/src/azkaban/viewer/jobsummary/velocity/jobsummary.vm b/az-jobsummary/src/azkaban/viewer/jobsummary/velocity/jobsummary.vm
new file mode 100644
index 0000000..a25baff
--- /dev/null
+++ b/az-jobsummary/src/azkaban/viewer/jobsummary/velocity/jobsummary.vm
@@ -0,0 +1,123 @@
+#*
+ * Copyright 2012 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.
+*#
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+
+#parse ("azkaban/webapp/servlet/velocity/style.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
+ <script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+ <script type="text/javascript" src="${context}/jobsummary/js/azkaban/view/job-summary.js"></script>
+ <script type="text/javascript" src="${context}/jobsummary/js/azkaban/model/log-data.js"></script>
+ <script type="text/javascript">
+ var contextURL = "${context}";
+ var currentTime = ${currentTime};
+ var timezone = "${timezone}";
+ var errorMessage = null;
+ var successMessage = null;
+
+ var projectName = "${projectName}";
+ var flowName = "${flowid}";
+ var execId = "${execid}";
+ var jobId = "${jobid}";
+ var attempt = ${attempt};
+ </script>
+ </head>
+ <body>
+
+#set ($current_page = "$viewerName")
+#parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+#if ($errorMsg)
+ #parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
+#else
+
+ #parse ("azkaban/webapp/servlet/velocity/jobdetailsheader.vm")
+
+ <div class="container-full" id="jobSummaryView">
+ <div class="row">
+ <div class="col-lg-12">
+ <h3>
+ Job Summary
+ <div class="pull-right">
+ <button type="button" id="update-summary-btn" class="btn btn-xs btn-default">Refresh</button>
+ </div>
+ </h3>
+
+ <div class="callout callout-default" id="placeholder">
+ <h4>Job Summary not available</h4>
+ <p>There is no summary information available for this job yet. Try waiting and clicking Refresh.</p>
+ </div>
+
+ <div id="job-type">
+ <table id="job-type-table" class="table table-striped table-bordered table-hover">
+ </table>
+ </div>
+
+ <div id="command-summary">
+ <h4>Command Summary</h4>
+ <table id="command-table" class="table table-striped table-bordered table-hover">
+ </table>
+ </div>
+
+ <div id="pig-job-summary">
+ <h4>Pig Job Summary</h4>
+ <table class="table table-striped table-bordered table-hover">
+ <thead id="summary-header">
+ </thead>
+ <tbody id="summary-body">
+ </tbody>
+ </table>
+ </div>
+
+ <div id="pig-job-stats">
+ <h4>Pig Job Stats</h4>
+ <div class="scrollable">
+ <table class="table table-striped table-bordered table-hover table-condensed">
+ <thead id="stats-header">
+ </thead>
+ <tbody id="stats-body">
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div id="hive-job-summary">
+ <h4>Hive Job Summary</h4>
+ <table class="table table-striped table-bordered table-hover" id="hive-table">
+ <thead id="hive-table-header">
+ </thead>
+ <tbody id="hive-table-body">
+ </tbody>
+ </table>
+ </div>
+
+ <div id="job-ids">
+ <h4>Map Reduce Jobs</h4>
+ <table class="table table-striped table-bordered table-hover">
+ <tbody id="job-ids-table-body"></tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+#end
+ </div><!-- /.container-full -->
+ </body>
+</html>
az-jobsummary/src/web/js/azkaban/model/log-data.js 346(+346 -0)
diff --git a/az-jobsummary/src/web/js/azkaban/model/log-data.js b/az-jobsummary/src/web/js/azkaban/model/log-data.js
new file mode 100644
index 0000000..2fb9c83
--- /dev/null
+++ b/az-jobsummary/src/web/js/azkaban/model/log-data.js
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2018 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.
+ */
+
+$.namespace('azkaban');
+
+azkaban.LogDataModel = Backbone.Model.extend({
+ TIMESTAMP_REGEX: /^.*? - /gm,
+
+ JOB_TRACKER_URL_REGEX: /https?:\/\/[-\w\.]+(?::\d+)?\/[\w\/\.]*\?\S+(job_\d{12}_\d{4,})\S*/,
+
+ // Command properties
+ COMMAND_START: "Command: ",
+ CLASSPATH_REGEX: /(?:-cp|-classpath)\s+(\S+)/g,
+ ENVIRONMENT_VARIABLES_REGEX: /-D(\S+)/g,
+ JVM_MEMORY_REGEX: /(-Xm\S+)/g,
+ PIG_PARAMS_REGEX: /-param\s+(\S+)/g,
+
+ JOB_TYPE_REGEX: /Building (\S+) job executor/,
+
+ PIG_JOB_SUMMARY_START: "HadoopVersion",
+ PIG_JOB_STATS_START: "Job Stats (time in seconds):",
+
+ HIVE_PARSING_START: "Parsing command: ",
+ HIVE_PARSING_END: "Parse Completed",
+ HIVE_NUM_MAP_REDUCE_JOBS_STRING: "Total MapReduce jobs = ",
+ HIVE_MAP_REDUCE_JOB_START: "Starting Job",
+ HIVE_MAP_REDUCE_JOBS_SUMMARY: "MapReduce Jobs Launched:",
+ HIVE_MAP_REDUCE_SUMMARY_REGEX: /Job (\d+):\s+Map: (\d+)\s+Reduce: (\d+)\s+(?:Cumulative CPU: (.+?))?\s+HDFS Read: (\d+)\s+HDFS Write: (\d+)/,
+
+ JOB_ID_REGEX: /job_\d{12}_\d{4,}/,
+
+ initialize: function() {
+ this.set("offset", 0 );
+ this.set("logData", "");
+ this.on("change:logData", this.parseLogData);
+ },
+
+ refresh: function() {
+ var requestURL = contextURL + "/executor";
+ var finished = false;
+
+ var date = new Date();
+ var startTime = date.getTime();
+
+ while (!finished) {
+ var requestData = {
+ "execid": execId,
+ "jobId": jobId,
+ "ajax":"fetchExecJobLogs",
+ "offset": this.get("offset"),
+ "length": 50000,
+ "attempt": attempt
+ };
+
+ var self = this;
+
+ var successHandler = function(data) {
+ console.log("fetchLogs");
+ if (data.error) {
+ console.log(data.error);
+ finished = true;
+ }
+ else if (data.length == 0) {
+ finished = true;
+ }
+ else {
+ var date = new Date();
+ var endTime = date.getTime();
+ if ((endTime - startTime) > 10000) {
+ finished = true;
+ showDialog("Alert","The log is taking a long time to finish loading. Azkaban has stopped loading them. Please click Refresh to restart the load.");
+ }
+
+ self.set("offset", data.offset + data.length);
+ self.set("logData", self.get("logData") + data.data);
+ }
+ }
+
+ $.ajax({
+ url: requestURL,
+ type: "get",
+ async: false,
+ data: requestData,
+ dataType: "json",
+ error: function(data) {
+ console.log(data);
+ finished = true;
+ },
+ success: successHandler
+ });
+ }
+ },
+
+ parseLogData: function() {
+ var data = this.get("logData").replace(this.TIMESTAMP_REGEX, "");
+ var lines = data.split("\n");
+
+ if (this.parseCommand(lines)) {
+ this.parseJobType(lines);
+ this.parseJobTrackerUrls(lines);
+
+ var jobType = this.get("jobType");
+ if (jobType) {
+ if (jobType.indexOf("pig") !== -1) {
+ this.parsePigTable(lines, "pigSummary", this.PIG_JOB_SUMMARY_START, "", 0);
+ this.parsePigTable(lines, "pigStats", this.PIG_JOB_STATS_START, "", 1);
+ } else if (jobType.indexOf("hive") !== -1) {
+ this.parseHiveQueries(lines);
+ } else {
+ this.parseJobIds(lines);
+ }
+ }
+ }
+ },
+
+ parseCommand: function(lines) {
+ var commandStartIndex = -1;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines; i++) {
+ if (lines[i].indexOf(this.COMMAND_START) === 0) {
+ commandStartIndex = i;
+ break;
+ }
+ }
+
+ if (commandStartIndex != -1) {
+ var commandProperties = {};
+
+ var command = lines[commandStartIndex].substring(this.COMMAND_START.length);
+ commandProperties.Command = command;
+
+ this.parseCommandProperty(command, commandProperties, "Classpath", this.CLASSPATH_REGEX, ':');
+ this.parseCommandProperty(command, commandProperties, "-D", this.ENVIRONMENT_VARIABLES_REGEX);
+ this.parseCommandProperty(command, commandProperties, "Memory Settings", this.JVM_MEMORY_REGEX);
+ this.parseCommandProperty(command, commandProperties, "Params", this.PIG_PARAMS_REGEX);
+
+ this.set("commandProperties", commandProperties);
+
+ return true;
+ }
+
+ return false;
+ },
+
+ parseCommandProperty: function(command, commandProperties, propertyName, regex, split) {
+ var results = [];
+ var match;
+ while (match = regex.exec(command)) {
+ if (split) {
+ results = results.concat(match[1].split(split));
+ } else {
+ results.push(match[1]);
+ }
+ }
+
+ if (results.length > 0) {
+ commandProperties[propertyName] = results;
+ }
+ },
+
+ parseJobTrackerUrls: function(lines) {
+ var jobTrackerUrls = {};
+ var jobTrackerUrlsOrdered = [];
+ var numLines = lines.length;
+ var match;
+ for (var i = 0; i < numLines; i++) {
+ if ((match = this.JOB_TRACKER_URL_REGEX.exec(lines[i])) && !jobTrackerUrls[match[1]]) {
+ jobTrackerUrls[match[1]] = match[0];
+ jobTrackerUrlsOrdered.push(match[0]);
+ }
+ }
+ this.set("jobTrackerUrls", jobTrackerUrls);
+ this.set("jobTrackerUrlsOrdered", jobTrackerUrlsOrdered);
+ },
+
+ parseJobIds: function(lines) {
+ var seenJobIds = {};
+ var jobIds = [];
+ var numLines = lines.length;
+ var match;
+ for (var i = 0; i < numLines; i++) {
+ if ((match = this.JOB_ID_REGEX.exec(lines[i])) && !seenJobIds[match[0]]) {
+ seenJobIds[match[0]] = true;
+ jobIds.push(match[0]);
+ }
+ }
+
+ if (jobIds.length > 0) {
+ this.set("jobIds", jobIds);
+ }
+ },
+
+ parseJobType: function(lines) {
+ var numLines = lines.length;
+ var match;
+ for (var i = 0; i < numLines; i++) {
+ if (match = this.JOB_TYPE_REGEX.exec(lines[i])) {
+ this.set("jobType", match[1]);
+ break;
+ }
+ }
+ },
+
+ parsePigTable: function(lines, tableName, startPattern, endPattern, linesToSkipAfterStart) {
+ var index = -1;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines; i++) {
+ if (lines[i].indexOf(startPattern) === 0) {
+ index = i + linesToSkipAfterStart;
+ break;
+ }
+ }
+
+ if (index != -1) {
+ var table = [];
+ var line;
+ while ((line = lines[index]) !== endPattern) {
+ var columns = line.split("\t");
+ // If first column is a job id, make it a link to the job tracker.
+ if (this.get("jobTrackerUrls")[columns[0]]) {
+ columns[0] = "<a href='" + this.get("jobTrackerUrls")[columns[0]] + "'>" + columns[0] + "</a>";
+ }
+ table.push(columns);
+ index++;
+ }
+
+ this.set(tableName, table);
+ }
+ },
+
+ parseHiveQueries: function(lines) {
+ var hiveQueries = [];
+ var hiveQueryJobs = [];
+
+ var currMapReduceJob = 0;
+ var numLines = lines.length;
+ for (var i = 0; i < numLines;) {
+ var line = lines[i];
+ var parsingCommandIndex = line.indexOf(this.HIVE_PARSING_START);
+ if (parsingCommandIndex === -1) {
+ i++;
+ continue;
+ }
+
+ // parse query text, which could span multiple lines
+ var queryStartIndex = parsingCommandIndex + this.HIVE_PARSING_START.length;
+ var query = line.substring(queryStartIndex) + "<br/>";
+
+ i++;
+ while (i < numLines && (line = lines[i]).indexOf(this.HIVE_PARSING_END) === -1) {
+ query += line + "<br/>";
+ i++;
+ }
+ hiveQueries.push(query);
+ i++;
+
+ // parse the query's Map-Reduce jobs, if any.
+ var numMRJobs = 0;
+ while (i < numLines) {
+ line = lines[i];
+ if (line.indexOf(this.HIVE_NUM_MAP_REDUCE_JOBS_STRING) !== -1) {
+ // query involves map reduce jobs
+ var numMRJobs = parseInt(line.substring(this.HIVE_NUM_MAP_REDUCE_JOBS_STRING.length),10);
+ i++;
+
+ // get the map reduce jobs summary
+ while (i < numLines) {
+ line = lines[i];
+ if (line.indexOf(this.HIVE_MAP_REDUCE_JOBS_SUMMARY) !== -1) {
+ // job summary table found
+ i++;
+
+ var queryJobs = [];
+
+ var previousJob = -1;
+ var numJobsSeen = 0;
+ while (numJobsSeen < numMRJobs && i < numLines) {
+ line = lines[i];
+ var match;
+ if (match = this.HIVE_MAP_REDUCE_SUMMARY_REGEX.exec(line)) {
+ var currJob = parseInt(match[1], 10);
+ if (currJob === previousJob) {
+ i++;
+ continue;
+ }
+
+ var job = [];
+ job.push("<a href='" + this.get("jobTrackerUrlsOrdered")[currMapReduceJob++] + "'>" + currJob + "</a>");
+ job.push(match[2]);
+ job.push(match[3]);
+ if (match[4]) {
+ this.set("hasCumulativeCPU", true);
+ job.push(match[4]);
+ }
+ job.push(match[5]);
+ job.push(match[6]);
+
+ queryJobs.push(job);
+ previousJob = currJob;
+ numJobsSeen++;
+ }
+ i++;
+ }
+
+ if (numJobsSeen === numMRJobs) {
+ hiveQueryJobs.push(queryJobs);
+ }
+
+ break;
+ }
+ i++;
+ }
+ break;
+ }
+ else if (line.indexOf(this.HIVE_PARSING_START) !== -1) {
+ if (numMRJobs === 0) {
+ hiveQueryJobs.push(null);
+ }
+ break;
+ }
+ i++;
+ }
+ continue;
+ }
+
+ if (hiveQueries.length > 0) {
+ this.set("hiveSummary", {
+ hiveQueries: hiveQueries,
+ hiveQueryJobs: hiveQueryJobs
+ });
+ }
+ }
+});
az-jobsummary/src/web/js/azkaban/view/job-summary.js 245(+245 -0)
diff --git a/az-jobsummary/src/web/js/azkaban/view/job-summary.js b/az-jobsummary/src/web/js/azkaban/view/job-summary.js
new file mode 100644
index 0000000..f7b14e7
--- /dev/null
+++ b/az-jobsummary/src/web/js/azkaban/view/job-summary.js
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2018 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.
+ */
+
+$.namespace('azkaban');
+
+var jobSummaryView;
+azkaban.JobSummaryView = Backbone.View.extend({
+ events: {
+ "click #update-summary-btn" : "refresh"
+ },
+
+ initialize: function(settings) {
+ $("#job-type").hide();
+ $("#command-summary").hide();
+ $("#pig-job-summary").hide();
+ $("#pig-job-stats").hide();
+ $("#hive-job-summary").hide();
+ $("#job-ids").hide();
+
+ this.listenTo(this.model, "change:jobType", this.renderJobTypeTable);
+ this.listenTo(this.model, "change:commandProperties", this.renderCommandTable);
+ this.listenTo(this.model, "change:pigSummary", this.renderPigSummaryTable);
+ this.listenTo(this.model, "change:pigStats", this.renderPigStatsTable);
+ this.listenTo(this.model, "change:hiveSummary", this.renderHiveTable);
+ this.listenTo(this.model, "change:jobIds", this.renderJobIdsTable);
+ },
+
+ refresh: function() {
+ this.model.refresh();
+ },
+
+ handleUpdate: function(evt) {
+ renderJobTable(jobSummary.summaryTableHeaders, jobSummary.summaryTableData, "summary");
+ renderJobTable(jobSummary.statTableHeaders, jobSummary.statTableData, "stats");
+ renderHiveTable(jobSummary.hiveQueries, jobSummary.hiveQueryJobs);
+ },
+
+ renderJobTypeTable: function() {
+ var jobTypeTable = $("#job-type-table");
+ var jobType = this.model.get("jobType");
+
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ $(td).addClass("property-key");
+ $(td).html("<b>Job Type</b>");
+ $(tr).append(td);
+ td = document.createElement("td");
+ $(td).html(jobType);
+ $(tr).append(td);
+
+ jobTypeTable.append(tr);
+
+ $("#placeholder").hide();
+ $("#job-type").show();
+ },
+
+ renderJobIdsTable: function() {
+ var oldBody = $("#job-ids-table-body");
+ var newBody = $(document.createElement("tbody")).attr("id", "job-ids-table-body");
+
+ var jobIds = this.model.get("jobIds");
+ var jobUrls = this.model.get("jobTrackerUrls");
+ var numJobs = jobIds.length;
+ for (var i = 0; i < numJobs; i++) {
+ var job = jobIds[i];
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ var html = jobUrls[job] ? "<a href='" + jobUrls[job] + "'>" + job + "</a>" : job;
+ $(td).html(html);
+ $(tr).append(td);
+ newBody.append(tr);
+ }
+
+ oldBody.replaceWith(newBody);
+
+ $("#placeholder").hide();
+ $("#job-ids").show();
+ },
+
+ renderCommandTable: function() {
+ var commandTable = $("#command-table");
+ var commandProperties = this.model.get("commandProperties");
+
+ for (var key in commandProperties) {
+ if (commandProperties.hasOwnProperty(key)) {
+ var value = commandProperties[key];
+ if (Array.isArray(value)) {
+ value = value.join("<br/>");
+ }
+ var tr = document.createElement("tr");
+ var keyTd = document.createElement("td");
+ var valueTd = document.createElement("td");
+ $(keyTd).html("<b>" + key + "</b>");
+ $(valueTd).html(value);
+ $(tr).append(keyTd);
+ $(tr).append(valueTd);
+ commandTable.append(tr);
+ }
+ }
+
+ $("#placeholder").hide();
+ $("#command-summary").show();
+ },
+
+ renderPigTable: function(tableName, data) {
+ // Add table headers
+ var header = $("#" + tableName + "-header");
+ var tr = document.createElement("tr");
+ var i;
+ var headers = data[0];
+ var numColumns = headers.length;
+ for (i = 0; i < numColumns; i++) {
+ var th = document.createElement("th");
+ $(th).text(headers[i]);
+ $(tr).append(th);
+ }
+ header.append(tr);
+
+ // Add table body
+ var body = $("#" + tableName + "-body");
+ for (i = 1; i < data.length; i++) {
+ tr = document.createElement("tr");
+ var row = data[i];
+ for (var j = 0; j < numColumns; j++) {
+ var td = document.createElement("td");
+ if (j == 0) {
+ // first column is a link to job details page
+ $(td).html(row[j]);
+ } else {
+ $(td).text(row[j]);
+ }
+ $(tr).append(td);
+ }
+ body.append(tr);
+ }
+
+ $("#placeholder").hide();
+ $("#pig-job-" + tableName).show();
+ },
+
+ renderPigSummaryTable: function() {
+ this.renderPigTable("summary", this.model.get("pigSummary"));
+ },
+
+ renderPigStatsTable: function() {
+ this.renderPigTable("stats", this.model.get("pigStats"));
+ },
+
+ renderHiveTable: function() {
+ var hiveSummary = this.model.get("hiveSummary");
+ var queries = hiveSummary.hiveQueries;
+ var queryJobs = hiveSummary.hiveQueryJobs;
+
+ // Set up table column headers
+ var header = $("#hive-table-header");
+ var tr = document.createElement("tr");
+
+ var headers;
+ if (this.model.get("hasCumulativeCPU")) {
+ headers = ["Query","Job","Map","Reduce","Cumulative CPU","HDFS Read","HDFS Write"];
+ } else {
+ headers = ["Query","Job","Map","Reduce","HDFS Read","HDFS Write"];
+ }
+
+ var i;
+ for (i = 0; i < headers.length; i++) {
+ var th = document.createElement("th");
+ $(th).text(headers[i]);
+ $(tr).append(th);
+ }
+ header.html(tr);
+
+ // Construct table body
+ var oldBody = $("#hive-table-body");
+ var newBody = $(document.createElement("tbody")).attr("id", "hive-table-body");
+ for (i = 0; i < queries.length; i++) {
+ // new query
+ tr = document.createElement("tr");
+ var td = document.createElement("td");
+ $(td).html("<b>" + queries[i] + "</b>");
+ $(tr).append(td);
+
+ var jobs = queryJobs[i];
+ if (jobs != null) {
+ // add first job for this query
+ var jobValues = jobs[0];
+ var j;
+ for (j = 0; j < jobValues.length; j++) {
+ td = document.createElement("td");
+ $(td).html(jobValues[j]);
+ $(tr).append(td);
+ }
+ newBody.append(tr);
+
+ // add remaining jobs for this query
+ for (j = 1; j < jobs.length; j++) {
+ jobValues = jobs[j];
+ tr = document.createElement("tr");
+
+ // add empty cell for query column
+ td = document.createElement("td");
+ $(td).html(" ");
+ $(tr).append(td);
+
+ // add job values
+ for (var k = 0; k < jobValues.length; k++) {
+ td = document.createElement("td");
+ $(td).html(jobValues[k]);
+ $(tr).append(td);
+ }
+ newBody.append(tr);
+ }
+
+ } else {
+ newBody.append(tr);
+ }
+ }
+ oldBody.replaceWith(newBody);
+
+ $("#placeholder").hide();
+ $("#hive-job-summary").show();
+ }
+});
+
+$(function() {
+ var logDataModel = new azkaban.LogDataModel();
+ jobSummaryView = new azkaban.JobSummaryView({
+ el: $('#job-summary-view'),
+ model: logDataModel
+ });
+ logDataModel.refresh();
+});
settings.gradle 1(+1 -0)
diff --git a/settings.gradle b/settings.gradle
index 9a94fe6..43383a8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -29,4 +29,5 @@ include 'az-flow-trigger-dependency-plugin'
include 'test'
include 'az-reportal'
include 'az-hadoop-jobtype-plugin'
+include 'az-jobsummary'
include 'az-hdfs-viewer'