Jian Li

[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
...@@ -17,6 +17,7 @@ package org.onosproject.cpman; ...@@ -17,6 +17,7 @@ package org.onosproject.cpman;
17 17
18 import com.google.common.base.MoreObjects; 18 import com.google.common.base.MoreObjects;
19 19
20 +import java.util.Arrays;
20 import java.util.Objects; 21 import java.util.Objects;
21 22
22 import static com.google.common.base.MoreObjects.toStringHelper; 23 import static com.google.common.base.MoreObjects.toStringHelper;
...@@ -29,13 +30,14 @@ public class ControlLoadSnapshot { ...@@ -29,13 +30,14 @@ public class ControlLoadSnapshot {
29 private final long latest; 30 private final long latest;
30 private final long average; 31 private final long average;
31 private final long time; 32 private final long time;
33 + private long[] recent;
32 34
33 /** 35 /**
34 * Instantiates a new control metric response with given latest, average, time. 36 * Instantiates a new control metric response with given latest, average, time.
35 * 37 *
36 * @param latest latest value of control metric 38 * @param latest latest value of control metric
37 * @param average average value of control metric 39 * @param average average value of control metric
38 - * @param time last logging time fo control metric 40 + * @param time last logging time of control metric
39 */ 41 */
40 public ControlLoadSnapshot(long latest, long average, long time) { 42 public ControlLoadSnapshot(long latest, long average, long time) {
41 this.latest = latest; 43 this.latest = latest;
...@@ -44,6 +46,22 @@ public class ControlLoadSnapshot { ...@@ -44,6 +46,22 @@ public class ControlLoadSnapshot {
44 } 46 }
45 47
46 /** 48 /**
49 + * Instantiates a new control metric response with given latest, average, time,
50 + * recent values.
51 + *
52 + * @param latest latest value of control metric
53 + * @param average average value of control metric
54 + * @param time last logging time of control metric
55 + * @param recent a set of historical data
56 + */
57 + public ControlLoadSnapshot(long latest, long average, long time, long[] recent) {
58 + this.latest = latest;
59 + this.average = average;
60 + this.time = time;
61 + this.recent = recent;
62 + }
63 +
64 + /**
47 * Returns latest value of control metric. 65 * Returns latest value of control metric.
48 * 66 *
49 * @return latest value of control metric 67 * @return latest value of control metric
...@@ -70,9 +88,18 @@ public class ControlLoadSnapshot { ...@@ -70,9 +88,18 @@ public class ControlLoadSnapshot {
70 return average; 88 return average;
71 } 89 }
72 90
91 + /**
92 + * Returns a set of historical recent of control metric.
93 + *
94 + * @return a set of historical recent of control metric
95 + */
96 + public long[] recent() {
97 + return recent;
98 + }
99 +
73 @Override 100 @Override
74 public int hashCode() { 101 public int hashCode() {
75 - return Objects.hash(latest, average, time); 102 + return Objects.hash(latest, average, time, recent);
76 } 103 }
77 104
78 @Override 105 @Override
...@@ -84,7 +111,8 @@ public class ControlLoadSnapshot { ...@@ -84,7 +111,8 @@ public class ControlLoadSnapshot {
84 final ControlLoadSnapshot other = (ControlLoadSnapshot) obj; 111 final ControlLoadSnapshot other = (ControlLoadSnapshot) obj;
85 return Objects.equals(this.latest, other.latest) && 112 return Objects.equals(this.latest, other.latest) &&
86 Objects.equals(this.average, other.average) && 113 Objects.equals(this.average, other.average) &&
87 - Objects.equals(this.time, other.time); 114 + Objects.equals(this.time, other.time) &&
115 + Arrays.equals(this.recent, other.recent);
88 } 116 }
89 return false; 117 return false;
90 } 118 }
...@@ -95,7 +123,9 @@ public class ControlLoadSnapshot { ...@@ -95,7 +123,9 @@ public class ControlLoadSnapshot {
95 helper = toStringHelper(this) 123 helper = toStringHelper(this)
96 .add("latest", latest) 124 .add("latest", latest)
97 .add("average", average) 125 .add("average", average)
98 - .add("time", time); 126 + .add("time", time)
127 + .add("recent", recent);
128 +
99 return helper.toString(); 129 return helper.toString();
100 } 130 }
101 } 131 }
......
...@@ -16,44 +16,122 @@ ...@@ -16,44 +16,122 @@
16 package org.onosproject.cpman.gui; 16 package org.onosproject.cpman.gui;
17 17
18 import com.fasterxml.jackson.databind.node.ObjectNode; 18 import com.fasterxml.jackson.databind.node.ObjectNode;
19 +import com.google.common.base.Strings;
19 import com.google.common.collect.ImmutableSet; 20 import com.google.common.collect.ImmutableSet;
21 +import com.google.common.collect.Maps;
22 +import org.apache.commons.lang.ArrayUtils;
23 +import org.apache.commons.lang3.StringUtils;
24 +import org.joda.time.LocalDateTime;
25 +import org.onosproject.cluster.ClusterService;
26 +import org.onosproject.cpman.ControlLoadSnapshot;
27 +import org.onosproject.cpman.ControlMetricType;
28 +import org.onosproject.cpman.ControlPlaneMonitorService;
29 +import org.onosproject.net.DeviceId;
30 +import org.onosproject.net.device.DeviceService;
20 import org.onosproject.ui.RequestHandler; 31 import org.onosproject.ui.RequestHandler;
21 import org.onosproject.ui.UiMessageHandler; 32 import org.onosproject.ui.UiMessageHandler;
33 +import org.onosproject.ui.chart.ChartModel;
34 +import org.onosproject.ui.chart.ChartRequestHandler;
35 +import org.slf4j.Logger;
36 +import org.slf4j.LoggerFactory;
22 37
23 import java.util.Collection; 38 import java.util.Collection;
24 -import java.util.Random; 39 +import java.util.Map;
40 +import java.util.Optional;
41 +import java.util.concurrent.ExecutionException;
42 +import java.util.concurrent.TimeUnit;
43 +
44 +import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS;
45 +import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE;
25 46
26 /** 47 /**
27 * CpmanViewMessageHandler class implementation. 48 * CpmanViewMessageHandler class implementation.
28 */ 49 */
29 public class CpmanViewMessageHandler extends UiMessageHandler { 50 public class CpmanViewMessageHandler extends UiMessageHandler {
30 51
52 + private final Logger log = LoggerFactory.getLogger(getClass());
53 +
31 private static final String CPMAN_DATA_REQ = "cpmanDataRequest"; 54 private static final String CPMAN_DATA_REQ = "cpmanDataRequest";
32 private static final String CPMAN_DATA_RESP = "cpmanDataResponse"; 55 private static final String CPMAN_DATA_RESP = "cpmanDataResponse";
56 + private static final String CPMANS = "cpmans";
57 +
58 + // TODO: we assume that server side always returns 60 data points
59 + // to feed 1 hour time slots, later this should make to be configurable
60 + private static final int NUM_OF_DATA_POINTS = 60;
33 61
34 - private static final String RANDOM = "random"; 62 + private static final int MILLI_CONV_UNIT = 1000;
35 63
36 @Override 64 @Override
37 protected Collection<RequestHandler> createRequestHandlers() { 65 protected Collection<RequestHandler> createRequestHandlers() {
38 return ImmutableSet.of( 66 return ImmutableSet.of(
39 - new CpmanDataRequestHandler() 67 + new ControlMessageRequest()
40 ); 68 );
41 } 69 }
42 70
43 - // handler for sample data requests 71 + private final class ControlMessageRequest extends ChartRequestHandler {
44 - private final class CpmanDataRequestHandler extends RequestHandler {
45 72
46 - private CpmanDataRequestHandler() { 73 + private ControlMessageRequest() {
47 - super(CPMAN_DATA_REQ); 74 + super(CPMAN_DATA_REQ, CPMAN_DATA_RESP, CPMANS);
75 + }
76 +
77 + @Override
78 + protected String[] getSeries() {
79 + return CONTROL_MESSAGE_METRICS.stream().map(type ->
80 + StringUtils.lowerCase(type.name())).toArray(String[]::new);
48 } 81 }
49 82
50 @Override 83 @Override
51 - public void process(long sid, ObjectNode payload) { 84 + protected void populateChart(ChartModel cm, ObjectNode payload) {
52 - ObjectNode result = objectNode(); 85 + String uri = string(payload, "devId");
53 - Random random = new Random(); 86 + if (!Strings.isNullOrEmpty(uri)) {
54 - result.put(RANDOM, random.nextInt(50) + 1); 87 + Map<ControlMetricType, Long[]> data = Maps.newHashMap();
88 + DeviceId deviceId = DeviceId.deviceId(uri);
89 + ClusterService cs = get(ClusterService.class);
90 + ControlPlaneMonitorService cpms = get(ControlPlaneMonitorService.class);
91 +
92 + if (cpms.availableResources(CONTROL_MESSAGE).contains(deviceId.toString())) {
93 + LocalDateTime ldt = null;
94 +
95 + try {
96 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
97 + ControlLoadSnapshot cls = cpms.getLoad(cs.getLocalNode().id(),
98 + cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES,
99 + Optional.of(deviceId)).get();
100 + data.put(cmt, ArrayUtils.toObject(cls.recent()));
101 + if (ldt == null) {
102 + ldt = new LocalDateTime(cls.time() * MILLI_CONV_UNIT);
103 + }
104 + }
105 +
106 + for (int i = 0; i < NUM_OF_DATA_POINTS; i++) {
107 + Map<String, Long> local = Maps.newHashMap();
108 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
109 + local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt)[i]);
110 + }
111 +
112 + local.put(LABEL, ldt.minusMinutes(NUM_OF_DATA_POINTS - i).toDateTime().getMillis());
113 +
114 + populateMetric(cm.addDataPoint(ldt.minusMinutes(NUM_OF_DATA_POINTS - i)
115 + .toDateTime().getMillis()), local);
116 + }
117 +
118 + } catch (InterruptedException | ExecutionException e) {
119 + log.warn(e.getMessage());
120 + }
121 + }
122 + } else {
123 + DeviceService ds = get(DeviceService.class);
124 + ds.getAvailableDevices();
125 + }
126 + }
127 +
128 + private void populateAllDevs(ChartModel.DataPoint dataPoint, Map<String, Long> data) {
129 +
130 + }
55 131
56 - sendMessage(CPMAN_DATA_RESP, 0, result); 132 + private void populateMetric(ChartModel.DataPoint dataPoint,
133 + Map<String, Long> data) {
134 + data.forEach((k, v) -> dataPoint.data(k, v.doubleValue()));
57 } 135 }
58 } 136 }
59 } 137 }
......
...@@ -81,10 +81,10 @@ public class ControlPlaneManager { ...@@ -81,10 +81,10 @@ public class ControlPlaneManager {
81 // TODO: this can be changed to switch-case if we have more than 81 // TODO: this can be changed to switch-case if we have more than
82 // one event type 82 // one event type
83 if (event.type().equals(STATS_UPDATE)) { 83 if (event.type().equals(STATS_UPDATE)) {
84 - controlMessages.forEach(c -> { 84 + controlMessages.forEach(c ->
85 monitorService.updateMetric(getControlMetric(c), 1, 85 monitorService.updateMetric(getControlMetric(c), 1,
86 - Optional.of(c.deviceId())); 86 + Optional.of(c.deviceId()))
87 - }); 87 + );
88 } 88 }
89 } 89 }
90 } 90 }
......
...@@ -169,7 +169,7 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -169,7 +169,7 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
169 if (ctrlMsgBuf.get(deviceId.get()).keySet() 169 if (ctrlMsgBuf.get(deviceId.get()).keySet()
170 .containsAll(CONTROL_MESSAGE_METRICS)) { 170 .containsAll(CONTROL_MESSAGE_METRICS)) {
171 updateControlMessages(ctrlMsgBuf.get(deviceId.get()), deviceId.get()); 171 updateControlMessages(ctrlMsgBuf.get(deviceId.get()), deviceId.get());
172 - ctrlMsgBuf.get(deviceId.get()); 172 + ctrlMsgBuf.clear();
173 } 173 }
174 } 174 }
175 } else { 175 } else {
...@@ -327,8 +327,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -327,8 +327,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
327 */ 327 */
328 private void updateNetworkMetrics(Map<ControlMetricType, Double> metricMap, 328 private void updateNetworkMetrics(Map<ControlMetricType, Double> metricMap,
329 String resourceName) { 329 String resourceName) {
330 - networkMetricsMap.putIfAbsent(resourceName, genMDbBuilder(resourceName, 330 + if (!networkMetricsMap.containsKey(resourceName)) {
331 + networkMetricsMap.put(resourceName, genMDbBuilder(resourceName,
331 Type.NETWORK, NETWORK_METRICS)); 332 Type.NETWORK, NETWORK_METRICS));
333 + }
332 networkMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap)); 334 networkMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap));
333 } 335 }
334 336
...@@ -340,8 +342,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -340,8 +342,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
340 */ 342 */
341 private void updateDiskMetrics(Map<ControlMetricType, Double> metricMap, 343 private void updateDiskMetrics(Map<ControlMetricType, Double> metricMap,
342 String resourceName) { 344 String resourceName) {
343 - diskMetricsMap.putIfAbsent(resourceName, genMDbBuilder(resourceName, 345 + if (!diskMetricsMap.containsKey(resourceName)) {
346 + diskMetricsMap.put(resourceName, genMDbBuilder(resourceName,
344 Type.DISK, DISK_METRICS)); 347 Type.DISK, DISK_METRICS));
348 + }
345 diskMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap)); 349 diskMetricsMap.get(resourceName).updateMetrics(convertMap(metricMap));
346 } 350 }
347 351
...@@ -353,8 +357,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -353,8 +357,10 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
353 */ 357 */
354 private void updateControlMessages(Map<ControlMetricType, Double> metricMap, 358 private void updateControlMessages(Map<ControlMetricType, Double> metricMap,
355 DeviceId deviceId) { 359 DeviceId deviceId) {
356 - controlMessageMap.putIfAbsent(deviceId, genMDbBuilder(deviceId.toString(), 360 + if (!controlMessageMap.containsKey(deviceId)) {
361 + controlMessageMap.put(deviceId, genMDbBuilder(deviceId.toString(),
357 Type.CONTROL_MESSAGE, CONTROL_MESSAGE_METRICS)); 362 Type.CONTROL_MESSAGE, CONTROL_MESSAGE_METRICS));
363 + }
358 controlMessageMap.get(deviceId).updateMetrics(convertMap(metricMap)); 364 controlMessageMap.get(deviceId).updateMetrics(convertMap(metricMap));
359 } 365 }
360 366
...@@ -478,7 +484,9 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -478,7 +484,9 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
478 */ 484 */
479 private ControlLoadSnapshot snapshot(ControlLoad cl, int duration, TimeUnit unit) { 485 private ControlLoadSnapshot snapshot(ControlLoad cl, int duration, TimeUnit unit) {
480 if (cl != null) { 486 if (cl != null) {
481 - return new ControlLoadSnapshot(cl.latest(), cl.average(duration, unit), cl.time()); 487 +
488 + return new ControlLoadSnapshot(cl.latest(), cl.average(duration, unit),
489 + cl.time(), cl.recent(duration, unit));
482 } 490 }
483 return null; 491 return null;
484 } 492 }
......
1 <!-- partial HTML --> 1 <!-- partial HTML -->
2 <div id="ov-cpman"> 2 <div id="ov-cpman">
3 - <div class="button-panel"> 3 + <div>
4 - <div class="my-button" ng-click="getData()"> 4 + <canvas id="line" class="chart chart-line" chart-data="data"
5 - Fetch Data 5 + chart-labels="labels" chart-legend="true" chart-series="series">
6 - </div> 6 + </canvas>
7 - </div>
8 -
9 - <div class="data-panel">
10 - <table>
11 - <tr>
12 - <td> Number </td>
13 - <td class="number"> {{data.random}} </td>
14 - </tr>
15 - </table>
16 </div> 7 </div>
17 </div> 8 </div>
......
...@@ -20,66 +20,60 @@ ...@@ -20,66 +20,60 @@
20 (function () { 20 (function () {
21 'use strict'; 21 'use strict';
22 22
23 - // injected refs 23 + // injected references
24 - var $log, $scope, wss, ks; 24 + var $log, $scope, $location, ks, fs, cbs;
25 25
26 - // constants 26 + var labels = new Array(60);
27 - var dataReq = 'cpmanDataRequest', 27 + var data = new Array(new Array(60), new Array(60), new Array(60),
28 - dataResp = 'cpmanDataResponse'; 28 + new Array(60), new Array(60), new Array(60));
29 29
30 - function addKeyBindings() { 30 + angular.module('ovCpman', ["chart.js"])
31 - var map = {
32 - space: [getData, 'Fetch data from server'],
33 -
34 - _helpFormat: [
35 - ['space']
36 - ]
37 - };
38 -
39 - ks.keyBindings(map);
40 - }
41 -
42 - function getData() {
43 - wss.sendEvent(dataReq);
44 - }
45 -
46 - function respDataCb(data) {
47 - $scope.data = data;
48 - $scope.$apply();
49 - }
50 -
51 -
52 - angular.module('ovCpman', [])
53 .controller('OvCpmanCtrl', 31 .controller('OvCpmanCtrl',
54 - ['$log', '$scope', 'WebSocketService', 'KeyService', 32 + ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService',
55 33
56 - function (_$log_, _$scope_, _wss_, _ks_) { 34 + function (_$log_, _$scope_, _$location_, _fs_, _cbs_) {
35 + var params;
57 $log = _$log_; 36 $log = _$log_;
58 $scope = _$scope_; 37 $scope = _$scope_;
59 - wss = _wss_; 38 + $location = _$location_;
60 - ks = _ks_; 39 + fs = _fs_;
61 - 40 + cbs = _cbs_;
62 - var handlers = {};
63 - $scope.data = {};
64 41
65 - // data response handler 42 + params = $location.search();
66 - handlers[dataResp] = respDataCb; 43 + if (params.hasOwnProperty('devId')) {
67 - wss.bindHandlers(handlers); 44 + $scope.devId = params['devId'];
45 + }
68 46
69 - addKeyBindings(); 47 + cbs.buildChart({
48 + scope: $scope,
49 + tag: 'cpman',
50 + query: params
51 + });
70 52
71 - // custom click handler 53 + var idx = 0;
72 - $scope.getData = getData; 54 + var date;
55 + $scope.$watch('chartData', function () {
56 + idx = 0;
57 + if (!fs.isEmptyObject($scope.chartData)) {
58 + $scope.chartData.forEach(function (cm) {
59 + data[0][idx] = cm.inbound_packet;
60 + data[1][idx] = cm.outbound_packet;
61 + data[2][idx] = cm.flow_mod_packet;
62 + data[3][idx] = cm.flow_removed_packet;
63 + data[4][idx] = cm.request_packet;
64 + data[5][idx] = cm.reply_packet;
65 + date = new Date(cm.label);
66 + labels[idx] = date.getHours() + ":" + date.getMinutes();
67 + idx++;
68 + });
69 + }
70 + });
73 71
74 - // get data the first time... 72 + $scope.series = ['INBOUND', 'OUTBOUND', 'FLOW-MOD',
75 - getData(); 73 + 'FLOW-REMOVED', 'STATS-REQUEST', 'STATS-REPLY'];
74 + $scope.labels = labels;
76 75
77 - // cleanup 76 + $scope.data = data;
78 - $scope.$on('$destroy', function () {
79 - wss.unbindHandlers(handlers);
80 - ks.unbindKeys();
81 - $log.log('OvCpmanCtrl has been destroyed');
82 - });
83 77
84 $log.log('OvCpmanCtrl has been created'); 78 $log.log('OvCpmanCtrl has been created');
85 }]); 79 }]);
......
...@@ -149,11 +149,13 @@ public class MetricsDatabaseTest { ...@@ -149,11 +149,13 @@ public class MetricsDatabaseTest {
149 devMetricsMap = Maps.newHashMap(); 149 devMetricsMap = Maps.newHashMap();
150 150
151 Set<DeviceId> devices = ImmutableSet.of(devId1, devId2); 151 Set<DeviceId> devices = ImmutableSet.of(devId1, devId2);
152 - devices.forEach(dev -> 152 + devices.forEach(dev -> {
153 - devMetricsMap.putIfAbsent(dev, 153 + if (!devMetricsMap.containsKey(dev)) {
154 - genMDbBuilder(type, ControlResource.CONTROL_MESSAGE_METRICS) 154 + devMetricsMap.put(dev, genMDbBuilder(type, ControlResource.CONTROL_MESSAGE_METRICS)
155 .withResourceName(dev.toString()) 155 .withResourceName(dev.toString())
156 - .build())); 156 + .build());
157 + }
158 + });
157 159
158 Map<String, Double> metrics1 = new HashMap<>(); 160 Map<String, Double> metrics1 = new HashMap<>();
159 ControlResource.CONTROL_MESSAGE_METRICS.forEach(msgType -> 161 ControlResource.CONTROL_MESSAGE_METRICS.forEach(msgType ->
......
...@@ -18,6 +18,10 @@ package org.onosproject.ui.chart; ...@@ -18,6 +18,10 @@ package org.onosproject.ui.chart;
18 import com.fasterxml.jackson.databind.node.ObjectNode; 18 import com.fasterxml.jackson.databind.node.ObjectNode;
19 import org.onosproject.ui.RequestHandler; 19 import org.onosproject.ui.RequestHandler;
20 20
21 +import java.util.ArrayList;
22 +import java.util.Arrays;
23 +import java.util.List;
24 +
21 /** 25 /**
22 * Message handler specifically for the chart views. 26 * Message handler specifically for the chart views.
23 */ 27 */
...@@ -25,6 +29,7 @@ public abstract class ChartRequestHandler extends RequestHandler { ...@@ -25,6 +29,7 @@ public abstract class ChartRequestHandler extends RequestHandler {
25 29
26 private final String respType; 30 private final String respType;
27 private final String nodeName; 31 private final String nodeName;
32 + protected static final String LABEL = "label";
28 33
29 /** 34 /**
30 * Constructs a chart model handler for a specific graph view. When chart 35 * Constructs a chart model handler for a specific graph view. When chart
...@@ -61,7 +66,11 @@ public abstract class ChartRequestHandler extends RequestHandler { ...@@ -61,7 +66,11 @@ public abstract class ChartRequestHandler extends RequestHandler {
61 * @return an empty chart model 66 * @return an empty chart model
62 */ 67 */
63 protected ChartModel createChartModel() { 68 protected ChartModel createChartModel() {
64 - return new ChartModel(getSeries()); 69 + List<String> series = new ArrayList<>();
70 + series.addAll(Arrays.asList(getSeries()));
71 + series.add(LABEL);
72 + String[] seiresArray = new String[series.size()];
73 + return new ChartModel(series.toArray(seiresArray));
65 } 74 }
66 75
67 /** 76 /**
......