Jian Li
Committed by Gerrit Code Review

[ONOS-3851] Implement default Web GUI page for CPMan

- Reduce the datapoints to 20, resolve cold start problem
- Code refactoring for CpmanViewMessageHandler
- Code refactoring for cpman.js
- Show "No Data" message when client does not receive any data
- Clean up cpman.css
- Specify default colors for charting
- Resolve ArrayIndexOutofBoundsException when the number returned
  dataset is less the number what we expected

Change-Id: I67ab3160ab66f92eaffeffc2d61c7d0e17be0512
...@@ -27,7 +27,6 @@ import org.onosproject.cpman.ControlLoadSnapshot; ...@@ -27,7 +27,6 @@ import org.onosproject.cpman.ControlLoadSnapshot;
27 import org.onosproject.cpman.ControlMetricType; 27 import org.onosproject.cpman.ControlMetricType;
28 import org.onosproject.cpman.ControlPlaneMonitorService; 28 import org.onosproject.cpman.ControlPlaneMonitorService;
29 import org.onosproject.net.DeviceId; 29 import org.onosproject.net.DeviceId;
30 -import org.onosproject.net.device.DeviceService;
31 import org.onosproject.ui.RequestHandler; 30 import org.onosproject.ui.RequestHandler;
32 import org.onosproject.ui.UiMessageHandler; 31 import org.onosproject.ui.UiMessageHandler;
33 import org.onosproject.ui.chart.ChartModel; 32 import org.onosproject.ui.chart.ChartModel;
...@@ -38,14 +37,16 @@ import org.slf4j.LoggerFactory; ...@@ -38,14 +37,16 @@ import org.slf4j.LoggerFactory;
38 import java.util.Collection; 37 import java.util.Collection;
39 import java.util.Map; 38 import java.util.Map;
40 import java.util.Optional; 39 import java.util.Optional;
40 +import java.util.Set;
41 import java.util.concurrent.ExecutionException; 41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.TimeUnit; 42 import java.util.concurrent.TimeUnit;
43 +import java.util.stream.LongStream;
43 44
44 import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS; 45 import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS;
45 import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE; 46 import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE;
46 47
47 /** 48 /**
48 - * CpmanViewMessageHandler class implementation. 49 + * Message handler for control plane monitoring view related messages.
49 */ 50 */
50 public class CpmanViewMessageHandler extends UiMessageHandler { 51 public class CpmanViewMessageHandler extends UiMessageHandler {
51 52
...@@ -55,12 +56,14 @@ public class CpmanViewMessageHandler extends UiMessageHandler { ...@@ -55,12 +56,14 @@ public class CpmanViewMessageHandler extends UiMessageHandler {
55 private static final String CPMAN_DATA_RESP = "cpmanDataResponse"; 56 private static final String CPMAN_DATA_RESP = "cpmanDataResponse";
56 private static final String CPMANS = "cpmans"; 57 private static final String CPMANS = "cpmans";
57 58
58 - // TODO: we assume that server side always returns 60 data points 59 + // TODO: we assume that server side always returns 20 data points
59 // to feed 1 hour time slots, later this should make to be configurable 60 // to feed 1 hour time slots, later this should make to be configurable
60 - private static final int NUM_OF_DATA_POINTS = 60; 61 + private static final int NUM_OF_DATA_POINTS = 20;
61 62
62 private static final int MILLI_CONV_UNIT = 1000; 63 private static final int MILLI_CONV_UNIT = 1000;
63 64
65 + private long timestamp = 0L;
66 +
64 @Override 67 @Override
65 protected Collection<RequestHandler> createRequestHandlers() { 68 protected Collection<RequestHandler> createRequestHandlers() {
66 return ImmutableSet.of( 69 return ImmutableSet.of(
...@@ -83,50 +86,104 @@ public class CpmanViewMessageHandler extends UiMessageHandler { ...@@ -83,50 +86,104 @@ public class CpmanViewMessageHandler extends UiMessageHandler {
83 @Override 86 @Override
84 protected void populateChart(ChartModel cm, ObjectNode payload) { 87 protected void populateChart(ChartModel cm, ObjectNode payload) {
85 String uri = string(payload, "devId"); 88 String uri = string(payload, "devId");
89 + ControlPlaneMonitorService cpms = get(ControlPlaneMonitorService.class);
90 + ClusterService cs = get(ClusterService.class);
86 if (!Strings.isNullOrEmpty(uri)) { 91 if (!Strings.isNullOrEmpty(uri)) {
87 - Map<ControlMetricType, Long[]> data = Maps.newHashMap();
88 DeviceId deviceId = DeviceId.deviceId(uri); 92 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 if (cpms.availableResources(CONTROL_MESSAGE).contains(deviceId.toString())) {
93 - LocalDateTime ldt = null; 94 + Map<ControlMetricType, Long[]> data = generateMatrix(cpms, cs, deviceId);
94 - 95 + LocalDateTime ldt = new LocalDateTime(timestamp * MILLI_CONV_UNIT);
95 - try { 96 +
96 - for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) { 97 + populateMetrics(cm, data, ldt, NUM_OF_DATA_POINTS);
97 - ControlLoadSnapshot cls = cpms.getLoad(cs.getLocalNode().id(), 98 + }
98 - cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES, 99 + } else {
99 - Optional.of(deviceId)).get(); 100 + Set<String> deviceIds = cpms.availableResources(CONTROL_MESSAGE);
100 - data.put(cmt, ArrayUtils.toObject(cls.recent())); 101 + for (String deviceId : deviceIds) {
101 - if (ldt == null) { 102 + Map<ControlMetricType, Long> data =
102 - ldt = new LocalDateTime(cls.time() * MILLI_CONV_UNIT); 103 + populateDeviceMetrics(cpms, cs, DeviceId.deviceId(deviceId));
103 - } 104 + Map<String, Long> local = Maps.newHashMap();
104 - } 105 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
105 - 106 + local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt));
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 } 107 }
108 + // TODO: need to find a way to present device id using long type
109 + String shortId = StringUtils.substring(deviceId,
110 + deviceId.length() - 2, deviceId.length());
111 + local.put(LABEL, Long.valueOf(shortId));
112 + populateMetric(cm.addDataPoint(Long.valueOf(shortId)), local);
121 } 113 }
114 + }
115 + }
116 +
117 + private Map<ControlMetricType, Long> populateDeviceMetrics(ControlPlaneMonitorService cpms,
118 + ClusterService cs, DeviceId deviceId) {
119 + Map<ControlMetricType, Long> data = Maps.newHashMap();
120 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
121 + ControlLoadSnapshot cls;
122 + try {
123 + cls = cpms.getLoad(cs.getLocalNode().id(),
124 + cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES,
125 + Optional.of(deviceId)).get();
126 + data.put(cmt, Math.round(LongStream.of(cls.recent()).average().getAsDouble()));
127 + timestamp = cls.time();
128 + } catch (InterruptedException | ExecutionException e) {
129 + log.warn(e.getMessage());
130 + }
131 + }
132 + return data;
133 + }
134 +
135 + private Map<ControlMetricType, Long[]> generateMatrix(ControlPlaneMonitorService cpms,
136 + ClusterService cs, DeviceId deviceId) {
137 + Map<ControlMetricType, Long[]> data = Maps.newHashMap();
138 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
139 + ControlLoadSnapshot cls;
140 + try {
141 + cls = cpms.getLoad(cs.getLocalNode().id(),
142 + cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES,
143 + Optional.of(deviceId)).get();
144 +
145 + // TODO: in some cases, the number of returned dataset is
146 + // less than what we expected (expected -1)
147 + // As a workaround, we simply fill the slot with 0 values,
148 + // such a bug should be fixed with updated RRD4J lib...
149 + data.put(cmt, ArrayUtils.toObject(fillData(cls.recent(), NUM_OF_DATA_POINTS)));
150 + timestamp = cls.time();
151 + } catch (InterruptedException | ExecutionException e) {
152 + log.warn(e.getMessage());
153 + }
154 + }
155 + return data;
156 + }
157 +
158 + private long[] fillData(long[] origin, int expected) {
159 + if (origin.length == expected) {
160 + return origin;
122 } else { 161 } else {
123 - DeviceService ds = get(DeviceService.class); 162 + long[] filled = new long[expected];
124 - ds.getAvailableDevices(); 163 + for (int i = 0; i < expected; i++) {
164 + if (i == 0) {
165 + filled[i] = 0;
166 + } else {
167 + filled[i] = origin[i - 1];
168 + }
169 + }
170 + return filled;
125 } 171 }
126 } 172 }
127 173
128 - private void populateAllDevs(ChartModel.DataPoint dataPoint, Map<String, Long> data) { 174 + private void populateMetrics(ChartModel cm, Map<ControlMetricType,
175 + Long[]> data, LocalDateTime time, int numOfDp) {
176 + for (int i = 0; i < numOfDp; i++) {
177 + Map<String, Long> local = Maps.newHashMap();
178 + for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
179 + local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt)[i]);
180 + }
129 181
182 + local.put(LABEL, time.minusMinutes(numOfDp - i).toDateTime().getMillis());
183 +
184 + populateMetric(cm.addDataPoint(time.minusMinutes(numOfDp - i)
185 + .toDateTime().getMillis()), local);
186 + }
130 } 187 }
131 188
132 private void populateMetric(ChartModel.DataPoint dataPoint, 189 private void populateMetric(ChartModel.DataPoint dataPoint,
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
20 20
21 #ov-cpman { 21 #ov-cpman {
22 padding: 20px; 22 padding: 20px;
23 + position: relative;
23 } 24 }
24 .light #ov-cpman { 25 .light #ov-cpman {
25 color: navy; 26 color: navy;
...@@ -40,22 +41,17 @@ ...@@ -40,22 +41,17 @@
40 background-color: #444; 41 background-color: #444;
41 } 42 }
42 43
43 -#ov-cpman .my-button { 44 +#ov-cpman #chart-loader {
44 - cursor: pointer; 45 + position: absolute;
45 - padding: 4px; 46 + width: 200px;
47 + height: 50px;
48 + margin-left: -100px;
49 + margin-top: -25px;
50 + z-index: 900;
51 + top: 50%;
46 text-align: center; 52 text-align: center;
47 -} 53 + left: 50%;
48 - 54 + font-size: 25px;
49 -.light #ov-cpman .my-button { 55 + font-weight: bold;
50 - color: white; 56 + color: #ccc;
51 - background-color: #99d;
52 -}
53 -.dark #ov-cpman .my-button {
54 - color: black;
55 - background-color: #aaa;
56 -}
57 -
58 -#ov-cpman .number {
59 - font-size: 140%;
60 - text-align: right;
61 } 57 }
...\ No newline at end of file ...\ No newline at end of file
......
1 <!-- partial HTML --> 1 <!-- partial HTML -->
2 <div id="ov-cpman"> 2 <div id="ov-cpman">
3 - <div> 3 + <div id="chart-loader" ng-show="!devId && showLoader">
4 + No Data
5 + </div>
6 + <div ng-show="!devId">
7 + <canvas id="bar" class="chart chart-bar" chart-data="data"
8 + chart-labels="labels" chart-legend="true" chart-click="onClick"
9 + chart-series="series" chart-options="options" height="100%">
10 + </canvas>
11 + </div>
12 + <div ng-show="devId">
13 + <h2>
14 + Chart for Device {{devId || "(No device selected)"}}
15 + </h2>
4 <canvas id="line" class="chart chart-line" chart-data="data" 16 <canvas id="line" class="chart chart-line" chart-data="data"
5 - chart-labels="labels" chart-legend="true" chart-series="series"> 17 + chart-labels="labels" chart-legend="true"
18 + chart-series="series" chart-options="options" height="100%">
6 </canvas> 19 </canvas>
7 </div> 20 </div>
8 </div> 21 </div>
......
...@@ -21,27 +21,48 @@ ...@@ -21,27 +21,48 @@
21 'use strict'; 21 'use strict';
22 22
23 // injected references 23 // injected references
24 - var $log, $scope, $location, ks, fs, cbs; 24 + var $log, $scope, $location, ks, fs, cbs, ns;
25 25
26 - var labels = new Array(60); 26 + var hasDeviceId;
27 - var data = new Array(new Array(60), new Array(60), new Array(60), 27 +
28 - new Array(60), new Array(60), new Array(60)); 28 + var labels = new Array(1);
29 + var data = new Array(6);
30 + for (var i = 0; i < 6; i++) {
31 + data[i] = new Array(1);
32 + }
33 +
34 + var date, max, merged;
35 +
36 + function ceil(num) {
37 + if (isNaN(num)) {
38 + return 0;
39 + }
40 + var pre = num.toString().length - 1
41 + var pow = Math.pow(10, pre);
42 + return (Math.ceil(num / pow)) * pow;
43 + }
29 44
30 angular.module('ovCpman', ["chart.js"]) 45 angular.module('ovCpman', ["chart.js"])
31 .controller('OvCpmanCtrl', 46 .controller('OvCpmanCtrl',
32 - ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService', 47 + ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService', 'NavService',
33 48
34 - function (_$log_, _$scope_, _$location_, _fs_, _cbs_) { 49 + function (_$log_, _$scope_, _$location_, _fs_, _cbs_, _ns_) {
35 var params; 50 var params;
36 $log = _$log_; 51 $log = _$log_;
37 $scope = _$scope_; 52 $scope = _$scope_;
38 $location = _$location_; 53 $location = _$location_;
39 fs = _fs_; 54 fs = _fs_;
40 cbs = _cbs_; 55 cbs = _cbs_;
56 + ns = _ns_;
41 57
42 params = $location.search(); 58 params = $location.search();
59 +
43 if (params.hasOwnProperty('devId')) { 60 if (params.hasOwnProperty('devId')) {
44 $scope.devId = params['devId']; 61 $scope.devId = params['devId'];
62 + hasDeviceId = true;
63 + } else {
64 + $scope.type = 'StackedBar';
65 + hasDeviceId = false;
45 } 66 }
46 67
47 cbs.buildChart({ 68 cbs.buildChart({
...@@ -50,31 +71,70 @@ ...@@ -50,31 +71,70 @@
50 query: params 71 query: params
51 }); 72 });
52 73
53 - var idx = 0;
54 - var date;
55 $scope.$watch('chartData', function () { 74 $scope.$watch('chartData', function () {
56 - idx = 0;
57 if (!fs.isEmptyObject($scope.chartData)) { 75 if (!fs.isEmptyObject($scope.chartData)) {
58 - $scope.chartData.forEach(function (cm) { 76 + $scope.showLoader = false;
77 + var length = $scope.chartData.length;
78 + labels = new Array(length);
79 + for (var i = 0; i < 6; i++) {
80 + data[i] = new Array(length);
81 + }
82 +
83 + $scope.chartData.forEach(function (cm, idx) {
59 data[0][idx] = cm.inbound_packet; 84 data[0][idx] = cm.inbound_packet;
60 data[1][idx] = cm.outbound_packet; 85 data[1][idx] = cm.outbound_packet;
61 data[2][idx] = cm.flow_mod_packet; 86 data[2][idx] = cm.flow_mod_packet;
62 data[3][idx] = cm.flow_removed_packet; 87 data[3][idx] = cm.flow_removed_packet;
63 data[4][idx] = cm.request_packet; 88 data[4][idx] = cm.request_packet;
64 data[5][idx] = cm.reply_packet; 89 data[5][idx] = cm.reply_packet;
65 - date = new Date(cm.label); 90 +
66 - labels[idx] = date.getHours() + ":" + date.getMinutes(); 91 + if(hasDeviceId) {
67 - idx++; 92 + date = new Date(cm.label);
93 + labels[idx] = date.getHours() + ":" + date.getMinutes();
94 + } else {
95 + labels[idx] = cm.label;
96 + }
68 }); 97 });
69 } 98 }
99 +
100 + merged = [].concat.apply([], data);
101 + max = Math.max.apply(null, merged);
102 + $scope.labels = labels;
103 + $scope.data = data;
104 + $scope.options = {
105 + scaleOverride : true,
106 + scaleSteps : 10,
107 + scaleStepWidth : ceil(max) / 10,
108 + scaleStartValue : 0
109 + };
110 + $scope.onClick = function (points, evt) {
111 + if (points[0]) {
112 + // TODO: this will be replaced with real device id
113 + var tmpId = 'of:000000000000020' + points[0].label;
114 + ns.navTo('cpman', { devId: tmpId });
115 + $log.log(points[0].label);
116 + }
117 + };
70 }); 118 });
71 119
72 $scope.series = ['INBOUND', 'OUTBOUND', 'FLOW-MOD', 120 $scope.series = ['INBOUND', 'OUTBOUND', 'FLOW-MOD',
73 'FLOW-REMOVED', 'STATS-REQUEST', 'STATS-REPLY']; 121 'FLOW-REMOVED', 'STATS-REQUEST', 'STATS-REPLY'];
74 $scope.labels = labels; 122 $scope.labels = labels;
75 -
76 $scope.data = data; 123 $scope.data = data;
77 124
125 + $scope.chartColors = [
126 + '#286090',
127 + '#F7464A',
128 + '#46BFBD',
129 + '#FDB45C',
130 + '#97BBCD',
131 + '#4D5360',
132 + '#8c4f9f'
133 + ];
134 + Chart.defaults.global.colours = $scope.chartColors;
135 +
136 + $scope.showLoader = true;
137 +
78 $log.log('OvCpmanCtrl has been created'); 138 $log.log('OvCpmanCtrl has been created');
79 }]); 139 }]);
80 140
......