[ONOS-3851] Initial implementation of Web GUI of CPMan
- Revise chart model to provide default label
- Visualize control message stats per device
Change-Id: I88b8e63ce92114907bba185b1906569fa8cc0b83
diff --git a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlLoadSnapshot.java b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlLoadSnapshot.java
index 36e436a..40c1d04 100644
--- a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlLoadSnapshot.java
+++ b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlLoadSnapshot.java
@@ -17,6 +17,7 @@
import com.google.common.base.MoreObjects;
+import java.util.Arrays;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
@@ -29,13 +30,14 @@
private final long latest;
private final long average;
private final long time;
+ private long[] recent;
/**
* Instantiates a new control metric response with given latest, average, time.
*
- * @param latest latest value of control metric
+ * @param latest latest value of control metric
* @param average average value of control metric
- * @param time last logging time fo control metric
+ * @param time last logging time of control metric
*/
public ControlLoadSnapshot(long latest, long average, long time) {
this.latest = latest;
@@ -44,6 +46,22 @@
}
/**
+ * Instantiates a new control metric response with given latest, average, time,
+ * recent values.
+ *
+ * @param latest latest value of control metric
+ * @param average average value of control metric
+ * @param time last logging time of control metric
+ * @param recent a set of historical data
+ */
+ public ControlLoadSnapshot(long latest, long average, long time, long[] recent) {
+ this.latest = latest;
+ this.average = average;
+ this.time = time;
+ this.recent = recent;
+ }
+
+ /**
* Returns latest value of control metric.
*
* @return latest value of control metric
@@ -70,9 +88,18 @@
return average;
}
+ /**
+ * Returns a set of historical recent of control metric.
+ *
+ * @return a set of historical recent of control metric
+ */
+ public long[] recent() {
+ return recent;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(latest, average, time);
+ return Objects.hash(latest, average, time, recent);
}
@Override
@@ -84,7 +111,8 @@
final ControlLoadSnapshot other = (ControlLoadSnapshot) obj;
return Objects.equals(this.latest, other.latest) &&
Objects.equals(this.average, other.average) &&
- Objects.equals(this.time, other.time);
+ Objects.equals(this.time, other.time) &&
+ Arrays.equals(this.recent, other.recent);
}
return false;
}
@@ -95,7 +123,9 @@
helper = toStringHelper(this)
.add("latest", latest)
.add("average", average)
- .add("time", time);
+ .add("time", time)
+ .add("recent", recent);
+
return helper.toString();
}
}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/gui/CpmanViewMessageHandler.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/gui/CpmanViewMessageHandler.java
index 7d0785f3..2d4e28f 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/gui/CpmanViewMessageHandler.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/gui/CpmanViewMessageHandler.java
@@ -16,44 +16,122 @@
package org.onosproject.cpman.gui;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.LocalDateTime;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cpman.ControlLoadSnapshot;
+import org.onosproject.cpman.ControlMetricType;
+import org.onosproject.cpman.ControlPlaneMonitorService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.chart.ChartModel;
+import org.onosproject.ui.chart.ChartRequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.Collection;
-import java.util.Random;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS;
+import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE;
/**
* CpmanViewMessageHandler class implementation.
*/
public class CpmanViewMessageHandler extends UiMessageHandler {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
private static final String CPMAN_DATA_REQ = "cpmanDataRequest";
private static final String CPMAN_DATA_RESP = "cpmanDataResponse";
+ private static final String CPMANS = "cpmans";
- private static final String RANDOM = "random";
+ // TODO: we assume that server side always returns 60 data points
+ // to feed 1 hour time slots, later this should make to be configurable
+ private static final int NUM_OF_DATA_POINTS = 60;
+
+ private static final int MILLI_CONV_UNIT = 1000;
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
- new CpmanDataRequestHandler()
+ new ControlMessageRequest()
);
}
- // handler for sample data requests
- private final class CpmanDataRequestHandler extends RequestHandler {
+ private final class ControlMessageRequest extends ChartRequestHandler {
- private CpmanDataRequestHandler() {
- super(CPMAN_DATA_REQ);
+ private ControlMessageRequest() {
+ super(CPMAN_DATA_REQ, CPMAN_DATA_RESP, CPMANS);
}
@Override
- public void process(long sid, ObjectNode payload) {
- ObjectNode result = objectNode();
- Random random = new Random();
- result.put(RANDOM, random.nextInt(50) + 1);
+ protected String[] getSeries() {
+ return CONTROL_MESSAGE_METRICS.stream().map(type ->
+ StringUtils.lowerCase(type.name())).toArray(String[]::new);
+ }
- sendMessage(CPMAN_DATA_RESP, 0, result);
+ @Override
+ protected void populateChart(ChartModel cm, ObjectNode payload) {
+ String uri = string(payload, "devId");
+ if (!Strings.isNullOrEmpty(uri)) {
+ Map<ControlMetricType, Long[]> data = Maps.newHashMap();
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ ClusterService cs = get(ClusterService.class);
+ ControlPlaneMonitorService cpms = get(ControlPlaneMonitorService.class);
+
+ if (cpms.availableResources(CONTROL_MESSAGE).contains(deviceId.toString())) {
+ LocalDateTime ldt = null;
+
+ try {
+ for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
+ ControlLoadSnapshot cls = cpms.getLoad(cs.getLocalNode().id(),
+ cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES,
+ Optional.of(deviceId)).get();
+ data.put(cmt, ArrayUtils.toObject(cls.recent()));
+ if (ldt == null) {
+ ldt = new LocalDateTime(cls.time() * MILLI_CONV_UNIT);
+ }
+ }
+
+ for (int i = 0; i < NUM_OF_DATA_POINTS; i++) {
+ Map<String, Long> local = Maps.newHashMap();
+ for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
+ local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt)[i]);
+ }
+
+ local.put(LABEL, ldt.minusMinutes(NUM_OF_DATA_POINTS - i).toDateTime().getMillis());
+
+ populateMetric(cm.addDataPoint(ldt.minusMinutes(NUM_OF_DATA_POINTS - i)
+ .toDateTime().getMillis()), local);
+ }
+
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn(e.getMessage());
+ }
+ }
+ } else {
+ DeviceService ds = get(DeviceService.class);
+ ds.getAvailableDevices();
+ }
+ }
+
+ private void populateAllDevs(ChartModel.DataPoint dataPoint, Map<String, Long> data) {
+
+ }
+
+ private void populateMetric(ChartModel.DataPoint dataPoint,
+ Map<String, Long> data) {
+ data.forEach((k, v) -> dataPoint.data(k, v.doubleValue()));
}
}
}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
index f3a29ee..5f0918b 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
@@ -81,10 +81,10 @@
// TODO: this can be changed to switch-case if we have more than
// one event type
if (event.type().equals(STATS_UPDATE)) {
- controlMessages.forEach(c -> {
+ controlMessages.forEach(c ->
monitorService.updateMetric(getControlMetric(c), 1,
- Optional.of(c.deviceId()));
- });
+ Optional.of(c.deviceId()))
+ );
}
}
}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
index d32d2e0..dff72db 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
@@ -169,7 +169,7 @@
if (ctrlMsgBuf.get(deviceId.get()).keySet()
.containsAll(CONTROL_MESSAGE_METRICS)) {
updateControlMessages(ctrlMsgBuf.get(deviceId.get()), deviceId.get());
- ctrlMsgBuf.get(deviceId.get());
+ ctrlMsgBuf.clear();
}
}
} else {
@@ -327,8 +327,10 @@
*/
private void updateNetworkMetrics(Map<ControlMetricType, Double> metricMap,
String resourceName) {
- networkMetricsMap.putIfAbsent(resourceName, genMDbBuilder(resourceName,
- Type.NETWORK, NETWORK_METRICS));
+ if (!networkMetricsMap.containsKey(resourceName)) {
+ networkMetricsMap.put(resourceName, genMDbBuilder(resourceName,
+ Type.NETWORK, NETWORK_METRICS));
+ }
networkMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap));
}
@@ -340,8 +342,10 @@
*/
private void updateDiskMetrics(Map<ControlMetricType, Double> metricMap,
String resourceName) {
- diskMetricsMap.putIfAbsent(resourceName, genMDbBuilder(resourceName,
- Type.DISK, DISK_METRICS));
+ if (!diskMetricsMap.containsKey(resourceName)) {
+ diskMetricsMap.put(resourceName, genMDbBuilder(resourceName,
+ Type.DISK, DISK_METRICS));
+ }
diskMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap));
}
@@ -353,8 +357,10 @@
*/
private void updateControlMessages(Map<ControlMetricType, Double> metricMap,
DeviceId deviceId) {
- controlMessageMap.putIfAbsent(deviceId, genMDbBuilder(deviceId.toString(),
- Type.CONTROL_MESSAGE, CONTROL_MESSAGE_METRICS));
+ if (!controlMessageMap.containsKey(deviceId)) {
+ controlMessageMap.put(deviceId, genMDbBuilder(deviceId.toString(),
+ Type.CONTROL_MESSAGE, CONTROL_MESSAGE_METRICS));
+ }
controlMessageMap.get(deviceId).updateMetrics(convertMap(metricMap));
}
@@ -478,7 +484,9 @@
*/
private ControlLoadSnapshot snapshot(ControlLoad cl, int duration, TimeUnit unit) {
if (cl != null) {
- return new ControlLoadSnapshot(cl.latest(), cl.average(duration, unit), cl.time());
+
+ return new ControlLoadSnapshot(cl.latest(), cl.average(duration, unit),
+ cl.time(), cl.recent(duration, unit));
}
return null;
}
diff --git a/apps/cpman/app/src/main/resources/app/view/cpman/cpman.html b/apps/cpman/app/src/main/resources/app/view/cpman/cpman.html
index d7cd832..a07e544 100644
--- a/apps/cpman/app/src/main/resources/app/view/cpman/cpman.html
+++ b/apps/cpman/app/src/main/resources/app/view/cpman/cpman.html
@@ -1,17 +1,8 @@
<!-- partial HTML -->
<div id="ov-cpman">
- <div class="button-panel">
- <div class="my-button" ng-click="getData()">
- Fetch Data
- </div>
- </div>
-
- <div class="data-panel">
- <table>
- <tr>
- <td> Number </td>
- <td class="number"> {{data.random}} </td>
- </tr>
- </table>
+ <div>
+ <canvas id="line" class="chart chart-line" chart-data="data"
+ chart-labels="labels" chart-legend="true" chart-series="series">
+ </canvas>
</div>
</div>
diff --git a/apps/cpman/app/src/main/resources/app/view/cpman/cpman.js b/apps/cpman/app/src/main/resources/app/view/cpman/cpman.js
index 65b78b3..8474629 100644
--- a/apps/cpman/app/src/main/resources/app/view/cpman/cpman.js
+++ b/apps/cpman/app/src/main/resources/app/view/cpman/cpman.js
@@ -20,67 +20,61 @@
(function () {
'use strict';
- // injected refs
- var $log, $scope, wss, ks;
+ // injected references
+ var $log, $scope, $location, ks, fs, cbs;
- // constants
- var dataReq = 'cpmanDataRequest',
- dataResp = 'cpmanDataResponse';
+ var labels = new Array(60);
+ var data = new Array(new Array(60), new Array(60), new Array(60),
+ new Array(60), new Array(60), new Array(60));
- function addKeyBindings() {
- var map = {
- space: [getData, 'Fetch data from server'],
-
- _helpFormat: [
- ['space']
- ]
- };
-
- ks.keyBindings(map);
- }
-
- function getData() {
- wss.sendEvent(dataReq);
- }
-
- function respDataCb(data) {
- $scope.data = data;
- $scope.$apply();
- }
-
-
- angular.module('ovCpman', [])
+ angular.module('ovCpman', ["chart.js"])
.controller('OvCpmanCtrl',
- ['$log', '$scope', 'WebSocketService', 'KeyService',
+ ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService',
- function (_$log_, _$scope_, _wss_, _ks_) {
+ function (_$log_, _$scope_, _$location_, _fs_, _cbs_) {
+ var params;
$log = _$log_;
$scope = _$scope_;
- wss = _wss_;
- ks = _ks_;
+ $location = _$location_;
+ fs = _fs_;
+ cbs = _cbs_;
- var handlers = {};
- $scope.data = {};
+ params = $location.search();
+ if (params.hasOwnProperty('devId')) {
+ $scope.devId = params['devId'];
+ }
- // data response handler
- handlers[dataResp] = respDataCb;
- wss.bindHandlers(handlers);
-
- addKeyBindings();
-
- // custom click handler
- $scope.getData = getData;
-
- // get data the first time...
- getData();
-
- // cleanup
- $scope.$on('$destroy', function () {
- wss.unbindHandlers(handlers);
- ks.unbindKeys();
- $log.log('OvCpmanCtrl has been destroyed');
+ cbs.buildChart({
+ scope: $scope,
+ tag: 'cpman',
+ query: params
});
+ var idx = 0;
+ var date;
+ $scope.$watch('chartData', function () {
+ idx = 0;
+ if (!fs.isEmptyObject($scope.chartData)) {
+ $scope.chartData.forEach(function (cm) {
+ data[0][idx] = cm.inbound_packet;
+ data[1][idx] = cm.outbound_packet;
+ data[2][idx] = cm.flow_mod_packet;
+ data[3][idx] = cm.flow_removed_packet;
+ data[4][idx] = cm.request_packet;
+ data[5][idx] = cm.reply_packet;
+ date = new Date(cm.label);
+ labels[idx] = date.getHours() + ":" + date.getMinutes();
+ idx++;
+ });
+ }
+ });
+
+ $scope.series = ['INBOUND', 'OUTBOUND', 'FLOW-MOD',
+ 'FLOW-REMOVED', 'STATS-REQUEST', 'STATS-REPLY'];
+ $scope.labels = labels;
+
+ $scope.data = data;
+
$log.log('OvCpmanCtrl has been created');
}]);
diff --git a/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/MetricsDatabaseTest.java b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/MetricsDatabaseTest.java
index 110ba10..e08e2e2 100644
--- a/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/MetricsDatabaseTest.java
+++ b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/MetricsDatabaseTest.java
@@ -149,11 +149,13 @@
devMetricsMap = Maps.newHashMap();
Set<DeviceId> devices = ImmutableSet.of(devId1, devId2);
- devices.forEach(dev ->
- devMetricsMap.putIfAbsent(dev,
- genMDbBuilder(type, ControlResource.CONTROL_MESSAGE_METRICS)
- .withResourceName(dev.toString())
- .build()));
+ devices.forEach(dev -> {
+ if (!devMetricsMap.containsKey(dev)) {
+ devMetricsMap.put(dev, genMDbBuilder(type, ControlResource.CONTROL_MESSAGE_METRICS)
+ .withResourceName(dev.toString())
+ .build());
+ }
+ });
Map<String, Double> metrics1 = new HashMap<>();
ControlResource.CONTROL_MESSAGE_METRICS.forEach(msgType ->
diff --git a/core/api/src/main/java/org/onosproject/ui/chart/ChartRequestHandler.java b/core/api/src/main/java/org/onosproject/ui/chart/ChartRequestHandler.java
index 5fba98c..2cbc05f 100644
--- a/core/api/src/main/java/org/onosproject/ui/chart/ChartRequestHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/chart/ChartRequestHandler.java
@@ -18,6 +18,10 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.ui.RequestHandler;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Message handler specifically for the chart views.
*/
@@ -25,6 +29,7 @@
private final String respType;
private final String nodeName;
+ protected static final String LABEL = "label";
/**
* Constructs a chart model handler for a specific graph view. When chart
@@ -61,7 +66,11 @@
* @return an empty chart model
*/
protected ChartModel createChartModel() {
- return new ChartModel(getSeries());
+ List<String> series = new ArrayList<>();
+ series.addAll(Arrays.asList(getSeries()));
+ series.add(LABEL);
+ String[] seiresArray = new String[series.size()];
+ return new ChartModel(series.toArray(seiresArray));
}
/**