Merge pull request #3413 from pymumu/master
[project/luci.git] / modules / luci-mod-status / htdocs / luci-static / resources / view / status / load.js
1 'use strict';
2 'require view';
3 'require poll';
4 'require request';
5 'require rpc';
6
7 var callLuciRealtimeStats = rpc.declare({
8 object: 'luci',
9 method: 'getRealtimeStats',
10 params: [ 'mode', 'device' ],
11 expect: { result: [] }
12 });
13
14 var graphPolls = [],
15 pollInterval = 3;
16
17 Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; };
18
19 return view.extend({
20 load: function() {
21 return Promise.all([
22 this.loadSVG(L.resource('load.svg'))
23 ]);
24 },
25
26 updateGraph: function(svg, lines, cb) {
27 var G = svg.firstElementChild;
28
29 var view = document.querySelector('#view');
30
31 var width = view.offsetWidth - 2;
32 var height = 300 - 2;
33 var step = 5;
34
35 var data_wanted = Math.floor(width / step);
36
37 var data_values = [],
38 line_elements = [];
39
40 for (var i = 0; i < lines.length; i++)
41 if (lines[i] != null)
42 data_values.push([]);
43
44 var info = {
45 line_current: [],
46 line_average: [],
47 line_peak: []
48 };
49
50 /* prefill datasets */
51 for (var i = 0; i < data_values.length; i++)
52 for (var j = 0; j < data_wanted; j++)
53 data_values[i][j] = 0;
54
55 /* plot horizontal time interval lines */
56 for (var i = width % (step * 60); i < width; i += step * 60) {
57 var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
58 line.setAttribute('x1', i);
59 line.setAttribute('y1', 0);
60 line.setAttribute('x2', i);
61 line.setAttribute('y2', '100%');
62 line.setAttribute('style', 'stroke:black;stroke-width:0.1');
63
64 var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
65 text.setAttribute('x', i + 5);
66 text.setAttribute('y', 15);
67 text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
68 text.appendChild(document.createTextNode(Math.round((width - i) / step / 60) + 'm'));
69
70 G.appendChild(line);
71 G.appendChild(text);
72 }
73
74 info.interval = pollInterval;
75 info.timeframe = data_wanted / 60;
76
77 graphPolls.push({
78 svg: svg,
79 lines: lines,
80 cb: cb,
81 info: info,
82 width: width,
83 height: height,
84 step: step,
85 values: data_values,
86 timestamp: 0,
87 fill: 1
88 });
89 },
90
91 pollData: function() {
92 poll.add(L.bind(function() {
93 var tasks = [];
94
95 for (var i = 0; i < graphPolls.length; i++) {
96 var ctx = graphPolls[i];
97 tasks.push(L.resolveDefault(callLuciRealtimeStats('load'), []));
98 }
99
100 return Promise.all(tasks).then(L.bind(function(datasets) {
101 for (var gi = 0; gi < graphPolls.length; gi++) {
102 var ctx = graphPolls[gi],
103 data = datasets[gi],
104 values = ctx.values,
105 lines = ctx.lines,
106 info = ctx.info;
107
108 var data_scale = 0;
109 var data_wanted = Math.floor(ctx.width / ctx.step);
110 var last_timestamp = NaN;
111
112 for (var i = 0, di = 0; di < lines.length; di++) {
113 if (lines[di] == null)
114 continue;
115
116 var multiply = (lines[di].multiply != null) ? lines[di].multiply : 1,
117 offset = (lines[di].offset != null) ? lines[di].offset : 0;
118
119 for (var j = ctx.timestamp ? 0 : 1; j < data.length; j++) {
120 /* skip overlapping entries */
121 if (data[j][0] <= ctx.timestamp)
122 continue;
123
124 if (i == 0) {
125 ctx.fill++;
126 last_timestamp = data[j][0];
127 }
128
129 info.line_current[i] = data[j][di + 1] * multiply;
130 info.line_current[i] -= Math.min(info.line_current[i], offset);
131 values[i].push(info.line_current[i]);
132 }
133
134 i++;
135 }
136
137 /* cut off outdated entries */
138 ctx.fill = Math.min(ctx.fill, data_wanted);
139
140 for (var i = 0; i < values.length; i++) {
141 var len = values[i].length;
142 values[i] = values[i].slice(len - data_wanted, len);
143
144 /* find peaks, averages */
145 info.line_peak[i] = NaN;
146 info.line_average[i] = 0;
147
148 for (var j = 0; j < values[i].length; j++) {
149 info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]);
150 info.line_average[i] += values[i][j];
151 }
152
153 info.line_average[i] = info.line_average[i] / ctx.fill;
154 }
155
156 info.peak = Math.max.apply(Math, info.line_peak);
157
158 /* remember current timestamp, calculate horizontal scale */
159 if (!isNaN(last_timestamp))
160 ctx.timestamp = last_timestamp;
161
162 var size = Math.floor(Math.log2(info.peak)),
163 div = Math.pow(2, size - (size % 10)),
164 mult = info.peak / div,
165 mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000));
166
167 info.peak = info.peak + (mult * div) - (info.peak % (mult * div));
168
169 data_scale = ctx.height / info.peak;
170
171 /* plot data */
172 for (var i = 0, di = 0; di < lines.length; di++) {
173 if (lines[di] == null)
174 continue;
175
176 var el = ctx.svg.firstElementChild.getElementById(lines[di].line),
177 pt = '0,' + ctx.height,
178 y = 0;
179
180 if (!el)
181 continue;
182
183 for (var j = 0; j < values[i].length; j++) {
184 var x = j * ctx.step;
185
186 y = ctx.height - Math.floor(values[i][j] * data_scale);
187 //y -= Math.floor(y % (1 / data_scale));
188
189 pt += ' ' + x + ',' + y;
190 }
191
192 pt += ' ' + ctx.width + ',' + y + ' ' + ctx.width + ',' + ctx.height;
193
194 el.setAttribute('points', pt);
195
196 i++;
197 }
198
199 info.label_25 = 0.25 * info.peak;
200 info.label_50 = 0.50 * info.peak;
201 info.label_75 = 0.75 * info.peak;
202
203 if (typeof(ctx.cb) == 'function')
204 ctx.cb(ctx.svg, info);
205 }
206 }, this));
207 }, this), pollInterval);
208 },
209
210 loadSVG: function(src) {
211 return request.get(src).then(function(response) {
212 if (!response.ok)
213 throw new Error(response.statusText);
214
215 return E('div', {
216 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff'
217 }, E(response.text()));
218 });
219 },
220
221 render: function(data) {
222 var svg = data[0];
223
224 var v = E([], [
225 svg,
226 E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')),
227 E('br'),
228
229 E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
230 E('div', { 'class': 'tr' }, [
231 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f00' }, [ _('1 Minute Load:') ])),
232 E('div', { 'class': 'td', 'id': 'lb_load01_cur' }, [ '0.00' ]),
233
234 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
235 E('div', { 'class': 'td', 'id': 'lb_load01_avg' }, [ '0.00' ]),
236
237 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
238 E('div', { 'class': 'td', 'id': 'lb_load01_peak' }, [ '0.00' ])
239 ]),
240 E('div', { 'class': 'tr' }, [
241 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f60' }, [ _('5 Minute Load:') ])),
242 E('div', { 'class': 'td', 'id': 'lb_load05_cur' }, [ '0.00' ]),
243
244 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
245 E('div', { 'class': 'td', 'id': 'lb_load05_avg' }, [ '0.00' ]),
246
247 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
248 E('div', { 'class': 'td', 'id': 'lb_load05_peak' }, [ '0.00' ])
249 ]),
250 E('div', { 'class': 'tr' }, [
251 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #fa0' }, [ _('15 Minute Load:') ])),
252 E('div', { 'class': 'td', 'id': 'lb_load15_cur' }, [ '0.00' ]),
253
254 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
255 E('div', { 'class': 'td', 'id': 'lb_load15_avg' }, [ '0.00' ]),
256
257 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
258 E('div', { 'class': 'td', 'id': 'lb_load15_peak' }, [ '0.00' ])
259 ])
260 ])
261 ]);
262
263 this.updateGraph(svg, [ { line: 'load01' }, { line: 'load05' }, { line: 'load15' } ], function(svg, info) {
264 var G = svg.firstElementChild, tab = svg.parentNode;
265
266 G.getElementById('label_25').firstChild.data = '%.2f'.format(info.label_25 / 100);
267 G.getElementById('label_50').firstChild.data = '%.2f'.format(info.label_50 / 100);
268 G.getElementById('label_75').firstChild.data = '%.2f'.format(info.label_75 / 100);
269
270 tab.querySelector('#scale').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval);
271
272 tab.querySelector('#lb_load01_cur').firstChild.data = '%.2f'.format(info.line_current[0] / 100);
273 tab.querySelector('#lb_load01_avg').firstChild.data = '%.2f'.format(info.line_average[0] / 100);
274 tab.querySelector('#lb_load01_peak').firstChild.data = '%.2f'.format(info.line_peak[0] / 100);
275
276 tab.querySelector('#lb_load05_cur').firstChild.data = '%.2f'.format(info.line_current[1] / 100);
277 tab.querySelector('#lb_load05_avg').firstChild.data = '%.2f'.format(info.line_average[1] / 100);
278 tab.querySelector('#lb_load05_peak').firstChild.data = '%.2f'.format(info.line_peak[1] / 100);
279
280 tab.querySelector('#lb_load15_cur').firstChild.data = '%.2f'.format(info.line_current[2] / 100);
281 tab.querySelector('#lb_load15_avg').firstChild.data = '%.2f'.format(info.line_average[2] / 100);
282 tab.querySelector('#lb_load15_peak').firstChild.data = '%.2f'.format(info.line_peak[2] / 100);
283 });
284
285 this.pollData();
286
287 return v;
288 },
289
290 handleSaveApply: null,
291 handleSave: null,
292 handleReset: null
293 });