azkaban-aplcache
Details
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index 16b49bf..4419e70 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -1,8 +1,11 @@
package azkaban.executor;
import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -51,6 +54,9 @@ public class ExecutorManager {
private static final long ACCESS_ERROR_THRESHOLD = 30000;
private static final int UPDATE_THREAD_MS = 1000;
+ // log read buffer.
+ private static final int LOG_BUFFER_READ_SIZE = 10*1024;
+
private File basePath;
private AtomicInteger counter = new AtomicInteger();
@@ -449,6 +455,65 @@ public class ExecutorManager {
flow.setSubmitted(true);
}
+ public long getExecutableFlowLog(ExecutableFlow flow, StringBuffer buffer, long startChar, long maxSize) throws ExecutorManagerException {
+ String path = flow.getExecutionPath();
+ File execPath = new File(path);
+ if (!execPath.exists()) {
+ logger.error("Execution dir for " + flow + " doesn't exist.");
+ return -1;
+ }
+
+ String logFileName = "_flow." + flow.getExecutionId() + ".log";
+ File flowLogFile = new File(execPath, logFileName);
+
+ if (!flowLogFile.exists()) {
+ logger.error("Execution log for " + flowLogFile + " doesn't exist.");
+ return -1;
+ }
+
+ BufferedReader reader = null;
+ FileReader fileReader = null;
+ char[] charBuffer = new char[LOG_BUFFER_READ_SIZE];
+
+ try {
+ fileReader = new FileReader(flowLogFile);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ long charPosition = startChar;
+ int charRead = 0;
+ long totalCharRead = 0;
+ try {
+ reader = new BufferedReader(fileReader);
+ reader.skip(startChar);
+
+ do {
+ charRead = reader.read(charBuffer);
+ if (charRead == -1) {
+ break;
+ }
+ totalCharRead += charRead;
+ charPosition += charRead;
+ buffer.append(charBuffer, 0, charRead);
+ } while (charRead == charBuffer.length && totalCharRead < maxSize);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return charPosition;
+ }
+
public void cleanupAll(ExecutableFlow exflow) throws ExecutorManagerException{
String path = exflow.getExecutionPath();
File executionPath = new File(path);
diff --git a/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java b/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java
index c8c5f5b..c8c966d 100644
--- a/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java
@@ -29,6 +29,7 @@ public class FlowExecutorServlet extends LoginAbstractAzkabanServlet {
private static final long serialVersionUID = 1L;
private ProjectManager projectManager;
private ExecutorManager executorManager;
+ private static final int STRING_BUFFER_SIZE = 1024*5;
@Override
public void init(ServletConfig config) throws ServletException {
@@ -155,6 +156,9 @@ public class FlowExecutorServlet extends LoginAbstractAzkabanServlet {
else if (ajaxName.equals("resumeFlow")) {
ajaxResumeFlow(req, resp, ret, session.getUser(), exFlow);
}
+ else if (ajaxName.equals("fetchExecFlowLogs")) {
+ ajaxFetchExecFlowLogs(req, resp, ret, session.getUser(), exFlow);
+ }
}
}
else {
@@ -168,6 +172,26 @@ public class FlowExecutorServlet extends LoginAbstractAzkabanServlet {
this.writeJSON(resp, ret);
}
+ private void ajaxFetchExecFlowLogs(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException {
+ Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.READ);
+ if (project == null) {
+ return;
+ }
+
+ int startChar = this.getIntParam(req, "current");
+ int maxSize = this.getIntParam(req, "max");
+
+ StringBuffer buffer = new StringBuffer(STRING_BUFFER_SIZE);
+ try {
+ long character = executorManager.getExecutableFlowLog(exFlow, buffer, startChar, maxSize);
+ ret.put("current", character);
+ ret.put("log", buffer.toString());
+ } catch (ExecutorManagerException e) {
+ e.printStackTrace();
+ ret.put("error", e.getMessage());
+ }
+ }
+
private void ajaxCancelFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.EXECUTE);
if (project == null) {
@@ -187,7 +211,6 @@ public class FlowExecutorServlet extends LoginAbstractAzkabanServlet {
return;
}
-
}
private void ajaxPauseFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index e81170d..01c69b7 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -57,6 +57,8 @@
<li><a id="graphViewLink" href="#graph">Graph</a></li>
<li class="lidivider">|</li>
<li><a id="jobslistViewLink" href="#jobslist">Job List</a></li>
+ <li class="lidivider">|</li>
+ <li><a id="flowLogViewLink" href="#log">Log</a></li>
</ul>
<ul id="actionsBtns" class="buttons">
<li><div id="pausebtn" class="btn2">Pause</div></li>
@@ -98,6 +100,12 @@
</tbody>
</table>
</div>
+ <div id="flowLogView">
+ <div class="logHeader"><div class="logButtonRow"><div id="updateLogBtn" class="btn7">Load More</div></div></div>
+ <div class="logViewer">
+ <pre id="logSection" class="log"></pre>
+ </div>
+ </div>
</div>
#end
<ul id="jobMenu" class="contextMenu">
src/web/css/azkaban.css 66(+66 -0)
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index aea3d0b..fa0c773 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -328,6 +328,7 @@ tr:hover td {
.btn4,
.btn5,
.btn6,
+.btn7,
.btn-disabled {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
@@ -400,6 +401,7 @@ tr:hover td {
.btn4 {
background: -moz-linear-gradient(center top , white 0pt, #ECECEC 100%) repeat scroll 0 0 transparent;
background: -webkit-gradient(linear, center top, center bottom, from(#FFF), to(#ECECEC));
+ background: linear-gradient(top, #FFF 0, #FFF 1px, #E4E4E4 1px, #CECECE 100%);
border-color: #ccc;
color: #585858;
@@ -407,6 +409,33 @@ tr:hover td {
font-weight: normal;
}
+/* grey */
+.btn7 {
+ background: #f2f5f6; /* Old browsers */
+ background: -moz-linear-gradient(top, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f2f5f6), color-stop(37%,#e3eaed), color-stop(100%,#c8d7dc)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f2f5f6', endColorstr='#c8d7dc',GradientType=0 ); /* IE6-9 */
+
+ border-color: #9BA7AA;
+ color: #61696B;
+ display: inline-block;
+ font-weight: bold;
+}
+.btn7:hover {
+ background: #fcfeff; /* Old browsers */
+ background: -moz-linear-gradient(top, #fcfeff 0%, #f2f9fc 37%, #deeff4 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fcfeff), color-stop(37%,#f2f9fc), color-stop(100%,#deeff4)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #fcfeff 0%,#f2f9fc 37%,#deeff4 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #fcfeff 0%,#f2f9fc 37%,#deeff4 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #fcfeff 0%,#f2f9fc 37%,#deeff4 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #fcfeff 0%,#f2f9fc 37%,#deeff4 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfeff', endColorstr='#deeff4',GradientType=0 ); /* IE6-9 */
+}
+
/* grey right */
.btn5 {
background: #CECECE;
@@ -1137,6 +1166,43 @@ tr:hover td {
background: #E0E0E0;
}
+#flowLogView {
+ position: absolute;
+ top: 210px;
+ bottom: 5px;
+ left: 50px;
+ right: 50px;
+ background: #E0E0E0;
+}
+
+.logHeader {
+ height: 30px;
+ margin: 0px;
+ width: 100%;
+ background-color: #CCC;
+}
+
+.logButtonRow {
+ padding-top: 4px;
+ padding-left: 4px;
+}
+
+.logViewer {
+ position: absolute;
+ top: 35px;
+ bottom: 5px;
+ left: 5px;
+ right: 5px;
+ background-color: #FFF;
+ overflow:scroll;
+}
+
+.log {
+ padding-left: 15px;
+ font-family: "courier";
+ font-size: 10pt;
+}
+
.relative {
position: relative;
width: 100%;
src/web/js/azkaban.exflow.view.js 63(+62 -1)
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index 9c55b1e..79ec6a4 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -111,6 +111,7 @@ azkaban.FlowTabView= Backbone.View.extend({
events : {
"click #graphViewLink" : "handleGraphLinkClick",
"click #jobslistViewLink" : "handleJobslistLinkClick",
+ "click #flowLogViewLink" : "handleLogLinkClick",
"click #cancelbtn" : "handleCancelClick",
"click #restartbtn" : "handleRestartClick",
"click #pausebtn" : "handlePauseClick",
@@ -139,16 +140,29 @@ azkaban.FlowTabView= Backbone.View.extend({
handleGraphLinkClick: function(){
$("#jobslistViewLink").removeClass("selected");
$("#graphViewLink").addClass("selected");
+ $("#flowLogViewLink").removeClass("selected");
$("#jobListView").hide();
$("#graphView").show();
+ $("#flowLogView").hide();
},
handleJobslistLinkClick: function() {
$("#graphViewLink").removeClass("selected");
$("#jobslistViewLink").addClass("selected");
+ $("#flowLogViewLink").removeClass("selected");
$("#graphView").hide();
$("#jobListView").show();
+ $("#flowLogView").hide();
+ },
+ handleLogLinkClick: function() {
+ $("#graphViewLink").removeClass("selected");
+ $("#jobslistViewLink").removeClass("selected");
+ $("#flowLogViewLink").addClass("selected");
+
+ $("#graphView").hide();
+ $("#jobListView").hide();
+ $("#flowLogView").show();
},
handleFlowStatusChange: function() {
var data = this.model.get("data");
@@ -813,9 +827,51 @@ azkaban.ExecutionListView = Backbone.View.extend({
}
});
+var flowLogView;
+azkaban.FlowLogView = Backbone.View.extend({
+ events: {
+ "click #updateLogBtn" : "handleUpdate"
+ },
+ initialize: function(settings) {
+ this.model.set({"current": 0});
+ this.handleUpdate();
+ },
+ handleUpdate: function(evt) {
+ var current = this.model.get("current");
+ var requestURL = contextURL + "/executor";
+ var model = this.model;
+ ajaxCall(
+ requestURL,
+ {"execid": execId, "ajax":"fetchExecFlowLogs", "current": current, "max": 100000},
+ function(data) {
+ console.log("fetchLogs");
+ if (data.error) {
+ showDialog("Error", data.error);
+ }
+ else {
+ var log = $("#logSection").text();
+ if (!log) {
+ log = data.log;
+ }
+ else {
+ log += data.log;
+ }
+
+ current = data.current;
+ $("#logSection").text(log);
+ model.set({"current": current, "log": log});
+ }
+ }
+ );
+ }
+});
+
var graphModel;
azkaban.GraphModel = Backbone.Model.extend({});
+var logModel;
+azkaban.LogModel = Backbone.Model.extend({});
+
var updateTime = -1;
var updaterFunction = function() {
var requestURL = contextURL + "/executor";
@@ -869,10 +925,12 @@ $(function() {
var selected;
graphModel = new azkaban.GraphModel();
+ logModel = new azkaban.LogModel();
flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), model: graphModel});
svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
statusView = new azkaban.StatusView({el:$('#flow-status'), model: graphModel});
+ flowLogView = new azkaban.FlowLogView({el:$('#flowLogView'), model: logModel});
executionListView = new azkaban.ExecutionListView({el: $('#jobListView'), model:graphModel});
var requestURL = contextURL + "/executor";
@@ -903,9 +961,12 @@ $(function() {
if (hash == "#jobslist") {
flowTabView.handleJobslistLinkClick();
}
+ else if (hash == "#log") {
+ flowTabView.handleLogLinkClick();
+ }
}
- setTimeout(function() {updaterFunction()}, 2500);
+ setTimeout(function() {updaterFunction()}, 2500);
}
);
});