GUI -- Fixed intent perf GUI styling.

Change-Id: I552d3a50f7f4dd5bb1df7115c15eb6a04f538378
diff --git a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java
index 8683f3f..a979b65 100644
--- a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java
+++ b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java
@@ -37,10 +37,8 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
-import static org.onlab.util.Tools.groupedThreads;
+import static org.onlab.util.SharedExecutors.getPoolThreadExecutor;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -78,18 +76,13 @@
     private Map<NodeId, Integer> nodeToIndex;
 
     private NodeId nodeId;
-    private ExecutorService messageHandlingExecutor;
 
     @Activate
     public void activate() {
         nodeId = clusterService.getLocalNode().id();
 
-        // TODO: replace with shared executor
-        messageHandlingExecutor = Executors.newSingleThreadExecutor(
-                groupedThreads("onos/perf", "message-handler"));
-
         communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(),
-                                           messageHandlingExecutor);
+                                           getPoolThreadExecutor());
 
         nodes = clusterService.getNodes().toArray(new ControllerNode[]{});
         Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString()));
@@ -99,14 +92,13 @@
             nodeToIndex.put(nodes[i].id(), i);
         }
 
-        ui.setHeaders(getSampleHeaders());
         clearSamples();
+        ui.setCollector(this);
         log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
-        messageHandlingExecutor.shutdown();
         communicationService.removeSubscriber(SAMPLE);
         log.info("Stopped");
     }
diff --git a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java
index 96f2b32..909f3a5 100644
--- a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java
+++ b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java
@@ -156,7 +156,7 @@
     private ExecutorService workers;
     private ApplicationId appId;
     private Listener listener;
-    private boolean stopped;
+    private boolean stopped = true;
 
     private Timer reportTimer;
 
@@ -247,13 +247,18 @@
     }
 
     public void start() {
-        communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes()));
-        startTestRun();
+        if (stopped) {
+            stopped = false;
+            communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes()));
+            startTestRun();
+        }
     }
 
     public void stop() {
-        communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes()));
-        stopTestRun();
+        if (!stopped) {
+            communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes()));
+            stopTestRun();
+        }
     }
 
     private void logConfig(String prefix) {
@@ -282,7 +287,6 @@
     }
 
     private void stopTestRun() {
-        stopped = true;
         if (reporterTask != null) {
             reporterTask.cancel();
             reporterTask = null;
@@ -293,6 +297,11 @@
         } catch (InterruptedException e) {
             log.warn("Failed to stop worker", e);
         }
+
+        sampleCollector.recordSample(0, 0);
+        sampleCollector.recordSample(0, 0);
+        stopped = true;
+
         log.info("Stopped test run");
     }
 
diff --git a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
index f50ed3b..5d7132d 100644
--- a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
+++ b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
@@ -36,9 +36,7 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Random;
 import java.util.Set;
-import java.util.TimerTask;
 
 import static java.util.Collections.synchronizedSet;
 
@@ -58,25 +56,11 @@
     private UiExtension uiExtension = new UiExtension(views, this::newHandlers,
                                                       getClass().getClassLoader());
 
-    private List<String> headers = ImmutableList.of("One", "Two", "Three", "Four", "Five");
-
-    private Random random = new Random();
-    private TimerTask task;
+    private IntentPerfCollector collector;
 
     @Activate
     protected void activate() {
         uiExtensionService.register(uiExtension);
-//        task = new TimerTask() {
-//            @Override
-//            public void run() {
-//                Sample sample = new Sample(System.currentTimeMillis(), headers.size());
-//                for (int i = 0; i < headers.size(); i++) {
-//                    sample.data[i] = 25_000 + random.nextInt(20_000) - 5_000;
-//                }
-//                reportSample(sample);
-//            }
-//        };
-//        SharedExecutors.getTimer().scheduleAtFixedRate(task, 1000, 1000);
     }
 
     @Deactivate
@@ -96,12 +80,12 @@
     }
 
     /**
-     * Sets the headers for the subsequently reported samples.
+     * Binds the sample collector.
      *
-     * @param headers list of headers for future samples
+     * @param collector list of headers for future samples
      */
-    public void setHeaders(List<String> headers) {
-        this.headers = headers;
+    public void setCollector(IntentPerfCollector collector) {
+        this.collector = collector;
     }
 
     // Creates and returns session specific message handler.
@@ -122,22 +106,10 @@
         public void process(ObjectNode message) {
             streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart");
             if (streamingEnabled) {
-                sendHeaders();
+                sendInitData();
             }
         }
 
-        private void sendHeaders() {
-            ArrayNode an = mapper.createArrayNode();
-            for (String header : headers) {
-                an.add(header);
-            }
-
-            ObjectNode sn = mapper.createObjectNode();
-            sn.set("headers", an);
-
-            connection().sendMessage("intentPerfHeaders", 0, sn);
-        }
-
         @Override
         public void init(UiConnection connection, ServiceDirectory directory) {
             super.init(connection, directory);
@@ -152,18 +124,34 @@
 
         private void send(Sample sample) {
             if (streamingEnabled) {
-                ArrayNode an = mapper.createArrayNode();
-                for (double d : sample.data) {
-                    an.add(d);
-                }
-
-                ObjectNode sn = mapper.createObjectNode();
-                sn.put("time", sample.time);
-                sn.set("data", an);
-
-                connection().sendMessage("intentPerfSample", 0, sn);
+                connection().sendMessage("intentPerfSample", 0, sampleNode(sample));
             }
         }
+
+        private void sendInitData() {
+            ObjectNode rootNode = mapper.createObjectNode();
+            ArrayNode an = mapper.createArrayNode();
+            ArrayNode sn = mapper.createArrayNode();
+            rootNode.set("headers", an);
+            rootNode.set("samples", sn);
+
+            collector.getSampleHeaders().forEach(an::add);
+            collector.getSamples().forEach(s -> sn.add(sampleNode(s)));
+            connection().sendMessage("intentPerfInit", 0, rootNode);
+        }
+
+        private ObjectNode sampleNode(Sample sample) {
+            ObjectNode sampleNode = mapper.createObjectNode();
+            ArrayNode an = mapper.createArrayNode();
+            sampleNode.put("time", sample.time);
+            sampleNode.set("data", an);
+
+            for (double d : sample.data) {
+                an.add(d);
+            }
+            return sampleNode;
+        }
+
     }
 
 }
diff --git a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/Xdata.csv b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/Xdata.csv
deleted file mode 100644
index 1673d26..0000000
--- a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/Xdata.csv
+++ /dev/null
@@ -1,19 +0,0 @@
-date,value,node
-00:55:15,68.38,node1
-00:55:15,55.61,node2
-00:55:15,74.00,node3
-00:55:30,74.20,node1
-00:55:30,77.60,node2
-00:55:30,74.80,node3
-00:55:45,74.60,node1
-00:55:45,72.80,node2
-00:55:45,77.00,node3
-00:56:00,73.60,node1
-00:56:00,75.00,node2
-00:56:00,76.98,node3
-00:56:15,75.82,node1
-00:56:15,75.40,node2
-00:56:15,76.00,node3
-00:56:30,75.60,node1
-00:56:30,74.59,node2
-00:56:30,74.01,node3
\ No newline at end of file
diff --git a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/data.csv b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/data.csv
deleted file mode 100644
index f8f9938..0000000
--- a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/data.csv
+++ /dev/null
@@ -1,19 +0,0 @@
-key,value,date
-Group1,37,00:23:00
-Group2,12,00:23:00
-Group3,46,00:23:00
-Group1,32,00:23:05
-Group2,19,00:23:05
-Group3,42,00:23:05
-Group1,45,00:23:10
-Group2,16,00:23:10
-Group3,44,00:23:10
-Group1,24,00:23:15
-Group2,52,00:23:15
-Group3,64,00:23:15
-Group1,34,00:23:20
-Group2,62,00:23:20
-Group3,74,00:23:20
-Group1,34,00:23:25
-Group2,62,00:23:25
-Group3,74,00:23:25
\ No newline at end of file
diff --git a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css
index d98b620..15e95d5 100644
--- a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css
+++ b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css
@@ -22,9 +22,8 @@
     font: 12px sans-serif;
 }
 
-.line {
+.line,.lineTotal {
     fill: none;
-    stroke: #000;
     stroke-width: 2px;
 }
 
@@ -37,16 +36,20 @@
 
 .light .axis path,
 .light .axis line,
+.light .lineTotal {
+    stroke: #333;
+}
+
 .light .axis text {
-    stroke: #999;
+    fill: #333;
 }
 
 .dark .axis path,
 .dark .axis line,
-.dark .axis text {
+.dark .lineTotal {
     stroke: #eee;
 }
 
-.axis text {
-    stroke-width: 0.3;
-}
\ No newline at end of file
+.dark .axis text {
+    fill: #eee;
+}
diff --git a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js
index b578163..94304c7 100644
--- a/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js
+++ b/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js
@@ -31,7 +31,7 @@
 
     // ==========================
 
-    function createGraph(h) {
+    function createGraph(h, samples) {
         var stopped = false,
             n = 243,
             duration = 750,
@@ -75,7 +75,27 @@
         headers.forEach(function (h, li) {
             // Prime the data to match the headers and zero it out.
             data[li] = d3.range(n).map(function() { return 0 });
-            theSample[li] = 0;
+
+            if (li < headers.length - 1) {
+                samples.forEach(function (s, i) {
+                    var di = dataIndex(s.time);
+                    if (di >= 0) {
+                        data[li][di] = s.data[li];
+                    }
+                });
+
+                data[li].forEach(function (d, i) {
+                    if (!d && i > 0) {
+                        data[li][i] = data[li][i - 1];
+                    }
+                });
+            } else {
+                data[li].forEach(function (t, i) {
+                    for (var si = 0; si < headers.length - 1; si++) {
+                        data[li][i] = data[si][i];
+                    }
+                });
+            }
 
             // Create the lines
             lines[li] = d3.svg.line()
@@ -88,15 +108,24 @@
                 .attr("clip-path", "url(#intent-perf-clip)")
                 .append("path")
                 .datum(function () { return data[li]; })
-                .attr("id", "line" + li)
-                .style("stroke", lineColor(li))
-                .attr("class", "line");
+                .attr("id", "line" + li);
+
+            if (li < headers.length - 1) {
+                paths[li].attr("class", "line").style("stroke", lineColor(li));
+            } else {
+                paths[li].attr("class", "lineTotal");
+            }
         });
 
+        function dataIndex(time) {
+            var delta = now.getTime() - time;
+            var di = Math.round(n - 2 - (delta / duration));
+            // $log.info('now=' + now.getTime() + '; then=' + time + '; delta=' + delta + '; di=' + di + ';');
+            return di >= n || di < 0 ? -1 : di;
+        }
+
         function lineColor(li) {
-            return li < headers.length - 1 ?
-                sus.cat7().getColor(li, false, ts.theme()) :
-                ts.theme() === 'light' ? '#333' : '#eee';
+            return sus.cat7().getColor(li, false, ts.theme());
         }
 
         function tick() {
@@ -130,13 +159,17 @@
         function start() {
             stopped = false;
             headers.forEach(function (h, li) {
-                theSample[li] = 0;
+                theSample[li] = data[li][n-1];
             });
             tick();
         }
 
         function stop() {
-            stopped = true;
+            headers.forEach(function (h, li) {
+                theSample[li] = 0;
+            });
+            // Schedule delayed stop to allow 0s to render.
+            setTimeout(function () { stopped = true; }, 1000);
         }
 
         function resize(dim) {
@@ -190,7 +223,7 @@
     function createAndInitGraph(d) {
         if (!graph) {
             d.headers.push("total");
-            graph = createGraph(d.headers);
+            graph = createGraph(d.headers, d.samples);
         }
         graph.start();
     }
@@ -213,7 +246,7 @@
 
     function createHandlerMap() {
         handlerMap = {
-            intentPerfHeaders: createAndInitGraph,
+            intentPerfInit: createAndInitGraph,
             intentPerfSample: recordSample
         };
     }
diff --git a/apps/test/intent-perf/src/main/resources/sliding.html b/apps/test/intent-perf/src/main/resources/sliding.html
deleted file mode 100644
index 8dc501b..0000000
--- a/apps/test/intent-perf/src/main/resources/sliding.html
+++ /dev/null
@@ -1,205 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<style>
-
-    svg {
-        font: 10px sans-serif;
-    }
-
-    .line {
-        fill: none;
-        stroke: darkgreen;
-        stroke-width: 2px;
-    }
-
-    .axis path,
-    .axis line {
-        fill: none;
-        stroke: #999;
-        stroke-width: 2px;
-        shape-rendering: crispEdges;
-    }
-
-</style>
-<body>
-<script src="http://d3js.org/d3.v3.min.js"></script>
-<script>
-    (function () {
-        var cs = 0,
-                samples = [
-                    89.53,
-                    37515.81,
-                    104609.6,
-                    113105.11,
-                    103194.74,
-                    122151.63,
-                    128623.9,
-                    137325.33,
-                    154897.31,
-                    161235.07,
-                    162025.4,
-                    164902.64,
-                    158196.26,
-                    161072.44,
-                    160792.54,
-                    164692.44,
-                    161979.74,
-                    162137.4,
-                    159325.19,
-                    170465.44,
-                    168186.46,
-                    171152.34,
-                    168221.02,
-                    167440.73,
-                    165003.39,
-                    166855.18,
-                    157268.79,
-                    164087.54,
-                    162265.21,
-                    165990.16,
-                    176364.01,
-                    172064.07,
-                    184872.24,
-                    183249.8,
-                    182282.47,
-                    171475.11,
-                    158880.58,
-                    166016.69,
-                    168233.16,
-                    177759.92,
-                    179742.87,
-                    170819.44,
-                    167577.73,
-                    169479.9,
-                    175544.89,
-                    183792.01,
-                    184689.52,
-                    178503.87,
-                    173219.27,
-                    179085.49,
-                    179700.54,
-                    174281.17,
-                    181353.08,
-                    180173.14,
-                    184093.16,
-                    186011.5,
-                    176952.79,
-                    175319.2,
-                    169001.05,
-                    174545.12,
-                    169156.29,
-                    171804.3,
-                    159155.54,
-                    154709.96,
-                    157263.97
-                ],
-                theSample,
-                headers = [ "Whole", "Half", "Third" ];
-
-        var n = 243,
-                duration = 750,
-                now = new Date(Date.now() - duration),
-                data = [];
-
-        headers.forEach(function (d, li) {
-            data[li] = d3.range(n).map(function () { return 0; });
-        });
-
-        var margin = {top: 20, right: 100, bottom: 20, left: 100},
-                width = 960 - margin.right,
-                height = 512 - margin.top - margin.bottom;
-
-        var x = d3.time.scale()
-                .domain([now - (n - 2) * duration, now - duration])
-                .range([0, width]);
-
-        var y = d3.scale.linear()
-                .domain([0, 200000])
-                .range([height, 0]);
-
-        var svg = d3.select("body").append("p").append("svg")
-                .attr("width", width + margin.left + margin.right)
-                .attr("height", height + margin.top + margin.bottom)
-                .append("g")
-                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
-
-        svg.append("defs").append("clipPath")
-                .attr("id", "clip")
-                .append("rect")
-                .attr("width", width)
-                .attr("height", height);
-
-        var axis = svg.append("g")
-                .attr("class", "x axis")
-                .attr("transform", "translate(0," + height + ")")
-                .call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
-
-        svg.append("g")
-                .attr("class", "y axis")
-                .call(d3.svg.axis().scale(y).orient("left"));
-
-        svg.append("g")
-                .attr("class", "y axis")
-                .attr("transform", "translate(" + width + " ,0)")
-                .call(d3.svg.axis().scale(y).orient("right"));
-
-        var lines = [], paths = [];
-        data.forEach(function (p, li) {
-            lines[li]= d3.svg.line()
-                    .interpolate("basis")
-                    .x(function (d, i) {
-                        return x(now - (n - 1 - i) * duration);
-                    })
-                    .y(function (d, i) {
-                        return y(d);
-                    });
-
-            paths[li] = svg.append("g")
-                    .attr("clip-path", "url(#clip)")
-                    .append("path")
-                    .datum(function () { return data[li]; })
-                    .attr("id", "line" + li)
-                    .attr("class", "line");
-        });
-
-        var transition = d3.select({}).transition()
-                .duration(750)
-                .ease("linear");
-
-        function tick() {
-            transition = transition.each(function () {
-                // update the domains
-                now = new Date();
-                x.domain([now - (n - 2) * duration, now - duration]);
-
-                data.forEach(function (d, li) {
-                    // push the new most recent sample onto the back
-                    d.push(theSample[li]);
-
-                   // redraw the line and slide it left
-                    paths[li].attr("d", lines[li]).attr("transform", null);
-                    paths[li].transition().attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
-
-                    // pop the old data point off the front
-                    d.shift();
-                });
-
-                // slide the x-axis left
-                axis.call(x.axis);
-
-            }).transition().each("start", tick);
-        }
-
-        function setSample() {
-            var v = samples[cs++];
-            theSample = [ v, v/2, v/3 ];
-        }
-
-        setSample();
-        setInterval(setSample, 1000);
-        tick();
-
-    })()
-</script>
-</body>
-</html>