Implement CLI commands to show stats per VM.

Change-Id: I50d81d0236406996d9a378600dd0f9ec7c9244e6
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java
index e8c901c..55fac6f 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstancePortListCommand.java
@@ -23,6 +23,8 @@
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.openstacknetworking.api.InstancePort;
 import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Port;
 
 import java.util.Comparator;
 import java.util.List;
@@ -37,21 +39,25 @@
         description = "Lists all OpenStack instance ports")
 public class InstancePortListCommand extends AbstractShellCommand {
 
-    private static final String FORMAT = "%-40s%-10s%-25s%-15s%-20s";
+    private static final String FORMAT = "%-40s%-40s%-10s%-25s%-10s%-15s";
 
     @Override
     protected void doExecute() {
         InstancePortService service = get(InstancePortService.class);
+        OpenstackNetworkService osNetService = get(OpenstackNetworkService.class);
         List<InstancePort> instancePorts = Lists.newArrayList(service.instancePorts());
         instancePorts.sort(Comparator.comparing(InstancePort::portId));
 
         if (outputJson()) {
             print("%s", json(this, instancePorts));
         } else {
-            print(FORMAT, "ID", "State", "Device ID", "Port Number", "Fixed IP");
+            print(FORMAT, "Port ID", "VM Device ID", "State", "Device ID", "Port Number", "Fixed IP");
             for (InstancePort port : instancePorts) {
-                print(FORMAT, port.portId(), port.state(), port.deviceId().toString(),
-                        port.portNumber().toLong(), port.ipAddress().toString());
+                Port neutronPort = osNetService.port(port.portId());
+
+                print(FORMAT, port.portId(), neutronPort.getDeviceId(), port.state(),
+                        port.deviceId().toString(), port.portNumber().toLong(),
+                        port.ipAddress().toString());
             }
         }
     }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackVmStatsCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackVmStatsCommand.java
new file mode 100644
index 0000000..f551e76
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackVmStatsCommand.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2019-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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+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.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Port;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Lists port statistic of VM.
+ */
+@Service
+@Command(scope = "onos", name = "openstack-vm-stats",
+        description = "Show port stats which are attached to the VM")
+public class OpenstackVmStatsCommand extends AbstractShellCommand {
+    @Option(name = "-d", aliases = "--delta",
+            description = "Show delta port statistics,"
+                    + "only for the last polling interval",
+            required = false, multiValued = false)
+    private boolean delta = false;
+
+    @Option(name = "-t", aliases = "--table",
+            description = "Show delta port statistics in table format "
+                    + "using human readable unit",
+            required = false, multiValued = false)
+    private boolean table = false;
+
+    @Argument(name = "vmDeviceId", description = "VM Device ID")
+    @Completion(VmDeviceIdCompleter.class)
+    private String vmDeviceId = null;
+
+
+    private static final String NO_PORTS = "There's no port associated to given VM Device ID";
+    private static final String NO_INSTANCE_PORTS =
+            "There's no instance port associated to given VM Device ID";
+    private static final String FORMAT =
+            "   port=%s, pktRx=%s, pktTx=%s, bytesRx=%s, bytesTx=%s, pktRxDrp=%s, pktTxDrp=%s, Dur=%s%s";
+
+    @Override
+    protected void doExecute() {
+
+        OpenstackNetworkService osNetService = get(OpenstackNetworkService.class);
+        InstancePortService osInstanceService = get(InstancePortService.class);
+        DeviceService deviceService = get(DeviceService.class);
+
+        Set<Port> ports = osNetService.ports()
+                .stream()
+                .filter(port -> port.getDeviceId().equals(vmDeviceId))
+                .collect(Collectors.toSet());
+
+        if (ports.isEmpty()) {
+            print(NO_PORTS);
+            return;
+        }
+
+        Set<InstancePort> instancePorts = getInstancePortFromNeutronPortList(ports, osInstanceService);
+        if (instancePorts.isEmpty()) {
+            print(NO_INSTANCE_PORTS);
+            return;
+        }
+
+        Set<PortNumber> portNumbers =
+                instancePorts.stream().map(InstancePort::portNumber).collect(Collectors.toSet());
+
+        instancePorts.stream().findAny().ifPresent(instancePort -> {
+            DeviceId deviceId = instancePort.deviceId();
+
+            if (delta) {
+                printPortStatsDelta(vmDeviceId, deviceService.getPortDeltaStatistics(deviceId), portNumbers);
+                if (table) {
+                    printPortStatsDeltaTable(
+                            vmDeviceId, deviceService.getPortDeltaStatistics(deviceId), portNumbers);
+                }
+            } else {
+                printPortStats(vmDeviceId, deviceService.getPortStatistics(deviceId), portNumbers);
+            }
+        });
+    }
+
+    private Set<InstancePort> getInstancePortFromNeutronPortList(Set<Port> ports,
+                                                                 InstancePortService service) {
+        Set<InstancePort> instancePorts = Sets.newHashSet();
+
+        ports.forEach(port -> instancePorts.add(service.instancePort(port.getId())));
+
+        return instancePorts;
+    }
+
+    private void printPortStatsDelta(String vmDeviceId,
+                                     Iterable<PortStatistics> portStats,
+                                     Set<PortNumber> portNumbers) {
+        final String formatDelta = "   port=%s, pktRx=%s, pktTx=%s, bytesRx=%s, bytesTx=%s,"
+                + " rateRx=%s, rateTx=%s, pktRxDrp=%s, pktTxDrp=%s, interval=%s";
+
+        print("VM Device ID: %s", vmDeviceId);
+
+        for (PortStatistics stat : sortByPort(portStats)) {
+            if (portNumbers.contains(stat.portNumber())) {
+                float duration = ((float) stat.durationSec()) +
+                        (((float) stat.durationNano()) / TimeUnit.SECONDS.toNanos(1));
+                float rateRx = stat.bytesReceived() * 8 / duration;
+                float rateTx = stat.bytesSent() * 8 / duration;
+                print(formatDelta, stat.portNumber(),
+                        stat.packetsReceived(),
+                        stat.packetsSent(),
+                        stat.bytesReceived(),
+                        stat.bytesSent(),
+                        String.format("%.1f", rateRx),
+                        String.format("%.1f", rateTx),
+                        stat.packetsRxDropped(),
+                        stat.packetsTxDropped(),
+                        String.format("%.3f", duration));
+            }
+        }
+    }
+
+    private void printPortStatsDeltaTable(String vmDeviceId,
+                                     Iterable<PortStatistics> portStats,
+                                     Set<PortNumber> portNumbers) {
+
+        final String formatDeltaTable = "|%5s | %7s | %7s |  %7s | %7s | %7s | %7s |  %7s | %7s |%9s |";
+        print("+---------------------------------------------------------------------------------------------------+");
+        print("| VM Device ID = %-86s |", vmDeviceId);
+        print("|---------------------------------------------------------------------------------------------------|");
+        print("|      | Receive                                | Transmit                               | Time [s] |");
+        print("| Port | Packets |  Bytes  | Rate bps |   Drop  | Packets |  Bytes  | Rate bps |   Drop  | Interval |");
+        print("|---------------------------------------------------------------------------------------------------|");
+
+        for (PortStatistics stat : sortByPort(portStats)) {
+
+            if (portNumbers.contains(stat.portNumber())) {
+                float duration = ((float) stat.durationSec()) +
+                        (((float) stat.durationNano()) / TimeUnit.SECONDS.toNanos(1));
+                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));
+            }
+        }
+        print("+---------------------------------------------------------------------------------------------------+");
+
+    }
+    private void printPortStats(String vmDeviceId,
+                                Iterable<PortStatistics> portStats,
+                                Set<PortNumber> portNumbers) {
+        print("VM Device ID: %s", vmDeviceId);
+
+        for (PortStatistics stat : sortByPort(portStats)) {
+            if (portNumbers.contains(stat.portNumber())) {
+                print(FORMAT, stat.portNumber(), stat.packetsReceived(), stat.packetsSent(), stat.bytesReceived(),
+                        stat.bytesSent(), stat.packetsRxDropped(), stat.packetsTxDropped(), stat.durationSec(),
+                        annotations(stat.annotations()));
+            }
+        }
+    }
+
+    private static List<PortStatistics> sortByPort(Iterable<PortStatistics> portStats) {
+        List<PortStatistics> portStatsList = Lists.newArrayList(portStats);
+
+        portStatsList.sort(Comparator.comparing(ps -> ps.portNumber().toLong()));
+        return portStatsList;
+    }
+
+    private static String humanReadable(long bytes) {
+        int unit = 1000;
+        if (bytes < unit) {
+            return String.format("%s ", bytes);
+        }
+        int exp = (int) (Math.log(bytes) / Math.log(unit));
+        Character pre = ("KMGTPE").charAt(exp - 1);
+        return String.format("%.2f%s", bytes / Math.pow(unit, exp), pre);
+    }
+
+    private static String humanReadableBps(float bps) {
+        int unit = 1000;
+        if (bps < unit) {
+            return String.format("%.0f ", bps);
+        }
+        int exp = (int) (Math.log(bps) / Math.log(unit));
+        Character pre = ("KMGTPE").charAt(exp - 1);
+        return String.format("%.2f%s", bps / Math.pow(unit, exp), pre);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VmDeviceIdCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VmDeviceIdCompleter.java
new file mode 100644
index 0000000..9eec5b2
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VmDeviceIdCompleter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019-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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Port;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+import static org.onosproject.cli.AbstractShellCommand.get;
+
+/**
+ * VM Device ID Completer.
+ */
+@Service
+public class VmDeviceIdCompleter implements Completer {
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        OpenstackNetworkService osNetService = get(OpenstackNetworkService.class);
+
+        Set<String> set = osNetService.ports().stream().map(Port::getDeviceId)
+                .collect(Collectors.toSet());
+
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<String> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next());
+        }
+
+        return delegate.complete(session, commandLine, candidates);
+    }
+}