Detangling incubator: virtual nets, tunnels, resource labels, oh my

- virtual networking moved to /apps/virtual; with CLI & REST API
- tunnels and labels moved to /apps/tunnel; with CLI & REST API; UI disabled for now
- protobuf/models moved to /core/protobuf/models
- defunct grpc/rpc registry stuff left under /graveyard
- compile dependencies on /incubator moved to respective modules for compilation
- run-time dependencies will need to be re-tested for dependent apps

- /graveyard will be removed in not-too-distant future

Change-Id: I0a0b995c635487edcf95a352f50dd162186b0b39
diff --git a/core/net/src/main/java/org/onosproject/net/statistic/impl/PortStatisticsManager.java b/core/net/src/main/java/org/onosproject/net/statistic/impl/PortStatisticsManager.java
new file mode 100644
index 0000000..da41750
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/statistic/impl/PortStatisticsManager.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018-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.net.statistic.impl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.net.statistic.PortStatisticsService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.statistic.DefaultLoad;
+import org.onosproject.net.statistic.Load;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
+import static org.onosproject.net.device.DeviceEvent.Type.PORT_STATS_UPDATED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the port statistics service.
+ */
+@Component(immediate = true, service = PortStatisticsService.class)
+public class PortStatisticsManager implements PortStatisticsService {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final long POLL_FREQUENCY = 10_000; // milliseconds
+    private static final long STALE_LIMIT = (long) (1.5 * POLL_FREQUENCY);
+    private static final int SECOND = 1_000; // milliseconds
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+
+    private Map<ConnectPoint, DataPoint> current = Maps.newConcurrentMap();
+    private Map<ConnectPoint, DataPoint> previous = Maps.newConcurrentMap();
+
+    @Activate
+    public void activate() {
+        deviceService.addListener(deviceListener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        deviceService.removeListener(deviceListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public Load load(ConnectPoint connectPoint) {
+        return load(connectPoint, MetricType.BYTES);
+    }
+
+    @Override
+    public Load load(ConnectPoint connectPoint, MetricType metricType) {
+        DataPoint c = current.get(connectPoint);
+        DataPoint p = previous.get(connectPoint);
+        long now = System.currentTimeMillis();
+
+        if (c != null && p != null && (now - c.time < STALE_LIMIT)) {
+            if (c.time > p.time + SECOND) {
+                long cve = getEgressValue(c.stats, metricType);
+                long cvi = getIngressValue(c.stats, metricType);
+                long pve = getEgressValue(p.stats, metricType);
+                long pvi = getIngressValue(p.stats, metricType);
+                //Use max of either Tx or Rx load as the total load of a port
+                Load load = null;
+                if (cve >= pve) {
+                    load = new DefaultLoad(cve, pve, (int) (c.time - p.time) / SECOND);
+                }
+                if (cvi >= pvi) {
+                    Load rcvLoad = new DefaultLoad(cvi, pvi, (int) (c.time - p.time) / SECOND);
+                    load = ((load == null) || (rcvLoad.rate() > load.rate())) ? rcvLoad : load;
+                }
+                return load;
+            }
+        }
+        return null;
+    }
+
+    private long getEgressValue(PortStatistics stats, MetricType metricType) {
+        return metricType == MetricType.BYTES ? stats.bytesSent() : stats.packetsSent();
+    }
+
+    private long getIngressValue(PortStatistics stats, MetricType metricType) {
+        return metricType == MetricType.BYTES ? stats.bytesReceived() : stats.packetsReceived();
+    }
+
+    // Monitors port stats update messages.
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            DeviceEvent.Type type = event.type();
+            DeviceId deviceId = event.subject().id();
+            if (type == PORT_STATS_UPDATED) {
+                // Update port load
+                updateDeviceData(deviceId);
+
+            } else if (type == DEVICE_REMOVED ||
+                    (type == DEVICE_AVAILABILITY_CHANGED &&
+                            !deviceService.isAvailable(deviceId))) {
+                // Clean-up all port loads
+                pruneDeviceData(deviceId);
+            }
+        }
+    }
+
+    // Updates the port stats for the specified device
+    private void updateDeviceData(DeviceId deviceId) {
+        deviceService.getPortStatistics(deviceId)
+                .forEach(stats -> updatePortData(deviceId, stats));
+    }
+
+    // Updates the port stats for the specified port
+    private void updatePortData(DeviceId deviceId, PortStatistics stats) {
+        ConnectPoint cp = new ConnectPoint(deviceId, portNumber(stats.port()));
+        DataPoint c = current.get(cp);
+
+        // Create a new data point and make it the current one
+        current.put(cp, new DataPoint(stats));
+
+        // If we have a current data point, demote it to previous
+        if (c != null) {
+            previous.put(cp, c);
+        }
+    }
+
+    // Cleans all port loads for the specified device
+    private void pruneDeviceData(DeviceId deviceId) {
+        pruneMap(current, deviceId);
+        pruneMap(previous, deviceId);
+    }
+
+    private void pruneMap(Map<ConnectPoint, DataPoint> map, DeviceId deviceId) {
+        map.keySet().stream().filter(cp -> deviceId.equals(cp.deviceId()))
+                .collect(Collectors.toSet()).forEach(map::remove);
+    }
+
+    // Auxiliary data point to track when we receive different samples.
+    private class DataPoint {
+        long time;
+        PortStatistics stats;
+
+        DataPoint(PortStatistics stats) {
+            time = System.currentTimeMillis();
+            this.stats = stats;
+        }
+    }
+
+}