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
Showing
4 changed files
with
197 additions
and
71 deletions
... | @@ -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 | ... | ... |
-
Please register or login to post a comment