azkaban-memoizeit

Details

diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 7346df6..f8c4281 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -104,6 +104,7 @@
 				<li id="graphViewLink"><a href="#graph">Graph</a></li>
 				<li id="jobslistViewLink"><a href="#jobslist">Job List</a></li>
 				<li id="flowLogViewLink"><a href="#log">Flow Log</a></li>
+				<li id="statsLink"><a href="#stats">Stats</a></li>
 				<li class="nav-button pull-right"><button type="button" id="pausebtn" class="btn btn-primary">Pause</button></li>
 				<li class="nav-button pull-right"><button type="button" id="resumebtn" class="btn btn-primary">Resume</button></li>
 				<li class="nav-button pull-right"><button type="button" id="cancelbtn" class="btn btn-danger">Cancel</button></li>
@@ -160,7 +161,14 @@
           </div><!-- /.log-viewer -->
         </div><!-- /.col-xs-12 -->
       </div><!-- /.row -->
-    </div><!-- /. -->
+    </div><!-- /.container-full -->
+
+  ## Stats view.
+
+    <div class="container-full" id="statsView">
+			<div class="row" id="stats-view-content">
+			</div><!-- /.row -->
+    </div><!-- /.container-fill -->
 	
 	## Error message message dialog.
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index aa75aaf..89cdcf9 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -34,6 +34,7 @@
 		<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.stats.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.graph.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
@@ -140,9 +141,25 @@
 
     <div class="container-full" id="summaryView">
 			<div class="row" id="summary-view-content">
-			</div><!-- /.row -->
+      </div><!-- /.row -->
+      <div class="row" id="last-run-stats">
+        <div class="col-xs-12">
+          <h3>Last Run Stats</h3>
+          <div id="flow-stats-container">
+            <div class="alert alert-info">
+              <h4>Analyze last run</h4>
+              <p>Analyze the last run for aggregate performance statistics. <strong>Note:</strong> this may take a few minutes, especially if your flow is large.</p>
+              <p>
+                <button type="button" id="analyze-btn" class="btn btn-primary">Analyze</button>
+              </p>
+            </div>
+          </div>
+        </div><!-- /.col-lg-12 -->
+      </div>
     </div><!-- /.container-fill -->
 
+  ## Context menu and the rest of the page.
+
     <div class="container-full">
 			<div id="contextMenu">
 			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
index 2958199..3acf1dd 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -85,7 +85,7 @@
             </div>
 		#end
 	#else
-            <div class="alert alert-info">
+            <div class="alert alert-default">
               <h4>No Flows</h4>
               <p>No flows have been uploaded to this project yet.</p>
             </div>
diff --git a/src/tl/flowsummary.tl b/src/tl/flowsummary.tl
index 84ea565..f9e8b84 100644
--- a/src/tl/flowsummary.tl
+++ b/src/tl/flowsummary.tl
@@ -63,18 +63,3 @@
             </div>
           {/schedule}
         </div>
-
-        <div class="col-xs-12">
-          <h3>Last Run Stats</h3>
-          <div id="last-run-container">
-            <div class="alert alert-info" id="analyze-last-run">
-              <h4>Analyze last run</h4>
-              <p>Analyze the last run for aggregate performance statistics. <strong>Note:</strong> this may take a few minutes, especially if your flow is large.</p>
-              <p>
-                <button type="button" id="analyze-btn" class="btn btn-primary">Analyze</button>
-              </p>
-            </div>
-          </div>
-        </div><!-- /.col-lg-12 -->
-
-
diff --git a/src/web/js/azkaban.flow.stats.view.js b/src/web/js/azkaban.flow.stats.view.js
new file mode 100644
index 0000000..5f48edc
--- /dev/null
+++ b/src/web/js/azkaban.flow.stats.view.js
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+$.namespace('azkaban');
+
+azkaban.FlowStatsModel = Backbone.Model.extend({});
+azkaban.FlowStatsView = Backbone.View.extend({
+  events: {
+  },
+
+	initialize: function(settings) {
+		this.model.bind('change:view', this.handleChangeView, this);
+		this.model.bind('render', this.render, this);
+  },
+	
+  render: function(evt) {
+  },
+
+  show: function(execId) {
+    this.analyzeExecution(execId);
+  },
+
+  fetchJobs: function(execId) {
+    var requestURL = contextURL + "/executor";
+    var requestData = {"execid": execId, "ajax":"fetchexecflow"};
+    var jobs = [];
+    var successHandler = function(data) {
+      for (var i = 0; i < data.nodes.length; ++i) {
+        var node = data.nodes[i];
+        jobs.push(node.id);
+      }
+    };
+    $.ajax({
+      url: requestURL,
+      data: requestData,
+      success: successHandler,
+      dataType: "json",
+      async: false
+    });
+    return jobs;
+  },
+
+  fetchJobStats: function(jobId, execId) {
+    var requestURL = contextURL + "/executor";
+    var requestData = {
+      "execid": execId,
+      "flowid": flowId,
+      "jobid": jobId,
+      "ajax": "fetchExecJobStats"
+    };
+    var stats = null;
+    var successHandler = function(data) {
+      stats = data;
+    };
+    $.ajax({
+      url: requestURL,
+      data: requestData,
+      success: successHandler,
+      dataType: "json",
+      async: false
+    });
+    return stats;
+  },
+
+  updateStats: function(jobStats, data) {
+    var aggregateStats = data.stats;
+    var state = jobStats.state;
+    var conf = jobStats.conf;
+    var mappers = parseInt(state.totalMappers);
+    var reducers = parseInt(state.totalReducers);
+    if (mappers > aggregateStats.maxMapSlots) {
+      aggregateStats.maxMapSlots = mappers;
+    }
+    if (reducers > aggregateStats.maxReduceSlots) {
+      aggregateStats.maxReduceSlots = reducers;
+    }
+    aggregateStats.totalMapSlots += mappers;
+    aggregateStats.totalReduceSlots += reducers;
+  },
+
+  analyzeExecution: function(execId) {
+    var jobs = this.fetchJobs(execId);
+    if (jobs == null) {
+      this.model.set({'data': null});
+      this.model.trigger('render');
+      return;
+    }
+
+    var data = {
+      success: false,
+      message: null,
+      warnings: [],
+      stats: {
+        maxMapSlots: 0,
+        maxReduceSlots: 0,
+        totalMapSlots: 0,
+        totalReduceSlots: 0,
+        numJobs: jobs.length,
+        longestTaskTime: 0
+      }
+    };
+
+    for (var i = 0; i < jobs.length; ++i) {
+      var job = jobs[i];
+      var jobStats = this.fetchJobStats(job, execId);
+      if (jobStats.jobStats == null) {
+        data.warnings.push("No job stats available for job " + job.id);
+        continue;
+      }
+      for (var j = 0; j < jobStats.jobStats.length; ++j) {
+        this.updateStats(jobStats.jobStats[j], data);
+      }
+    }
+    data.success = true;
+    this.model.set({'data': data});
+    this.model.trigger('render');
+  },
+
+	render: function(evt) {
+    var view = this;
+    var data = this.model.get('data');
+    if (data == null) {
+      var msg = { message: "Error retrieving flow stats."};
+      dust.render("flowsummary-no-data", msg, function(err, out) {
+        view.display(out);
+      });
+    }
+    else if (data.success == "false") {
+      dust.render("flowsummary-no-data", data, function(err, out) {
+        view.display(out);
+      });
+    }
+    else {
+      dust.render("flowsummary-last-run", data, function(err, out) {
+        view.display(out);
+      });
+    }
+  },
+
+  display: function(out) {
+    $('#flow-stats-container').html(out);
+  },
+});
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 959dc4a..028a07b 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -347,132 +347,16 @@ azkaban.SummaryView = Backbone.View.extend({
     var view = this;
     var successHandler = function(data) {
       if (data.success == "false" || data.execId == null) {
-        view.renderLastRun(data);
+        dust.render("flowsummary-no-data", data, function(err, out) {
+          $('#flow-stats-container').html(out);
+        });
         return;
       }
-      view.analyzeLastRun(data.execId);
+      flowStatsView.show(data.execId);
     };
     $.get(requestURL, requestData, successHandler, 'json');
 	},
 
-  fetchJobs: function(execId) {
-    var requestURL = contextURL + "/executor";
-    var requestData = {"execid": execId, "ajax":"fetchexecflow"};
-    var jobs = [];
-    var successHandler = function(data) {
-      for (var i = 0; i < data.nodes.length; ++i) {
-        var node = data.nodes[i];
-        jobs.push(node.id);
-      }
-    };
-    $.ajax({
-      url: requestURL,
-      data: requestData,
-      success: successHandler,
-      dataType: "json",
-      async: false
-    });
-    return jobs;
-  },
-
-  fetchJobStats: function(jobId, execId) {
-    var requestURL = contextURL + "/executor";
-    var requestData = {
-      "execid": execId,
-      "flowid": flowId,
-      "jobid": jobId,
-      "ajax": "fetchExecJobStats"
-    };
-    var stats = null;
-    var successHandler = function(data) {
-      stats = data;
-    };
-    $.ajax({
-      url: requestURL,
-      data: requestData,
-      success: successHandler,
-      dataType: "json",
-      async: false
-    });
-    return stats;
-  },
-
-  updateStats: function(jobStats, data) {
-    var aggregateStats = data.stats;
-    var state = jobStats.state;
-    var conf = jobStats.conf;
-    var mappers = parseInt(state.totalMappers);
-    var reducers = parseInt(state.totalReducers);
-    if (mappers > aggregateStats.maxMapSlots) {
-      aggregateStats.maxMapSlots = mappers;
-    }
-    if (reducers > aggregateStats.maxReduceSlots) {
-      aggregateStats.maxReduceSlots = reducers;
-    }
-    aggregateStats.totalMapSlots += mappers;
-    aggregateStats.totalReduceSlots += reducers;
-  },
-
-  analyzeLastRun: function(execId) {
-    var jobs = this.fetchJobs(execId);
-    if (jobs == null) {
-      this.renderLastRun(null);
-      return;
-    }
-
-    var data = {
-      success: false,
-      message: null,
-      warnings: [],
-      stats: {
-        maxMapSlots: 0,
-        maxReduceSlots: 0,
-        totalMapSlots: 0,
-        totalReduceSlots: 0,
-        numJobs: jobs.length,
-        longestTaskTime: 0
-      }
-    };
-
-    for (var i = 0; i < jobs.length; ++i) {
-      var job = jobs[i];
-      var jobStats = this.fetchJobStats(job, execId);
-      if (jobStats.jobStats == null) {
-        data.warnings.push("No job stats available for job " + job.id);
-        continue;
-      }
-      for (var j = 0; j < jobStats.jobStats.length; ++j) {
-        this.updateStats(jobStats.jobStats[j], data);
-      }
-    }
-    data.success = true;
-    this.renderLastRun(data);
-  },
-
-  renderLastRun: function(data) {
-    var view = this;
-    if (data == null) {
-      var msg = { message: "Error retrieving last run data."};
-      dust.render("flowsummary-no-data", msg, function(err, out) {
-        view.displayLastRun(out);
-      });
-    }
-    else if (data.success == "false") {
-      dust.render("flowsummary-no-data", data, function(err, out) {
-        view.displayLastRun(out);
-      });
-    }
-    else {
-      dust.render("flowsummary-last-run", data, function(err, out) {
-        view.displayLastRun(out);
-      });
-    }
-  },
-
-  displayLastRun: function(out) {
-    $('#last-run-container').html(out);
-  },
-
 	handleChangeView: function(evt) {
 	},
 
@@ -544,6 +428,9 @@ azkaban.ExecutionModel = Backbone.Model.extend({});
 var summaryModel;
 azkaban.SummaryModel = Backbone.Model.extend({});
 
+var flowStatsView;
+var flowStatsModel;
+
 var mainSvgGraphView;
 
 $(function() {
@@ -560,7 +447,13 @@ $(function() {
 		el: $('#summaryView'),
 		model: summaryModel
 	});
-	
+
+  flowStatsModel = new azkaban.FlowStatsModel();
+	flowStatsView = new azkaban.FlowStatsView({
+		el: $('#last-run-stats'),
+		model: flowStatsModel
+	});
+
   flowTabView = new azkaban.FlowTabView({
 		el: $('#headertabs'), 
 		selectedView: selected