ONOS-6259: Topo2 - Implement server-side highlighting model
- (Still WIP)
- refactored trafficSummary() and related methods up into superclass
- introduced hook method for link aggregation

Change-Id: I0a993bbc5ba5f0e861214f8d06877dad3f7bc8ee
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
index dfedaf3..c59f2c5 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -17,15 +17,27 @@
 
 package org.onosproject.ui.impl;
 
+import org.onosproject.incubator.net.PortStatisticsService.MetricType;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.statistic.Load;
 import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.impl.topo.util.TrafficLink;
+import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
 import org.onosproject.ui.topo.AbstractTopoMonitor;
+import org.onosproject.ui.topo.Highlights;
 import org.onosproject.ui.topo.TopoUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashSet;
+import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 
+import static org.onosproject.incubator.net.PortStatisticsService.MetricType.BYTES;
+import static org.onosproject.incubator.net.PortStatisticsService.MetricType.PACKETS;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
 import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE;
 
 /**
@@ -36,7 +48,7 @@
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     // 4 Kilo Bytes as threshold
-    static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
+    protected static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
 
 
     /**
@@ -221,6 +233,163 @@
 
 
     // =======================================================================
+    // === Methods for computing traffic on links
+
+    /**
+     * Generates a {@link Highlights} object summarizing the traffic on the
+     * network, ready to be transmitted back to the client for display on
+     * the topology view.
+     *
+     * @param type the type of statistics to be displayed
+     * @return highlights, representing links to be labeled/colored
+     */
+    protected Highlights trafficSummary(TrafficLink.StatsType type) {
+        Highlights highlights = new Highlights();
+
+        // TODO: consider whether a map would be better...
+        Set<TrafficLink> linksWithTraffic = computeLinksWithTraffic(type);
+
+        Set<TrafficLink> aggregatedLinks = doAggregation(linksWithTraffic);
+
+        for (TrafficLink tlink : aggregatedLinks) {
+            highlights.add(tlink.highlight(type));
+        }
+        return highlights;
+    }
+
+    /**
+     * Generates a set of "traffic links" encapsulating information about the
+     * traffic on each link (that is deemed to have traffic).
+     *
+     * @param type the type of statistics to be displayed
+     * @return the set of links with traffic
+     */
+    protected Set<TrafficLink> computeLinksWithTraffic(TrafficLink.StatsType type) {
+        TrafficLinkMap linkMap = new TrafficLinkMap();
+        compileLinks(linkMap);
+        addEdgeLinks(linkMap);
+
+        Set<TrafficLink> linksWithTraffic = new HashSet<>();
+        // TODO: consider whether a map would be better...
+
+        for (TrafficLink tlink : linkMap.biLinks()) {
+            if (type == TrafficLink.StatsType.FLOW_STATS) {
+                attachFlowLoad(tlink);
+            } else if (type == TrafficLink.StatsType.PORT_STATS) {
+                attachPortLoad(tlink, BYTES);
+            } else if (type == TrafficLink.StatsType.PORT_PACKET_STATS) {
+                attachPortLoad(tlink, PACKETS);
+            }
+
+            // we only want to report on links deemed to have traffic
+            if (tlink.hasTraffic()) {
+                linksWithTraffic.add(tlink);
+            }
+        }
+        return linksWithTraffic;
+    }
+
+    /**
+     * Iterates across the set of links in the topology and generates the
+     * appropriate set of traffic links.
+     *
+     * @param linkMap link map to augment with traffic links
+     */
+    protected void compileLinks(TrafficLinkMap linkMap) {
+        services.link().getLinks().forEach(linkMap::add);
+    }
+
+    /**
+     * Iterates across the set of hosts in the topology and generates the
+     * appropriate set of traffic links for the edge links.
+     *
+     * @param linkMap link map to augment with traffic links
+     */
+    protected void addEdgeLinks(TrafficLinkMap linkMap) {
+        services.host().getHosts().forEach(host -> {
+            linkMap.add(createEdgeLink(host, true));
+            linkMap.add(createEdgeLink(host, false));
+        });
+    }
+
+    /**
+     * Processes the given traffic link to attach the "flow load" attributed
+     * to the underlying topology links.
+     *
+     * @param link the traffic link to process
+     */
+    protected void attachFlowLoad(TrafficLink link) {
+        link.addLoad(getLinkFlowLoad(link.one()));
+        link.addLoad(getLinkFlowLoad(link.two()));
+    }
+
+    /**
+     * Returns the load for the given link, as determined by the statistics
+     * service. May return null.
+     *
+     * @param link the link on which to look up the stats
+     * @return the corresponding load (or null)
+     */
+    protected Load getLinkFlowLoad(Link link) {
+        if (link != null && link.src().elementId() instanceof DeviceId) {
+            return services.flowStats().load(link);
+        }
+        return null;
+    }
+
+    /**
+     * Processes the given traffic link to attach the "port load" attributed
+     * to the underlying topology links, for the specified metric type (either
+     * bytes/sec or packets/sec).
+     *
+     * @param link       the traffic link to process
+     * @param metricType the metric type (bytes or packets)
+     */
+    protected void attachPortLoad(TrafficLink link, MetricType metricType) {
+        // For bi-directional traffic links, use
+        // the max link rate of either direction
+        // (we choose 'one' since we know that is never null)
+        Link one = link.one();
+        Load egressSrc = services.portStats().load(one.src(), metricType);
+        Load egressDst = services.portStats().load(one.dst(), metricType);
+        link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0);
+    }
+
+    /**
+     * Returns the load with the greatest rate.
+     *
+     * @param a load a
+     * @param b load b
+     * @return the larger of the two
+     */
+    protected Load maxLoad(Load a, Load b) {
+        if (a == null) {
+            return b;
+        }
+        if (b == null) {
+            return a;
+        }
+        return a.rate() > b.rate() ? a : b;
+    }
+
+
+    /**
+     * Subclasses (well, Traffic2Monitor really) can override this method and
+     * process the traffic links before generating the highlights object.
+     * In particular, links that roll up into "synthetic links" between
+     * regions should show aggregated data from the constituent links.
+     * <p>
+     * This default implementation does nothing.
+     *
+     * @param linksWithTraffic link data for all links
+     * @return transformed link data appropriate to the region display
+     */
+    protected Set<TrafficLink> doAggregation(Set<TrafficLink> linksWithTraffic) {
+        return linksWithTraffic;
+    }
+
+
+    // =======================================================================
     // === Background Task
 
     // Provides periodic update of traffic information to the client
@@ -258,5 +427,4 @@
             }
         }
     }
-
 }