Enhancing the GUI traffic-related code.
Fixed a defect in reactive forwarding.

Change-Id: I1a91f6e5f57b39425ef06092c82b06d04c9b59a0
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
index 7bb9b86..bfa7172 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
@@ -36,6 +36,7 @@
 import org.onlab.onos.net.HostId;
 import org.onlab.onos.net.HostLocation;
 import org.onlab.onos.net.Link;
+import org.onlab.onos.net.LinkKey;
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DeviceEvent;
 import org.onlab.onos.net.device.DeviceService;
@@ -66,6 +67,7 @@
 
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -82,6 +84,7 @@
 import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
 import static org.onlab.onos.net.DeviceId.deviceId;
 import static org.onlab.onos.net.HostId.hostId;
+import static org.onlab.onos.net.LinkKey.linkKey;
 import static org.onlab.onos.net.PortNumber.P0;
 import static org.onlab.onos.net.PortNumber.portNumber;
 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
@@ -110,8 +113,6 @@
     private static final String KB_UNIT = "KB";
     private static final String B_UNIT = "B";
 
-    private static final String ANIMATED = "animated";
-
     protected final ServiceDirectory directory;
     protected final ClusterService clusterService;
     protected final DeviceService deviceService;
@@ -560,14 +561,51 @@
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode paths = mapper.createArrayNode();
         payload.set("paths", paths);
-        for (Link link : linkService.getLinks()) {
-            Set<Link> links = new HashSet<>();
-            links.add(link);
-            addPathTraffic(paths, "plain", "secondary", links);
+
+        ObjectNode pathNodeN = mapper.createObjectNode();
+        ArrayNode linksNodeN = mapper.createArrayNode();
+        ArrayNode labelsN = mapper.createArrayNode();
+
+        pathNodeN.put("class", "plain").put("traffic", false);
+        pathNodeN.set("links", linksNodeN);
+        pathNodeN.set("labels", labelsN);
+        paths.add(pathNodeN);
+
+        ObjectNode pathNodeT = mapper.createObjectNode();
+        ArrayNode linksNodeT = mapper.createArrayNode();
+        ArrayNode labelsT = mapper.createArrayNode();
+
+        pathNodeT.put("class", "secondary").put("traffic", true);
+        pathNodeT.set("links", linksNodeT);
+        pathNodeT.set("labels", labelsT);
+        paths.add(pathNodeT);
+
+        for (BiLink link : consolidateLinks(linkService.getLinks())) {
+            boolean bi = link.two != null;
+            if (isInfrastructureEgress(link.one) ||
+                    (bi && isInfrastructureEgress(link.two))) {
+                link.addLoad(statService.load(link.one));
+                link.addLoad(bi ? statService.load(link.two) : null);
+                if (link.hasTraffic) {
+                    linksNodeT.add(compactLinkString(link.one));
+                    labelsT.add(formatBytes(link.bytes));
+                } else {
+                    linksNodeN.add(compactLinkString(link.one));
+                    labelsN.add("");
+                }
+            }
         }
         return envelope("showTraffic", sid, payload);
     }
 
+    private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
+        Map<LinkKey, BiLink> biLinks = new HashMap<>();
+        for (Link link : links) {
+            addLink(biLinks, link);
+        }
+        return biLinks.values();
+    }
+
     // Produces JSON message to trigger flow overview visualization
     protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
         ObjectNode payload = mapper.createObjectNode();
@@ -603,6 +641,33 @@
         ArrayNode paths = mapper.createArrayNode();
         payload.set("paths", paths);
 
+        // Classify links based on their traffic traffic first...
+        Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
+
+        // Then separate the links into their respective classes and send them out.
+        Map<String, ObjectNode> pathNodes = new HashMap<>();
+        for (BiLink biLink : biLinks.values()) {
+            boolean hasTraffic = biLink.hasTraffic;
+            String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
+            ObjectNode pathNode = pathNodes.get(tc);
+            if (pathNode == null) {
+                pathNode = mapper.createObjectNode()
+                        .put("class", tc).put("traffic", hasTraffic);
+                pathNode.set("links", mapper.createArrayNode());
+                pathNode.set("labels", mapper.createArrayNode());
+                pathNodes.put(tc, pathNode);
+                paths.add(pathNode);
+            }
+            ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
+            ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
+        }
+
+        return envelope("showTraffic", sid, payload);
+    }
+
+    // Classifies the link traffic according to the specified classes.
+    private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
+        Map<LinkKey, BiLink> biLinks = new HashMap<>();
         for (TrafficClass trafficClass : trafficClasses) {
             for (Intent intent : trafficClass.intents) {
                 boolean isOptical = intent instanceof OpticalConnectivityIntent;
@@ -611,24 +676,49 @@
                     for (Intent installable : installables) {
                         String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
                         if (installable instanceof PathIntent) {
-                            addPathTraffic(paths, cls, ANIMATED,
-                                           ((PathIntent) installable).path().links());
+                            classifyLinks(cls, biLinks, ((PathIntent) installable).path().links());
                         } else if (installable instanceof LinkCollectionIntent) {
-                            addPathTraffic(paths, cls, ANIMATED,
-                                           ((LinkCollectionIntent) installable).links());
+                            classifyLinks(cls, biLinks, ((LinkCollectionIntent) installable).links());
                         } else if (installable instanceof OpticalPathIntent) {
-                            addPathTraffic(paths, cls, ANIMATED,
-                                           ((OpticalPathIntent) installable).path().links());
+                            classifyLinks(cls, biLinks, ((OpticalPathIntent) installable).path().links());
                         }
-
                     }
                 }
             }
         }
-
-        return envelope("showTraffic", sid, payload);
+        return biLinks;
     }
 
+
+    // Adds the link segments (path or tree) associated with the specified
+    // connectivity intent
+    private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
+                               Iterable<Link> links) {
+        if (links != null) {
+            for (Link link : links) {
+                BiLink biLink = addLink(biLinks, link);
+                if (isInfrastructureEgress(link)) {
+                    biLink.addLoad(statService.load(link));
+                    biLink.addClass(type);
+                }
+            }
+        }
+    }
+
+
+    private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
+        LinkKey key = canonicalLinkKey(link);
+        BiLink biLink = biLinks.get(key);
+        if (biLink != null) {
+            biLink.setOther(link);
+        } else {
+            biLink = new BiLink(key, link);
+            biLinks.put(key, biLink);
+        }
+        return biLink;
+    }
+
+
     // Adds the link segments (path or tree) associated with the specified
     // connectivity intent
     protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
@@ -646,7 +736,7 @@
                     String label = "";
                     if (load.rate() > 0) {
                         hasTraffic = true;
-                        label = format(load);
+                        label = formatBytes(load.latest());
                     }
                     labels.add(label);
                 }
@@ -660,8 +750,7 @@
     }
 
     // Poor-mans formatting to get the labels with byte counts looking nice.
-    private String format(Load load) {
-        long bytes = load.latest();
+    private String formatBytes(long bytes) {
         String unit;
         double value;
         if (bytes > GB) {
@@ -713,6 +802,44 @@
         return result;
     }
 
+    // Produces canonical link key, i.e. one that will match link and its inverse.
+    private LinkKey canonicalLinkKey(Link link) {
+        String sn = link.src().elementId().toString();
+        String dn = link.dst().elementId().toString();
+        return sn.compareTo(dn) < 0 ?
+                linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
+    }
+
+    // Representation of link and its inverse and any traffic data.
+    private class BiLink {
+        public final LinkKey key;
+        public final Link one;
+        public Link two;
+        public boolean hasTraffic = false;
+        public long bytes = 0;
+        public String classes = "";
+
+        BiLink(LinkKey key, Link link) {
+            this.key = key;
+            this.one = link;
+        }
+
+        void setOther(Link link) {
+            this.two = link;
+        }
+
+        void addLoad(Load load) {
+            if (load != null) {
+                this.hasTraffic = hasTraffic || load.rate() > 0;
+                this.bytes += load.latest();
+            }
+        }
+
+        void addClass(String trafficClass) {
+            classes = classes + " " + trafficClass;
+        }
+    }
+
     // Auxiliary key/value carrier.
     private class Prop {
         public final String key;
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
index d624d04..84e2e02 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
@@ -558,7 +558,7 @@
         }
     }
 
-    // Accummulates events to drive methodic update of the summary pane.
+    // Accumulates events to drive methodic update of the summary pane.
     private class InternalEventAccummulator extends AbstractEventAccumulator {
         protected InternalEventAccummulator() {
             super(new Timer("topo-summary"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);