azkaban-aplcache

Adding flow logs.

9/6/2012 3:40:29 AM

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">  
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%;
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);
 	      }
 	    );
 });