Refactored Traffic Monitor code to display packets / second.
- cleaned up "rate thresholds" for coloring links.
- added unit tests for TopoUtils.
- "Monitor All Traffic" button on toolbar now cycles between 3 modes.

Change-Id: If33cfb3e6d6190e1321752b6d058274d3004f309
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java b/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
index 3ed50f8..2ab2512 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
@@ -21,6 +21,7 @@
 
 import java.text.DecimalFormat;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static org.onosproject.net.LinkKey.linkKey;
 
 /**
@@ -29,27 +30,20 @@
  */
 public final class TopoUtils {
 
-    // explicit decision made to not 'javadoc' these self explanatory constants
-    public static final double KILO = 1024;
-    public static final double MEGA = 1024 * KILO;
-    public static final double GIGA = 1024 * MEGA;
+    // explicit decision made to not 'javadoc' these constants
+    public static final double N_KILO = 1024;
+    public static final double N_MEGA = 1024 * N_KILO;
+    public static final double N_GIGA = 1024 * N_MEGA;
 
-    public static final String GBITS_UNIT = "Gb";
-    public static final String MBITS_UNIT = "Mb";
-    public static final String KBITS_UNIT = "Kb";
     public static final String BITS_UNIT = "b";
-    public static final String GBYTES_UNIT = "GB";
-    public static final String MBYTES_UNIT = "MB";
-    public static final String KBYTES_UNIT = "KB";
     public static final String BYTES_UNIT = "B";
-
+    public static final String PACKETS_UNIT = "p";
 
     private static final DecimalFormat DF2 = new DecimalFormat("#,###.##");
 
     private static final String COMPACT = "%s/%s-%s/%s";
     private static final String EMPTY = "";
     private static final String SPACE = " ";
-    private static final String PER_SEC = "ps";
     private static final String FLOW = "flow";
     private static final String FLOWS = "flows";
 
@@ -65,7 +59,7 @@
      */
     public static String compactLinkString(Link link) {
         return String.format(COMPACT, link.src().elementId(), link.src().port(),
-                             link.dst().elementId(), link.dst().port());
+                link.dst().elementId(), link.dst().port());
     }
 
     /**
@@ -83,64 +77,36 @@
     }
 
     /**
-     * Returns human readable count of bytes, to be displayed as a label.
+     * Returns a value representing a count of bytes.
      *
      * @param bytes number of bytes
-     * @return formatted byte count
+     * @return value representing bytes
      */
-    public static String formatBytes(long bytes) {
-        String unit;
-        double value;
-        if (bytes > GIGA) {
-            value = bytes / GIGA;
-            unit = GBYTES_UNIT;
-        } else if (bytes > MEGA) {
-            value = bytes / MEGA;
-            unit = MBYTES_UNIT;
-        } else if (bytes > KILO) {
-            value = bytes / KILO;
-            unit = KBYTES_UNIT;
-        } else {
-            value = bytes;
-            unit = BYTES_UNIT;
-        }
-        return DF2.format(value) + SPACE + unit;
+    public static ValueLabel formatBytes(long bytes) {
+        return new ValueLabel(bytes, BYTES_UNIT);
     }
 
     /**
-     * Returns human readable bit rate, to be displayed as a label.
+     * Returns a value representing a count of packets per second.
+     *
+     * @param packets number of packets (per second)
+     * @return value representing packets per second
+     */
+    public static ValueLabel formatPacketRate(long packets) {
+        return new ValueLabel(packets, PACKETS_UNIT).perSec();
+    }
+
+
+    /**
+     * Returns a value representing a count of bits per second,
+     * (clipped to a maximum of 10 Gbps).
+     * Note that the input is bytes per second.
      *
      * @param bytes bytes per second
-     * @return formatted bits per second
+     * @return value representing bits per second
      */
-    public static String formatBitRate(long bytes) {
-        String unit;
-        double value;
-
-        //Convert to bits
-        long bits = bytes * 8;
-        if (bits > GIGA) {
-            value = bits / GIGA;
-            unit = GBITS_UNIT;
-
-            // NOTE: temporary hack to clip rate at 10.0 Gbps
-            //  Added for the CORD Fabric demo at ONS 2015
-            // TODO: provide a more elegant solution to this issue
-            if (value > 10.0) {
-                value = 10.0;
-            }
-
-        } else if (bits > MEGA) {
-            value = bits / MEGA;
-            unit = MBITS_UNIT;
-        } else if (bits > KILO) {
-            value = bits / KILO;
-            unit = KBITS_UNIT;
-        } else {
-            value = bits;
-            unit = BITS_UNIT;
-        }
-        return DF2.format(value) + SPACE + unit + PER_SEC;
+    public static ValueLabel formatClippedBitRate(long bytes) {
+        return new ValueLabel(bytes * 8, BITS_UNIT).perSec().clipG(10.0);
     }
 
     /**
@@ -155,4 +121,150 @@
         }
         return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
     }
+
+
+    /**
+     * Enumeration of magnitudes.
+     */
+    public enum Magnitude {
+        ONE("", 1),
+        KILO("K", N_KILO),
+        MEGA("M", N_MEGA),
+        GIGA("G", N_GIGA);
+
+        private final String label;
+        private final double mult;
+
+        Magnitude(String label, double mult) {
+            this.label = label;
+            this.mult = mult;
+        }
+
+        @Override
+        public String toString() {
+            return label;
+        }
+
+        private double mult() {
+            return mult;
+        }
+    }
+
+
+    /**
+     * Encapsulates a value to be used as a label.
+     */
+    public static class ValueLabel {
+        private final long value;
+        private final String unit;
+
+        private double divDown;
+        private Magnitude mag;
+
+        private boolean perSec = false;
+        private boolean clipped = false;
+
+        /**
+         * Creates a value label with the given base value and unit. For
+         * example:
+         * <pre>
+         * ValueLabel bits = new ValueLabel(2_050, "b");
+         * ValueLabel bytesPs = new ValueLabel(3_000_000, "B").perSec();
+         * </pre>
+         * Generating labels:
+         * <pre>
+         *   bits.toString()     ...  "2.00 Kb"
+         *   bytesPs.toString()  ...  "2.86 MBps"
+         * </pre>
+         *
+         * @param value the base value
+         * @param unit  the value unit
+         */
+        public ValueLabel(long value, String unit) {
+            this.value = value;
+            this.unit = unit;
+            computeAdjusted();
+        }
+
+        private void computeAdjusted() {
+            if (value >= N_GIGA) {
+                divDown = value / N_GIGA;
+                mag = Magnitude.GIGA;
+            } else if (value >= N_MEGA) {
+                divDown = value / N_MEGA;
+                mag = Magnitude.MEGA;
+            } else if (value >= N_KILO) {
+                divDown = value / N_KILO;
+                mag = Magnitude.KILO;
+            } else {
+                divDown = value;
+                mag = Magnitude.ONE;
+            }
+        }
+
+        /**
+         * Mark this value to be expressed as a rate. That is, "ps" (per sec)
+         * will be appended in the string representation.
+         *
+         * @return self, for chaining
+         */
+        public ValueLabel perSec() {
+            perSec = true;
+            return this;
+        }
+
+        /**
+         * Clips the (adjusted) value to the given threshold expressed in
+         * Giga units. That is, if the adjusted value exceeds the threshold,
+         * it will be set to the threshold value and the clipped flag
+         * will be set. For example,
+         * <pre>
+         * ValueLabel tooMuch = new ValueLabel(12_000_000_000, "b")
+         *      .perSec().clipG(10.0);
+         *
+         * tooMuch.toString()    ...  "10.00 Gbps"
+         * tooMuch.clipped()     ...  true
+         * </pre>
+         *
+         * @param threshold the clip threshold (Giga)
+         * @return self, for chaining
+         */
+        public ValueLabel clipG(double threshold) {
+            return clip(threshold, Magnitude.GIGA);
+        }
+
+        private ValueLabel clip(double threshold, Magnitude m) {
+            checkArgument(threshold >= 1.0, "threshold must be 1.0 or more");
+            double clipAt = threshold * m.mult();
+            if (value > clipAt) {
+                divDown = threshold;
+                mag = m;
+                clipped = true;
+            }
+            return this;
+        }
+
+        /**
+         * Returns true if this value was clipped to a maximum threshold.
+         *
+         * @return true if value was clipped
+         */
+        public boolean clipped() {
+            return clipped;
+        }
+
+        /**
+         * Returns the magnitude value.
+         *
+         * @return the magnitude
+         */
+        public Magnitude magnitude() {
+            return mag;
+        }
+
+        @Override
+        public String toString() {
+            return DF2.format(divDown) + SPACE + mag + unit + (perSec ? "ps" : "");
+        }
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/TopoUtilsTest.java b/core/api/src/test/java/org/onosproject/ui/topo/TopoUtilsTest.java
new file mode 100644
index 0000000..f7b56eb
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/topo/TopoUtilsTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+package org.onosproject.ui.topo;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.ConnectPoint.deviceConnectPoint;
+
+/**
+ * Unit tests for {@link TopoUtils}.
+ */
+public class TopoUtilsTest {
+    private static final String AM_WL = "wrong label";
+    private static final String AM_WM = "wrong magnitude";
+    private static final String AM_CL = "clipped?";
+    private static final String AM_NCL = "not clipped?";
+
+    private static final ConnectPoint CP_FU = deviceConnectPoint("fu:001/3");
+    private static final ConnectPoint CP_BAH = deviceConnectPoint("bah:002/5");
+
+    private static final Link LINK_FU_BAH = DefaultLink.builder()
+            .src(CP_FU)
+            .dst(CP_BAH)
+            .type(Link.Type.DIRECT)
+            .providerId(ProviderId.NONE)
+            .build();
+
+    private static final Link LINK_BAH_FU = DefaultLink.builder()
+            .src(CP_BAH)
+            .dst(CP_FU)
+            .type(Link.Type.DIRECT)
+            .providerId(ProviderId.NONE)
+            .build();
+
+    private TopoUtils.ValueLabel vl;
+
+    @Test
+    public void linkStringFuBah() {
+        String compact = TopoUtils.compactLinkString(LINK_FU_BAH);
+        assertEquals("wrong link id", "fu:001/3-bah:002/5", compact);
+    }
+
+    @Test
+    public void linkStringBahFu() {
+        String compact = TopoUtils.compactLinkString(LINK_BAH_FU);
+        assertEquals("wrong link id", "bah:002/5-fu:001/3", compact);
+    }
+
+    @Test
+    public void canonLinkKey() {
+        LinkKey fb = TopoUtils.canonicalLinkKey(LINK_FU_BAH);
+        LinkKey bf = TopoUtils.canonicalLinkKey(LINK_BAH_FU);
+        assertEquals("not canonical", fb, bf);
+    }
+
+    @Test
+    public void formatSmallBytes() {
+        vl = TopoUtils.formatBytes(1_000L);
+        assertEquals(AM_WM, TopoUtils.Magnitude.ONE, vl.magnitude());
+        assertEquals(AM_WL, "1,000 B", vl.toString());
+    }
+
+    @Test
+    public void formatKiloBytes() {
+        vl = TopoUtils.formatBytes(2_000L);
+        assertEquals(AM_WM, TopoUtils.Magnitude.KILO, vl.magnitude());
+        assertEquals(AM_WL, "1.95 KB", vl.toString());
+    }
+
+    @Test
+    public void formatMegaBytes() {
+        vl = TopoUtils.formatBytes(3_000_000L);
+        assertEquals(AM_WM, TopoUtils.Magnitude.MEGA, vl.magnitude());
+        assertEquals(AM_WL, "2.86 MB", vl.toString());
+    }
+
+    @Test
+    public void formatGigaBytes() {
+        vl = TopoUtils.formatBytes(4_000_000_000L);
+        assertEquals(AM_WM, TopoUtils.Magnitude.GIGA, vl.magnitude());
+        assertEquals(AM_WL, "3.73 GB", vl.toString());
+    }
+
+    @Test
+    public void formatTeraBytes() {
+        vl = TopoUtils.formatBytes(5_000_000_000_000L);
+        assertEquals(AM_WM, TopoUtils.Magnitude.GIGA, vl.magnitude());
+        assertEquals(AM_WL, "4,656.61 GB", vl.toString());
+    }
+
+    @Test
+    public void formatPacketRateSmall() {
+        vl = TopoUtils.formatPacketRate(37);
+        assertEquals(AM_WL, "37 pps", vl.toString());
+    }
+
+    @Test
+    public void formatPacketRateKilo() {
+        vl = TopoUtils.formatPacketRate(1024);
+        assertEquals(AM_WL, "1 Kpps", vl.toString());
+    }
+
+    @Test
+    public void formatPacketRateKilo2() {
+        vl = TopoUtils.formatPacketRate(1034);
+        assertEquals(AM_WL, "1.01 Kpps", vl.toString());
+    }
+
+    @Test
+    public void formatPacketRateMega() {
+        vl = TopoUtils.formatPacketRate(9_000_000);
+        assertEquals(AM_WL, "8.58 Mpps", vl.toString());
+    }
+
+    // remember for the following method calls, the input is in bytes!
+    @Test
+    public void formatClippedBitsSmall() {
+        vl = TopoUtils.formatClippedBitRate(8);
+        assertEquals(AM_WL, "64 bps", vl.toString());
+        assertFalse(AM_CL, vl.clipped());
+    }
+
+    @Test
+    public void formatClippedBitsKilo() {
+        vl = TopoUtils.formatClippedBitRate(2_004);
+        assertEquals(AM_WL, "15.66 Kbps", vl.toString());
+        assertFalse(AM_CL, vl.clipped());
+    }
+
+    @Test
+    public void formatClippedBitsMega() {
+        vl = TopoUtils.formatClippedBitRate(3_123_123);
+        assertEquals(AM_WL, "23.83 Mbps", vl.toString());
+        assertFalse(AM_CL, vl.clipped());
+    }
+
+    @Test
+    public void formatClippedBitsGiga() {
+        vl = TopoUtils.formatClippedBitRate(500_000_000);
+        assertEquals(AM_WL, "3.73 Gbps", vl.toString());
+        assertFalse(AM_CL, vl.clipped());
+    }
+
+    @Test
+    public void formatClippedBitsGigaExceedThreshold() {
+        vl = TopoUtils.formatClippedBitRate(5_000_000_000L);
+        // approx. 37.25 Gbps
+        assertEquals(AM_WL, "10 Gbps", vl.toString());
+        assertTrue(AM_NCL, vl.clipped());
+    }
+
+    @Test
+    public void formatNoFlows() {
+        String f = TopoUtils.formatFlows(0);
+        assertEquals(AM_WL, "", f);
+    }
+
+    @Test
+    public void formatNegativeFlows() {
+        String f = TopoUtils.formatFlows(-3);
+        assertEquals(AM_WL, "", f);
+    }
+
+    @Test
+    public void formatOneFlow() {
+        String f = TopoUtils.formatFlows(1);
+        assertEquals(AM_WL, "1 flow", f);
+    }
+
+    @Test
+    public void formatManyFlows() {
+        String f = TopoUtils.formatFlows(42);
+        assertEquals(AM_WL, "42 flows", f);
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 85d5eec..e20216d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -53,10 +53,10 @@
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentEvent;
 import org.onosproject.net.intent.IntentListener;
-import org.onosproject.net.intent.Key;
-import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.link.LinkEvent;
 import org.onosproject.net.link.LinkListener;
 import org.onosproject.ui.JsonUtils;
@@ -111,8 +111,7 @@
     private static final String REQ_PREV_INTENT = "requestPrevRelatedIntent";
     private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic";
     private static final String SEL_INTENT = "selectIntent";
-    private static final String REQ_ALL_FLOW_TRAFFIC = "requestAllFlowTraffic";
-    private static final String REQ_ALL_PORT_TRAFFIC = "requestAllPortTraffic";
+    private static final String REQ_ALL_TRAFFIC = "requestAllTraffic";
     private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows";
     private static final String CANCEL_TRAFFIC = "cancelTraffic";
     private static final String REQ_SUMMARY = "requestSummary";
@@ -123,8 +122,6 @@
     private static final String TOPO_START = "topoStart";
     private static final String TOPO_SELECT_OVERLAY = "topoSelectOverlay";
     private static final String TOPO_STOP = "topoStop";
-
-    //Protected Intents events
     private static final String SEL_PROTECTED_INTENT = "selectProtectedIntent";
     private static final String CANCEL_PROTECTED_INTENT_HIGHLIGHT = "cancelProtectedIntentHighlight";
 
@@ -157,7 +154,12 @@
     private static final String ACTIVATE = "activate";
     private static final String DEACTIVATE = "deactivate";
     private static final String PURGE = "purge";
+    private static final String TRAFFIC_TYPE = "trafficType";
 
+    // field values
+    private static final String FLOW_STATS_BYTES = "flowStatsBytes";
+    private static final String PORT_STATS_BIT_SEC = "portStatsBitSec";
+    private static final String PORT_STATS_PKT_SEC = "portStatsPktSec";
 
     private static final String MY_APP_ID = "org.onosproject.gui";
 
@@ -234,8 +236,7 @@
                 new ResubmitIntent(),
                 new RemoveIntents(),
 
-                new ReqAllFlowTraffic(),
-                new ReqAllPortTraffic(),
+                new ReqAllTraffic(),
                 new ReqDevLinkFlows(),
                 new ReqRelatedIntents(),
                 new ReqNextIntent(),
@@ -560,25 +561,28 @@
 
     // ========= -----------------------------------------------------------------
 
-    private final class ReqAllFlowTraffic extends RequestHandler {
-        private ReqAllFlowTraffic() {
-            super(REQ_ALL_FLOW_TRAFFIC);
+    private final class ReqAllTraffic extends RequestHandler {
+        private ReqAllTraffic() {
+            super(REQ_ALL_TRAFFIC);
         }
 
         @Override
         public void process(ObjectNode payload) {
-            traffic.monitor(Mode.ALL_FLOW_TRAFFIC);
-        }
-    }
+            String trafficType = string(payload, TRAFFIC_TYPE, FLOW_STATS_BYTES);
 
-    private final class ReqAllPortTraffic extends RequestHandler {
-        private ReqAllPortTraffic() {
-            super(REQ_ALL_PORT_TRAFFIC);
-        }
-
-        @Override
-        public void process(ObjectNode payload) {
-            traffic.monitor(Mode.ALL_PORT_TRAFFIC);
+            switch (trafficType) {
+                case FLOW_STATS_BYTES:
+                    traffic.monitor(Mode.ALL_FLOW_TRAFFIC_BYTES);
+                    break;
+                case PORT_STATS_BIT_SEC:
+                    traffic.monitor(Mode.ALL_PORT_TRAFFIC_BIT_PS);
+                    break;
+                case PORT_STATS_PKT_SEC:
+                    traffic.monitor(Mode.ALL_PORT_TRAFFIC_PKT_PS);
+                    break;
+                default:
+                    break;
+            }
         }
     }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
index d324cc8..5cf5765 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -79,7 +79,7 @@
 public class TrafficMonitor extends AbstractTopoMonitor {
 
     // 4 Kilo Bytes as threshold
-    private static final double BPS_THRESHOLD = 4 * TopoUtils.KILO;
+    private static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
 
     private static final Logger log =
             LoggerFactory.getLogger(TrafficMonitor.class);
@@ -89,8 +89,9 @@
      */
     public enum Mode {
         IDLE,
-        ALL_FLOW_TRAFFIC,
-        ALL_PORT_TRAFFIC,
+        ALL_FLOW_TRAFFIC_BYTES,
+        ALL_PORT_TRAFFIC_BIT_PS,
+        ALL_PORT_TRAFFIC_PKT_PS,
         DEV_LINK_FLOWS,
         RELATED_INTENTS,
         SELECTED_INTENT
@@ -136,8 +137,9 @@
      * <p>
      * The monitoring mode is expected to be one of:
      * <ul>
-     * <li>ALL_FLOW_TRAFFIC</li>
-     * <li>ALL_PORT_TRAFFIC</li>
+     * <li>ALL_FLOW_TRAFFIC_BYTES</li>
+     * <li>ALL_PORT_TRAFFIC_BIT_PS</li>
+     * <li>ALL_PORT_TRAFFIC_PKT_PS</li>
      * <li>SELECTED_INTENT</li>
      * </ul>
      *
@@ -148,16 +150,22 @@
         this.mode = mode;
 
         switch (mode) {
-            case ALL_FLOW_TRAFFIC:
+            case ALL_FLOW_TRAFFIC_BYTES:
                 clearSelection();
                 scheduleTask();
                 sendAllFlowTraffic();
                 break;
 
-            case ALL_PORT_TRAFFIC:
+            case ALL_PORT_TRAFFIC_BIT_PS:
                 clearSelection();
                 scheduleTask();
-                sendAllPortTraffic();
+                sendAllPortTraffic(StatsType.PORT_STATS);
+                break;
+
+            case ALL_PORT_TRAFFIC_PKT_PS:
+                clearSelection();
+                scheduleTask();
+                sendAllPortTraffic(StatsType.PORT_PACKET_STATS);
                 break;
 
             case SELECTED_INTENT:
@@ -334,9 +342,9 @@
         msgHandler.sendHighlights(trafficSummary(StatsType.FLOW_STATS));
     }
 
-    private void sendAllPortTraffic() {
-        log.debug("sendAllPortTraffic");
-        msgHandler.sendHighlights(trafficSummary(StatsType.PORT_STATS));
+    private void sendAllPortTraffic(StatsType t) {
+        log.debug("sendAllPortTraffic: {}", t);
+        msgHandler.sendHighlights(trafficSummary(t));
     }
 
     private void sendDeviceLinkFlows() {
@@ -494,7 +502,6 @@
         Load egressSrc = servicesBundle.portStatsService().load(one.src(), metricType);
         Load egressDst = servicesBundle.portStatsService().load(one.dst(), metricType);
         link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0);
-//        link.addLoad(maxLoad(egressSrc, egressDst), 10);    // DEBUG ONLY!!
     }
 
     private Load maxLoad(Load a, Load b) {
@@ -669,11 +676,14 @@
         public void run() {
             try {
                 switch (mode) {
-                    case ALL_FLOW_TRAFFIC:
+                    case ALL_FLOW_TRAFFIC_BYTES:
                         sendAllFlowTraffic();
                         break;
-                    case ALL_PORT_TRAFFIC:
-                        sendAllPortTraffic();
+                    case ALL_PORT_TRAFFIC_BIT_PS:
+                        sendAllPortTraffic(StatsType.PORT_STATS);
+                        break;
+                    case ALL_PORT_TRAFFIC_PKT_PS:
+                        sendAllPortTraffic(StatsType.PORT_PACKET_STATS);
                         break;
                     case DEV_LINK_FLOWS:
                         sendDeviceLinkFlows();
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
index 570fed4..3075acd 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
@@ -23,12 +23,19 @@
 import org.onosproject.ui.topo.LinkHighlight;
 import org.onosproject.ui.topo.LinkHighlight.Flavor;
 import org.onosproject.ui.topo.Mod;
-import org.onosproject.ui.topo.TopoUtils;
+import org.onosproject.ui.topo.TopoUtils.Magnitude;
+import org.onosproject.ui.topo.TopoUtils.ValueLabel;
 
 import java.util.HashSet;
 import java.util.Set;
 
-import static org.onosproject.ui.topo.LinkHighlight.Flavor.*;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
+import static org.onosproject.ui.topo.TopoUtils.formatBytes;
+import static org.onosproject.ui.topo.TopoUtils.formatClippedBitRate;
+import static org.onosproject.ui.topo.TopoUtils.formatFlows;
+import static org.onosproject.ui.topo.TopoUtils.formatPacketRate;
 
 /**
  * Representation of a link and its inverse, and associated traffic data.
@@ -36,9 +43,12 @@
  * {@link LinkHighlight}s for showing traffic data on the topology view.
  */
 public class TrafficLink extends BiLink {
+    private static final Mod PORT_TRAFFIC_GREEN = new Mod("port-traffic-green");
+    private static final Mod PORT_TRAFFIC_YELLOW = new Mod("port-traffic-yellow");
+    private static final Mod PORT_TRAFFIC_ORANGE = new Mod("port-traffic-orange");
+    private static final Mod PORT_TRAFFIC_RED = new Mod("port-traffic-red");
 
     private static final String EMPTY = "";
-    private static final String QUE = "?";
 
     private long bytes = 0;
     private long rate = 0;
@@ -88,7 +98,7 @@
      * @return self, for chaining
      */
     public TrafficLink tagFlavor(Flavor flavor) {
-        this.taggedFlavor = flavor;
+        taggedFlavor = flavor;
         return this;
     }
 
@@ -144,14 +154,15 @@
         StatsType statsType = (StatsType) type;
         switch (statsType) {
             case FLOW_COUNT:
-                return highlightForFlowCount(statsType);
+                return highlightForFlowCount();
 
             case FLOW_STATS:
             case PORT_STATS:
+            case PORT_PACKET_STATS:
                 return highlightForStats(statsType);
 
             case TAGGED:
-                return highlightForTagging(statsType);
+                return highlightForTagging();
 
             default:
                 throw new IllegalStateException("unexpected case: " + statsType);
@@ -159,60 +170,83 @@
     }
 
     private LinkHighlight highlightForStats(StatsType type) {
-        LinkHighlight hlite = new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
-                .setLabel(generateLabel(type));
-        if (!mods.isEmpty()) {
-            mods.forEach(hlite::addMod);
+        ValueLabel vl = null;
+        Mod m = null;
+
+        // based on the type of stats, need to determine the label and "color"...
+        switch (type) {
+            case FLOW_STATS:
+                vl = formatBytes(bytes);
+                // default to "secondary highlighting" of link
+                break;
+
+            case PORT_STATS:
+                vl = formatClippedBitRate(rate);
+
+                // set color based on bits per second...
+                if (vl.magnitude() == Magnitude.ONE ||
+                        vl.magnitude() == Magnitude.KILO) {
+                    m = PORT_TRAFFIC_GREEN;
+
+                } else if (vl.magnitude() == Magnitude.MEGA) {
+                    m = PORT_TRAFFIC_YELLOW;
+
+                } else if (vl.magnitude() == Magnitude.GIGA) {
+                    m = vl.clipped() ? PORT_TRAFFIC_RED : PORT_TRAFFIC_ORANGE;
+                }
+                break;
+
+            case PORT_PACKET_STATS:
+                vl = formatPacketRate(rate);
+
+                // TODO: need to decide color threshold parameters for packets
+                //  for now, we'll just default to "green"
+                m = PORT_TRAFFIC_GREEN;
+                break;
+
+            default:
+                break;
         }
-        return hlite;
+
+        LinkHighlight hlite = new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT);
+        if (vl != null) {
+            hlite.setLabel(vl.toString());
+        }
+        if (m != null) {
+            hlite.addMod(m);
+        }
+
+        return addCustomMods(hlite);
     }
 
-    private LinkHighlight highlightForFlowCount(StatsType type) {
+    private LinkHighlight highlightForFlowCount() {
         Flavor flavor = flows > 0 ? PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
         LinkHighlight hlite = new LinkHighlight(linkId(), flavor)
-                .setLabel(generateLabel(type));
-        if (!mods.isEmpty()) {
-            mods.forEach(hlite::addMod);
-        }
-        return hlite;
+                .setLabel(formatFlows(flows));
 
+        return addCustomMods(hlite);
     }
 
-    private LinkHighlight highlightForTagging(StatsType type) {
+    private LinkHighlight highlightForTagging() {
         LinkHighlight hlite = new LinkHighlight(linkId(), taggedFlavor)
-                .setLabel(generateLabel(type));
+                .setLabel(hasTraffic ? formatBytes(bytes).toString() : EMPTY);
+
         if (isOptical) {
             hlite.addMod(LinkHighlight.MOD_OPTICAL);
         }
         if (antMarch) {
             hlite.addMod(LinkHighlight.MOD_ANIMATED);
         }
+        return addCustomMods(hlite);
+    }
+
+    private LinkHighlight addCustomMods(LinkHighlight hlite) {
         if (!mods.isEmpty()) {
             mods.forEach(hlite::addMod);
         }
         return hlite;
     }
 
-    // Generates a string representation of the load, to be used as a label
-    private String generateLabel(StatsType type) {
-        switch (type) {
-            case FLOW_COUNT:
-                return TopoUtils.formatFlows(flows);
-
-            case FLOW_STATS:
-                return TopoUtils.formatBytes(bytes);
-
-            case PORT_STATS:
-                return TopoUtils.formatBitRate(rate);
-
-            case TAGGED:
-                return hasTraffic ? TopoUtils.formatBytes(bytes) : EMPTY;
-
-            default:
-                return QUE;
-        }
-    }
-
     /**
      * Returns true if this link has been deemed to have enough traffic
      * to register on the topology view in the web UI.
diff --git a/web/gui/src/main/webapp/app/view/topo/topo-theme.css b/web/gui/src/main/webapp/app/view/topo/topo-theme.css
index 768c506..8f48766 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo-theme.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo-theme.css
@@ -267,24 +267,37 @@
     stroke: rgba(0,153,51,0.5);
 }
 
-/* Port traffic color visualization for Kbps, Mbps, and Gbps */
+/* Port traffic color visualization:
 
-#ov-topo svg .link.secondary.port-traffic-Kbps {
+    For bits per second we will use:
+        - green for Kbps,
+        - yellow for Mbps,
+        - orange for Gbps, and
+        - red for > 10 Gbps
+
+    For packets per second we will use:
+        - green for > 0
+        - yellow for > ?
+        - orange for > ??
+        - red for > ???
+*/
+
+#ov-topo svg .link.secondary.port-traffic-green {
     stroke: rgb(0,153,51);
     stroke-width: 5.0;
 }
 
-#ov-topo svg .link.secondary.port-traffic-Mbps {
+#ov-topo svg .link.secondary.port-traffic-yellow {
     stroke: rgb(128,145,27);
     stroke-width: 6.5;
 }
 
-#ov-topo svg .link.secondary.port-traffic-Gbps {
+#ov-topo svg .link.secondary.port-traffic-orange {
     stroke: rgb(255, 137, 3);
     stroke-width: 8.0;
 }
 
-#ov-topo svg .link.secondary.port-traffic-Gbps-choked {
+#ov-topo svg .link.secondary.port-traffic-red {
     stroke: rgb(183, 30, 21);
     stroke-width: 8.0;
 }
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 03d8477..0808271 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -898,8 +898,8 @@
 
     // TODO: find an automatic way of tracking via the "showHighlights" events
     var allTrafficClasses = 'primary secondary optical animated ' +
-        'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' +
-        'port-traffic-Gbps-choked';
+        'port-traffic-green port-traffic-yellow port-traffic-orange ' +
+        'port-traffic-red';
 
     function clearLinkTrafficStyle() {
         link.style('stroke-width', null)
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index 2db9275..11a4dba 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -32,9 +32,22 @@
          selectOrder()
      */
 
+    var allTrafficTypes = [
+            'flowStatsBytes',
+            'portStatsBitSec',
+            'portStatsPktSec'
+        ],
+        allTrafficMsgs = [
+            'Flow Stats (bytes)',
+            'Port Stats (bits / second)',
+            'Port Stats (packets / second)'
+        ];
+
     // internal state
     var trafficMode = null,
-        hoverMode = null;
+        hoverMode = null,
+        allTrafficIndex = 0;
+
 
 
     // === -----------------------------------------------------
@@ -104,18 +117,14 @@
         return true;
     }
 
-    function showAllFlowTraffic() {
+    function showAllTraffic() {
         trafficMode = 'allFlowPort';
         hoverMode = null;
-        wss.sendEvent('requestAllFlowTraffic');
-        flash.flash('All Flow Traffic');
-    }
-
-    function showAllPortTraffic() {
-        trafficMode = 'allFlowPort';
-        hoverMode = null;
-        wss.sendEvent('requestAllPortTraffic');
-        flash.flash('All Port Traffic');
+        wss.sendEvent('requestAllTraffic', {
+            trafficType: allTrafficTypes[allTrafficIndex]
+        });
+        flash.flash(allTrafficMsgs[allTrafficIndex]);
+        allTrafficIndex = (allTrafficIndex + 1) % 3;
     }
 
     function showDeviceLinkFlows () {
@@ -245,8 +254,7 @@
 
                 // invoked from toolbar overlay buttons or keystrokes
                 cancelTraffic: cancelTraffic,
-                showAllFlowTraffic: showAllFlowTraffic,
-                showAllPortTraffic: showAllPortTraffic,
+                showAllTraffic: showAllTraffic,
                 showDeviceLinkFlows: showDeviceLinkFlows,
                 showRelatedIntents: showRelatedIntents,
                 showPrevIntent: showPrevIntent,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index ac8f4a2..7877e93 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -75,13 +75,8 @@
             },
 
             A: {
-                cb: function () { tts.showAllFlowTraffic(); },
-                tt: 'Monitor all traffic using flow stats',
-                gid: 'm_allTraffic'
-            },
-            Q: {
-                cb: function () { tts.showAllPortTraffic(); },
-                tt: 'Monitor all traffic using port stats',
+                cb: function () { tts.showAllTraffic(); },
+                tt: 'Monitor all traffic',
                 gid: 'm_allTraffic'
             },
             F: {
@@ -111,7 +106,7 @@
             },
 
             _keyOrder: [
-                '0', 'A', 'Q', 'F', 'V', 'leftArrow', 'rightArrow', 'W'
+                '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W'
             ]
         },
 
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
index 0a04f34..2f51848 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
@@ -38,8 +38,7 @@
             'initTraffic',
             'destroyTraffic',
             'cancelTraffic',
-            'showAllFlowTraffic',
-            'showAllPortTraffic',
+            'showAllTraffic',
             'showDeviceLinkFlows',
             'showRelatedIntents',
             'showPrevIntent',