Add tooltip for container resource configs (#3371)

diff --git a/heron/tools/ui/resources/static/css/visstyle.css b/heron/tools/ui/resources/static/css/visstyle.css
index 34bee52..3f95ef5 100644
--- a/heron/tools/ui/resources/static/css/visstyle.css
+++ b/heron/tools/ui/resources/static/css/visstyle.css
@@ -175,7 +175,7 @@
   opacity: 1;
 }
 
-.aurora {
+.container {
   stroke: #888;
   stroke-width: 1px;
   fill: #f0f5fa;
diff --git a/heron/tools/ui/resources/static/js/physical-plan.js b/heron/tools/ui/resources/static/js/physical-plan.js
index 04443fa..49fe75f 100644
--- a/heron/tools/ui/resources/static/js/physical-plan.js
+++ b/heron/tools/ui/resources/static/js/physical-plan.js
@@ -24,7 +24,7 @@
 (function (global) {
   var topo, instances, containers;
 
-  function drawPhysicalPlan(planController, data, div_id, outerWidth, minHeight, cluster, environ, toponame) {
+  function drawPhysicalPlan(planController, data, packingData, div_id, outerWidth, minHeight, cluster, environ, toponame) {
     var margin = {
       top: 50,
       right: 0,
@@ -35,6 +35,7 @@
 
     instances = data.result.instances;
     containers = data.result.stmgrs;
+    containerPlan = packingData.container_plans;
 
     var sptblt = Object.keys(data.result.spouts)
                        .concat(Object.keys(data.result.bolts));
@@ -56,28 +57,42 @@
       }
     }
 
-    var auroraContainers = _.values(containers);
-    auroraContainers.forEach(function (container) {
+    var containerList = _.values(containers);
+    containerList.forEach(function (container) {
       // make ordering of instances the same in each container
       container.children = _.sortBy(container.children, 'name');
+      // Parse index
+      container.index = parseInt(container.id.split('-')[1]);
+      // Search for resource config in packing plan
+      container.required_resources = {
+        'cpu': 0,
+        'disk': 0,
+        'ram': 0
+      };
+      for (var i in containerPlan) {
+        var packing = containerPlan[i];
+        if (packing.id === container.index) {
+          container.required_resources = packing.required_resources;
+        }
+      }
     });
 
     // Sort the containers by their id so they are easier to find in the UI.
-    auroraContainers = _.sortBy(auroraContainers, function(container) {
-      return parseInt(container.id.split('-')[1]);
+    containerList = _.sortBy(containerList, function(container) {
+      return container.index;
     });
 
-    var maxInstances = d3.max(auroraContainers, function (d) {
+    var maxInstances = d3.max(containerList, function (d) {
       return d.children.length;
     });
 
     /**
-     * Config paramaters for aurora container/heron instance layout
+     * Config paramaters for container/heron instance layout
      */
 
-    // margin outside of each aurora container as a fraction of the container width
+    // margin outside of each container as a fraction of the container width
     var containerMarginRatio = 0.08;
-    // padding inside each aurora container as a fraction of container width
+    // padding inside each container as a fraction of container width
     var containerPaddingRatio = 0.05;
     // margin around each heron instance as a faction of the instance width
     var instanceMarginRatio = 0.15;
@@ -87,19 +102,19 @@
     var maxInstanceWith = 30;
 
     /**
-     * Compute the aurora container and instance sizes
+     * Compute the container and instance sizes
      */
 
     var innerCols = Math.ceil(Math.sqrt(maxInstances));
-    // compute the min aurora columns allowed to keep instance size below limit
-    var minAuroraCols = Math.ceil(width / (innerCols * maxInstanceWith / (1 - 2 * instanceMarginRatio)));
-    // compute the max aurora columns allowed to keep instance size above min limit
-    var maxAuroraCols = Math.ceil(width / (innerCols * minInstanceWidth / (1 - 2 * instanceMarginRatio)));
-    // compute # cols to give 50% more columns than rows, and bound it by max/min aurora columns
-    var desiredAuroraCols = Math.ceil(Math.sqrt(auroraContainers.length) * 1.5);
-    var cols = Math.min(maxAuroraCols, Math.max(minAuroraCols, desiredAuroraCols));
+    // compute the min columns allowed to keep instance size below limit
+    var minContainerCols = Math.ceil(width / (innerCols * maxInstanceWith / (1 - 2 * instanceMarginRatio)));
+    // compute the max columns allowed to keep instance size above min limit
+    var maxContainerCols = Math.ceil(width / (innerCols * minInstanceWidth / (1 - 2 * instanceMarginRatio)));
+    // compute # cols to give 50% more columns than rows, and bound it by max/min columns
+    var desiredContainerCols = Math.ceil(Math.sqrt(containerList.length) * 1.5);
+    var cols = Math.min(maxContainerCols, Math.max(minContainerCols, desiredContainerCols));
     // from there, compute the required number of rows and exact instance/container sizes...
-    var rows = Math.ceil(auroraContainers.length / cols);
+    var rows = Math.ceil(containerList.length / cols);
     var innerRows = Math.ceil(maxInstances / innerCols);
     var containerWidth = (width / cols) * (1 - 2 * containerMarginRatio);
     var containerPadding = containerWidth * containerPaddingRatio;
@@ -108,7 +123,7 @@
     var containerHeight = instanceSize * innerRows + containerPadding * 2;
     var height = (containerHeight + containerMargin * 2) * rows;
     var totalVisHeight = height + margin.top + margin.bottom;
-    var totalVisWidth = Math.min(width, auroraContainers.length * (containerWidth + containerMargin * 2));
+    var totalVisWidth = Math.min(width, containerList.length * (containerWidth + containerMargin * 2));
 
     var svg = d3.select(div_id).text("").append("svg")
         .attr("width", totalVisWidth)
@@ -122,7 +137,7 @@
       .attr('y', height + 25)
       .style('text-anchor', 'middle');
 
-    var tip = d3.tip()
+    var instance_tip = d3.tip()
         .attr('class', 'd3-tip main text-center')
         .offset([8, 0])
         .direction('s')
@@ -143,24 +158,37 @@
           return result;
         });
 
-    var auroraGs = svg.selectAll('.aurora-container')
-        .data(auroraContainers, function (d) { return d.id; })
+    var container_tip = d3.tip()
+        .attr('class', 'd3-tip main text-center container')
+        .offset([8, 0])
+        .direction('s')
+        .html(function (container) {
+          return '<ul>' +
+          '<li>cpu required: ' + container.required_resources.cpu + '</li>' +
+          '<li>ram required: ' + (container.required_resources.ram / 1048576).toFixed(2) + 'MB</li>' +
+          '<li>disk required: ' + (container.required_resources.disk / 1048576).toFixed(2) + 'MB</li>' +
+          '</ul>';
+        });
+
+    var containerGs = svg.selectAll('.physical-plan')
+        .data(containerList, function (d) { return d.id; })
         .enter()
       .append('g')
-        .attr('class', 'aurora-container')
+        .attr('class', 'physical-plan')
         .attr('transform', function (d, i) {
           var x = (i % cols) * (containerWidth + 2 * containerMargin) + containerMargin;
           var y = Math.floor(i / cols) * (containerHeight + 2 * containerMargin) + containerMargin;
           return 'translate(' + x + ',' + y + ')';
         });
 
-    auroraGs.append('rect')
+    containerGs.append('rect')
         .attr('width', containerWidth)
         .attr('height', containerHeight)
-        .attr('class', 'aurora');
+        .attr('class', 'container')
+        .on('mouseover', container_tip.show)
+        .on('mouseout', container_tip.hide);
 
-
-    auroraGs.selectAll('.instance')
+    containerGs.selectAll('.instance')
         .data(function (d) { return d.children; }, function (d) { return d.id; })
         .enter()
       .append('rect')
@@ -175,16 +203,17 @@
         .attr('x', function (d, i) { return containerPadding + (i % innerCols) * instanceSize + instanceSize * instanceMarginRatio; })
         .attr('y', function (d, i) { return containerPadding +Math.floor(i / innerCols) * instanceSize + instanceSize * instanceMarginRatio; })
         .on('mouseover', function (d) {
-          planController.physicalComponentHoverOver(d, tip);
+          planController.physicalComponentHoverOver(d, instance_tip);
         })
         .on('mouseout', function (d) {
-          planController.physicalComponentHoverOut(d, tip);
+          planController.physicalComponentHoverOut(d, instance_tip);
         })
         .on('click', function (d) {
           planController.physicalComponentClicked(d);
         });
 
-    svg.call(tip);
+    svg.call(instance_tip);
+    svg.call(container_tip);
   }
 
   // Stash the old values for transition.
diff --git a/heron/tools/ui/resources/templates/topology.html b/heron/tools/ui/resources/templates/topology.html
index 707b042..70dd003 100644
--- a/heron/tools/ui/resources/templates/topology.html
+++ b/heron/tools/ui/resources/templates/topology.html
@@ -208,18 +208,20 @@
       .append("p")
       .html('{{topology}}');
 
-    var logicalPlan, physicalPlan;
+    var logicalPlan, physicalPlan, packingPlan;
 
     d3.json("./{{topology}}/logicalplan.json",
       function(data) {
         // if there are any errors display using dialog box
         if (data.status != "success") {
-          console.log("Loading json failed!");
+          console.log("Loading logical plan json failed!");
           return;
         }
 
         logicalPlan = data.result;
-        if (physicalPlan) { startRendering(); }
+        if (physicalPlan && logicalPlan && packingPlan) {
+          startRendering();
+        }
       }
     );
 
@@ -227,12 +229,29 @@
       function(data) {
         // if there are any errors display using dialog box
         if (data.status != "success" ) {
-          console.log("Loading json failed!");
+          console.log("Loading physical plan json failed!");
           return;
         }
 
         physicalPlan = data;
-        if (logicalPlan) { startRendering(); }
+        if (physicalPlan && logicalPlan && packingPlan) {
+          startRendering();
+        }
+      }
+    );
+
+    d3.json("./{{topology}}/packingplan.json",
+      function(data) {
+        // if there are any errors display using dialog box
+        if (data.status != "success" ) {
+          console.log("Loading packing plan json failed!");
+          return;
+        }
+
+        packingPlan = data.result;
+        if (physicalPlan && logicalPlan && packingPlan) {
+          startRendering();
+        }
       }
     );
 
@@ -240,7 +259,7 @@
       drawLogicalPlan(planController, logicalPlan, "#logical-plan", $("#logical-plan").width(), 400, "{{baseUrl}}",
           "{{cluster}}", "{{environ}}", "{{topology}}");
       planController.planResized();
-      drawPhysicalPlan(planController, physicalPlan, "#physical-plan", $("#physical-plan").width(), 400, "{{baseUrl}}", "{{cluster}}",
+      drawPhysicalPlan(planController, physicalPlan, packingPlan, "#physical-plan", $("#physical-plan").width(), 400, "{{baseUrl}}", "{{cluster}}",
           "{{environ}}", "{{topology}}");
       planController.planResized();
     }