Fix portstats cli for json output.

Jira Id: ONOS-8134

Change-Id: If0f33d1eee230f6ceebf57acebfc81aef599e03c
diff --git a/cli/src/main/java/org/onosproject/cli/net/DevicePortStatsCommand.java b/cli/src/main/java/org/onosproject/cli/net/DevicePortStatsCommand.java
index 8105bff..624be19 100644
--- a/cli/src/main/java/org/onosproject/cli/net/DevicePortStatsCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/DevicePortStatsCommand.java
@@ -15,19 +15,17 @@
  */
 package org.onosproject.cli.net;
 
-import static org.onosproject.cli.net.DevicesListCommand.getSortedDevices;
-import static org.onosproject.net.DeviceId.deviceId;
-
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 
 import com.google.common.collect.Lists;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.Completion;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
@@ -35,6 +33,13 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.device.PortStatistics;
 
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.onosproject.cli.net.DevicesListCommand.getSortedDevices;
+import static org.onosproject.net.DeviceId.deviceId;
+
 /**
  * Lists port statistic of all ports in the system.
  */
@@ -49,7 +54,7 @@
 
     @Option(name = "-d", aliases = "--delta",
             description = "Show delta port statistics,"
-            + "only for the last polling interval",
+                    + "only for the last polling interval",
             required = false, multiValued = false)
     private boolean delta = false;
 
@@ -83,21 +88,36 @@
         }
 
         if (uri == null) {
-            for (Device d : getSortedDevices(deviceService)) {
+            if (outputJson()) {
                 if (delta) {
-                    if (table) {
-                        printPortStatsDeltaTable(d.id(), deviceService.getPortDeltaStatistics(d.id()));
-                    } else {
-                        printPortStatsDelta(d.id(), deviceService.getPortDeltaStatistics(d.id()));
-                    }
+                    print("%s", jsonPortStatsDelta(deviceService, getSortedDevices(deviceService)));
                 } else {
-                    printPortStats(d.id(), deviceService.getPortStatistics(d.id()));
+                    print("%s", jsonPortStats(deviceService, getSortedDevices(deviceService)));
+                }
+            } else {
+                for (Device d : getSortedDevices(deviceService)) {
+                    if (delta) {
+                        if (table) {
+                            printPortStatsDeltaTable(d.id(), deviceService.getPortDeltaStatistics(d.id()));
+                        } else {
+                            printPortStatsDelta(d.id(), deviceService.getPortDeltaStatistics(d.id()));
+                        }
+                    } else {
+                        printPortStats(d.id(), deviceService.getPortStatistics(d.id()));
+                    }
                 }
             }
         } else {
             Device d = deviceService.getDevice(deviceId(uri));
             if (d == null) {
                 error("No such device %s", uri);
+            } else if (outputJson()) {
+                if (delta) {
+                    print("%s", jsonPortStatsDelta(d.id(), new ObjectMapper(),
+                            deviceService.getPortDeltaStatistics(d.id())));
+                } else {
+                    print("%s", jsonPortStats(d.id(), new ObjectMapper(), deviceService.getPortStatistics(d.id())));
+                }
             } else if (delta) {
                 if (table) {
                     printPortStatsDeltaTable(d.id(), deviceService.getPortDeltaStatistics(d.id()));
@@ -111,6 +131,123 @@
     }
 
     /**
+     * Produces JSON array containing portstats of the specified devices.
+     *
+     * @param deviceService device service
+     * @param devices       collection of devices
+     * @return JSON Array
+     */
+    protected JsonNode jsonPortStats(DeviceService deviceService, Iterable<Device> devices) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (Device device : devices) {
+            result.add(jsonPortStats(device.id(), mapper, deviceService.getPortStatistics(device.id())));
+        }
+
+        return result;
+    }
+
+    /**
+     * Produces JSON array containing portstats of the specified device.
+     *
+     * @param deviceId  device id
+     * @param portStats collection of port statistics
+     * @return JSON array
+     */
+    private JsonNode jsonPortStats(DeviceId deviceId, ObjectMapper mapper, Iterable<PortStatistics> portStats) {
+        ObjectNode result = mapper.createObjectNode();
+        ArrayNode portStatsNode = mapper.createArrayNode();
+
+        for (PortStatistics stat : sortByPort(portStats)) {
+            if (isIrrelevant(stat)) {
+                continue;
+            }
+            if (nonzero && stat.isZero()) {
+                continue;
+            }
+
+            portStatsNode.add(mapper.createObjectNode()
+                    .put("port", stat.portNumber().toString())
+                    .put("pktRx", stat.packetsReceived())
+                    .put("pktTx", stat.packetsSent())
+                    .put("bytesRx", stat.bytesReceived())
+                    .put("bytesTx", stat.bytesSent())
+                    .put("pktRxDrp", stat.packetsRxDropped())
+                    .put("pktTxDrp", stat.packetsTxDropped())
+                    .put("Dur", stat.durationSec())
+                    .set("annotations", annotations(mapper, stat.annotations())));
+        }
+
+        result.put("deviceId", deviceId.toString());
+        result.set("portStats", portStatsNode);
+
+        return result;
+    }
+
+    /**
+     * Produces JSON array containing delta portstats of the specified devices.
+     *
+     * @param deviceService device service
+     * @param devices       collection of devices
+     * @return JSON Array
+     */
+    protected JsonNode jsonPortStatsDelta(DeviceService deviceService, Iterable<Device> devices) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (Device device : devices) {
+            result.add(jsonPortStatsDelta(device.id(), mapper, deviceService.getPortDeltaStatistics(device.id())));
+        }
+
+        return result;
+    }
+
+    /**
+     * Produces JSON array containing delta portstats of the specified device id.
+     *
+     * @param deviceId  device id
+     * @param portStats collection of port statistics
+     * @return JSON array
+     */
+    private JsonNode jsonPortStatsDelta(DeviceId deviceId, ObjectMapper mapper, Iterable<PortStatistics> portStats) {
+        ObjectNode result = mapper.createObjectNode();
+        ArrayNode portStatsNode = mapper.createArrayNode();
+
+        for (PortStatistics stat : sortByPort(portStats)) {
+            if (isIrrelevant(stat)) {
+                continue;
+            }
+            if (nonzero && stat.isZero()) {
+                continue;
+            }
+
+            float duration = ((float) stat.durationSec()) +
+                    (((float) stat.durationNano()) / TimeUnit.SECONDS.toNanos(1));
+            float rateRx = stat.bytesReceived() * 8 / duration;
+            float rateTx = stat.bytesSent() * 8 / duration;
+
+            portStatsNode.add(mapper.createObjectNode()
+                    .put("port", stat.portNumber().toString())
+                    .put("pktRx", stat.packetsReceived())
+                    .put("pktTx", stat.packetsSent())
+                    .put("bytesRx", stat.bytesReceived())
+                    .put("bytesTx", stat.bytesSent())
+                    .put("rateRx", String.format("%.1f", rateRx))
+                    .put("rateTx", String.format("%.1f", rateTx))
+                    .put("pktRxDrp", stat.packetsRxDropped())
+                    .put("pktTxDrp", stat.packetsTxDropped())
+                    .put("interval", String.format("%.3f", duration)));
+        }
+
+        result.put("deviceId", deviceId.toString());
+        result.set("portStats", portStatsNode);
+
+        return result;
+    }
+
+
+    /**
      * Prints Port Statistics.
      *
      * @param deviceId
@@ -197,15 +334,15 @@
             float rateRx = duration > 0 ? stat.bytesReceived() * 8 / duration : 0;
             float rateTx = duration > 0 ? stat.bytesSent() * 8 / duration : 0;
             print(formatDeltaTable, stat.portNumber(),
-                  humanReadable(stat.packetsReceived()),
-                  humanReadable(stat.bytesReceived()),
-                  humanReadableBps(rateRx),
-                  humanReadable(stat.packetsRxDropped()),
-                  humanReadable(stat.packetsSent()),
-                  humanReadable(stat.bytesSent()),
-                  humanReadableBps(rateTx),
-                  humanReadable(stat.packetsTxDropped()),
-                  String.format("%.3f", duration));
+                    humanReadable(stat.packetsReceived()),
+                    humanReadable(stat.bytesReceived()),
+                    humanReadableBps(rateRx),
+                    humanReadable(stat.packetsRxDropped()),
+                    humanReadable(stat.packetsSent()),
+                    humanReadable(stat.bytesSent()),
+                    humanReadableBps(rateTx),
+                    humanReadable(stat.packetsTxDropped()),
+                    String.format("%.3f", duration));
         }
         print("+---------------------------------------------------------------------------------------------------+");
     }
diff --git a/cli/src/test/java/org/onosproject/cli/net/DevicePortStatsCommandTest.java b/cli/src/test/java/org/onosproject/cli/net/DevicePortStatsCommandTest.java
new file mode 100644
index 0000000..7717692
--- /dev/null
+++ b/cli/src/test/java/org/onosproject/cli/net/DevicePortStatsCommandTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.cli.net;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.easymock.EasyMock.*;
+
+/**
+ * Unit test for DevicePortStatsCommand.
+ */
+public class DevicePortStatsCommandTest {
+
+    private DevicePortStatsCommand devicePortStatsCommand;
+    private PortStatistics portStatistics;
+    private DeviceService deviceService;
+    private List<Device> devices = new ArrayList<>();
+    private List<PortStatistics> portStatisticsList = new ArrayList<>();
+    private DeviceId id1;
+
+
+    @Before
+    public void setUp() {
+        devicePortStatsCommand = new DevicePortStatsCommand();
+
+        id1 = NetTestTools.did("d1");
+
+        DefaultDevice d1 = new DefaultDevice(NetTestTools.PID, id1, Device.Type.SWITCH,
+                                             "test", "1.0", "1.0",
+                                             "abacab", new ChassisId("c"),
+                                             DefaultAnnotations.EMPTY);
+
+        devices.add(d1);
+
+        portStatistics = DefaultPortStatistics.builder()
+                .setDurationSec(1)
+                .setBytesReceived(10)
+                .setBytesSent(20)
+                .setDurationNano(30)
+                .setPacketsReceived(40)
+                .setPacketsSent(50)
+                .setPacketsRxDropped(60)
+                .setPacketsRxErrors(70)
+                .setPacketsTxDropped(80)
+                .setPacketsTxErrors(90)
+                .setPort(PortNumber.portNumber(81))
+                .setDeviceId(id1)
+                .build();
+
+        portStatisticsList.add(portStatistics);
+
+        deviceService = createMock(DeviceService.class);
+        expect(deviceService.getPortStatistics(id1))
+                .andReturn(portStatisticsList);
+        expect(deviceService.getPortDeltaStatistics(id1))
+                .andReturn(portStatisticsList);
+
+        replay(deviceService);
+    }
+
+
+    /**
+     * Tests json port stats output.
+     */
+    @Test
+    public void testJsonPortStats() {
+
+        JsonNode node = devicePortStatsCommand.jsonPortStats(deviceService, devices).get(0);
+
+        assertEquals(node.findValue("deviceId").asText(), id1.toString());
+        assertEquals(node.findValue("port").asText(), portStatistics.portNumber().toString());
+        assertEquals(node.findValue("pktRx").asLong(), portStatistics.packetsReceived());
+        assertEquals(node.findValue("pktTx").asLong(), portStatistics.packetsSent());
+        assertEquals(node.findValue("bytesRx").asLong(), portStatistics.bytesReceived());
+        assertEquals(node.findValue("bytesTx").asLong(), portStatistics.bytesSent());
+        assertEquals(node.findValue("pktRxDrp").asLong(), portStatistics.packetsRxDropped());
+        assertEquals(node.findValue("pktTxDrp").asLong(), portStatistics.packetsTxDropped());
+        assertEquals(node.findValue("Dur").asLong(), portStatistics.durationSec());
+
+    }
+
+    /**
+     * Tests json port stats delta output.
+     */
+    @Test
+    public void testJsonPortStatsDelta() {
+
+        JsonNode node = devicePortStatsCommand.jsonPortStatsDelta(deviceService, devices).get(0);
+
+        float duration = ((float) portStatistics.durationSec()) +
+                (((float) portStatistics.durationNano()) / TimeUnit.SECONDS.toNanos(1));
+        float rateRx = portStatistics.bytesReceived() * 8 / duration;
+        float rateTx = portStatistics.bytesSent() * 8 / duration;
+
+        assertEquals(node.findValue("deviceId").asText(), id1.toString());
+        assertEquals(node.findValue("port").asText(), portStatistics.portNumber().toString());
+        assertEquals(node.findValue("pktRx").asLong(), portStatistics.packetsReceived());
+        assertEquals(node.findValue("pktTx").asLong(), portStatistics.packetsSent());
+        assertEquals(node.findValue("bytesRx").asLong(), portStatistics.bytesReceived());
+        assertEquals(node.findValue("bytesTx").asLong(), portStatistics.bytesSent());
+        assertEquals(node.findValue("rateRx").asText(), String.format("%.1f", rateRx));
+        assertEquals(node.findValue("rateTx").asText(), String.format("%.1f", rateTx));
+        assertEquals(node.findValue("pktRxDrp").asLong(), portStatistics.packetsRxDropped());
+        assertEquals(node.findValue("pktTxDrp").asLong(), portStatistics.packetsTxDropped());
+        assertEquals(node.findValue("interval").asText(), String.format("%.3f", duration));
+
+    }
+}
diff --git a/cli/src/test/java/org/onosproject/cli/net/package-info.java b/cli/src/test/java/org/onosproject/cli/net/package-info.java
new file mode 100644
index 0000000..a46b087
--- /dev/null
+++ b/cli/src/test/java/org/onosproject/cli/net/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Test Administrative console command-line extensions for interacting with the
+ * network model &amp; services.
+ */
+package org.onosproject.cli.net;
\ No newline at end of file
diff --git a/cli/src/test/java/org/onosproject/cli/package-info.java b/cli/src/test/java/org/onosproject/cli/package-info.java
new file mode 100644
index 0000000..212c946
--- /dev/null
+++ b/cli/src/test/java/org/onosproject/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Administrative console command-line extensions test.
+ */
+package org.onosproject.cli;
\ No newline at end of file