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);
+ }
+}