mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 03:01:54 +08:00
510 lines
14 KiB
JavaScript
510 lines
14 KiB
JavaScript
'use strict';
|
|
'require dom';
|
|
'require fs';
|
|
'require poll';
|
|
'require request';
|
|
'require view';
|
|
'require ui';
|
|
|
|
document.head.append(E('style', {'type': 'text/css'},
|
|
`
|
|
:root {
|
|
--app-cpu-status-total: gray;
|
|
--app-cpu-status-user: blue;
|
|
--app-cpu-status-sys: red;
|
|
--app-cpu-status-sirq: green;
|
|
}
|
|
.svg_background {
|
|
width: 100%;
|
|
height: 300px;
|
|
border: 1px solid #000;
|
|
background: #fff';
|
|
}
|
|
[data-darkmode="true"] .svg_background {
|
|
background-color: var(--background-color-high) !important;
|
|
}
|
|
.graph_legend {
|
|
border-bottom: 2px solid;
|
|
}
|
|
.total {
|
|
border-color: var(--app-cpu-status-total);
|
|
}
|
|
.user{
|
|
border-color: var(--app-cpu-status-user);
|
|
}
|
|
.sys {
|
|
border-color: var(--app-cpu-status-sys);
|
|
}
|
|
.sirq {
|
|
border-color: var(--app-cpu-status-sirq);
|
|
}
|
|
svg line.grid {
|
|
stroke: black;
|
|
stroke-width: 0.1;
|
|
}
|
|
[data-darkmode="true"] svg line.grid {
|
|
stroke: #fff !important;
|
|
}
|
|
svg text {
|
|
fill: #eee;
|
|
font-size: 9pt;
|
|
font-family: sans-serif;
|
|
text-shadow: 1px 1px 1px #000;
|
|
}
|
|
svg #total_line {
|
|
fill: var(--app-cpu-status-total);
|
|
fill-opacity: 0.4;
|
|
stroke: var(--app-cpu-status-total);
|
|
stroke-width: 1;
|
|
}
|
|
svg #user_line {
|
|
fill: var(--app-cpu-status-user);
|
|
fill-opacity: 0.5;
|
|
stroke: var(--app-cpu-status-user);
|
|
stroke-width: 1;
|
|
}
|
|
svg #sys_line {
|
|
fill: var(--app-cpu-status-sys);
|
|
fill-opacity: 0.5;
|
|
stroke: var(--app-cpu-status-sys);
|
|
stroke-width: 1;
|
|
}
|
|
svg #sirq_line {
|
|
fill: var(--app-cpu-status-sirq);
|
|
fill-opacity: 0.5;
|
|
stroke: var(--app-cpu-status-sirq);
|
|
stroke-width: 1;
|
|
}
|
|
`));
|
|
|
|
Math.log2 = Math.log2 || (x => Math.log(x) * Math.LOG2E);
|
|
|
|
return view.extend({
|
|
pollInterval : 3,
|
|
|
|
dataBufferSize: 4,
|
|
|
|
CPUDevices : {},
|
|
|
|
graphPolls : [],
|
|
|
|
lastStatArray : null,
|
|
|
|
parseProcData(data) {
|
|
let cpu_stat_array = [];
|
|
let stat_items_array = data.trim().split('\n').filter(s => s.startsWith('cpu'));
|
|
|
|
for(let str of stat_items_array) {
|
|
let arr = str.split(/\s+/).slice(0, 8);
|
|
arr[0] = (arr[0] === 'cpu') ? -1 : arr[0].replace('cpu', '');
|
|
cpu_stat_array.push(arr.map(e => Number(e)));
|
|
};
|
|
|
|
cpu_stat_array.sort((a, b) => a[0] - b[0]);
|
|
return cpu_stat_array;
|
|
},
|
|
|
|
calcCPULoad(cpu_stat_array) {
|
|
let ret_array = [];
|
|
cpu_stat_array.forEach((c, i) => {
|
|
let loadUser = 0,
|
|
loadNice = 0,
|
|
loadSys = 0,
|
|
loadIdle = 0,
|
|
loadIo = 0,
|
|
loadIrq = 0,
|
|
loadSirq = 0,
|
|
loadAvg = 0;
|
|
if(this.lastStatArray !== null && this.lastStatArray.cpuNum === c.cpuNum) {
|
|
let user = c[1] - this.lastStatArray[i][1],
|
|
nice = c[2] - this.lastStatArray[i][2],
|
|
sys = c[3] - this.lastStatArray[i][3],
|
|
idle = c[4] - this.lastStatArray[i][4],
|
|
io = c[5] - this.lastStatArray[i][5],
|
|
irq = c[6] - this.lastStatArray[i][6],
|
|
sirq = c[7] - this.lastStatArray[i][7];
|
|
let sum = user + nice + sys + idle + io + irq + sirq;
|
|
loadUser = Number((100 * user / sum).toFixed(1));
|
|
loadNice = Number((100 * nice / sum).toFixed(1));
|
|
loadSys = Number((100 * sys / sum).toFixed(1));
|
|
loadIdle = Number((100 * idle / sum).toFixed(1));
|
|
loadIo = Number((100 * io / sum).toFixed(1));
|
|
loadIrq = Number((100 * irq / sum).toFixed(1));
|
|
loadSirq = Number((100 * sirq / sum).toFixed(1));
|
|
loadAvg = Math.round(100 * (user + nice + sys + io + irq + sirq) / sum);
|
|
};
|
|
ret_array.push(
|
|
{cpuNum: c[0], loadUser, loadNice, loadSys, loadIdle, loadIo, loadIrq, loadSirq, loadAvg});
|
|
});
|
|
this.lastStatArray = cpu_stat_array;
|
|
return ret_array;
|
|
},
|
|
|
|
getCPUData(cpu_data) {
|
|
return L.resolveDefault(fs.read('/proc/stat'), null).then(cpu_data => {
|
|
if(cpu_data) {
|
|
let cpu_stat_array = this.parseProcData(cpu_data);
|
|
|
|
// For single-core CPU
|
|
if(cpu_stat_array.length === 2) {
|
|
cpu_stat_array = cpu_stat_array.slice(0, 1);
|
|
};
|
|
|
|
let cpu_load_array = this.calcCPULoad(cpu_stat_array);
|
|
|
|
for(let i = 0; i < cpu_load_array.length; i++) {
|
|
let num = i;
|
|
let total = cpu_load_array[i].loadAvg;
|
|
let user = cpu_load_array[i].loadUser;
|
|
let sys = cpu_load_array[i].loadSys;
|
|
let sirq = cpu_load_array[i].loadSirq;
|
|
|
|
if(!(num in this.CPUDevices)) {
|
|
this.CPUDevices[num] = {
|
|
num : i,
|
|
name: String(cpu_load_array[i].cpuNum),
|
|
load: [],
|
|
};
|
|
};
|
|
|
|
let load_array = this.CPUDevices[num].load;
|
|
load_array.push([
|
|
new Date().getTime(),
|
|
total || 0,
|
|
user || 0,
|
|
sys || 0,
|
|
sirq || 0,
|
|
]);
|
|
if(load_array.length > this.dataBufferSize) {
|
|
load_array.shift();
|
|
};
|
|
};
|
|
|
|
};
|
|
return this.CPUDevices;
|
|
});
|
|
},
|
|
|
|
loadSVG(src) {
|
|
return request.get(src).then(response => {
|
|
if(!response.ok) {
|
|
throw new Error(response.statusText);
|
|
};
|
|
|
|
return E('div', {
|
|
'class': 'svg_background',
|
|
}, E(response.text()));
|
|
});
|
|
},
|
|
|
|
updateGraph(cpu_num, svg, peak, lines, cb) {
|
|
let G = svg.firstElementChild;
|
|
let view = document.querySelector('#view');
|
|
let width = view.offsetWidth - 2;
|
|
let height = 300 - 2;
|
|
let base_step = 5;
|
|
let time_interval = 60;
|
|
let time_interval_min = time_interval / 60
|
|
let step = base_step * this.pollInterval;
|
|
let data_wanted = Math.ceil(width / step);
|
|
let timeline_offset = width % step;
|
|
let data_values = [];
|
|
let line_elements = [];
|
|
|
|
for(let i = 0; i < lines.length; i++) {
|
|
if(lines[i] != null) {
|
|
data_values.push([]);
|
|
};
|
|
};
|
|
|
|
let info = {
|
|
line_current: [],
|
|
line_average: [],
|
|
line_peak : [],
|
|
};
|
|
|
|
/* prefill datasets */
|
|
for(let i = 0; i < data_values.length; i++) {
|
|
for(let j = 0; j < data_wanted; j++) {
|
|
data_values[i][j] = NaN;
|
|
};
|
|
};
|
|
|
|
/* plot horizontal time interval lines */
|
|
for(let i = width % (base_step * time_interval); i < width; i += base_step * time_interval) {
|
|
let x = i - (timeline_offset);
|
|
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
line.setAttribute('x1', x);
|
|
line.setAttribute('y1', 0);
|
|
line.setAttribute('x2', x);
|
|
line.setAttribute('y2', '100%');
|
|
line.setAttribute('class', 'grid');
|
|
|
|
let text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
text.setAttribute('x', x + 5);
|
|
text.setAttribute('y', 15);
|
|
text.append(document.createTextNode(String((width - i) / base_step / time_interval) + 'm'));
|
|
|
|
G.append(line);
|
|
G.append(text);
|
|
};
|
|
|
|
info.interval = this.pollInterval;
|
|
info.timeframe = Math.floor(data_wanted / time_interval * this.pollInterval);
|
|
|
|
this.graphPolls.push({
|
|
cpu_num,
|
|
svg,
|
|
peak,
|
|
lines,
|
|
cb,
|
|
info,
|
|
width,
|
|
height,
|
|
step,
|
|
data_wanted,
|
|
values : data_values,
|
|
timestamp : 0,
|
|
fill : 1,
|
|
});
|
|
},
|
|
|
|
pollData() {
|
|
poll.add(L.bind(function() {
|
|
return this.getCPUData().then(L.bind(function(datasets) {
|
|
|
|
for(let gi = 0; gi < this.graphPolls.length; gi++) {
|
|
let ctx = this.graphPolls[gi];
|
|
|
|
if(!datasets[ctx.cpu_num]) {
|
|
continue;
|
|
};
|
|
|
|
let data = datasets[ctx.cpu_num].load;
|
|
|
|
if(!data) {
|
|
continue;
|
|
};
|
|
|
|
let values = ctx.values;
|
|
let lines = ctx.lines;
|
|
let info = ctx.info;
|
|
let peak = ctx.peak;
|
|
let data_scale = 0;
|
|
let data_wanted = ctx.data_wanted;
|
|
let last_timestamp = NaN;
|
|
|
|
for(let i = 0, di = 0; di < lines.length; di++) {
|
|
if(lines[di] == null) {
|
|
continue;
|
|
};
|
|
|
|
for(let j = ctx.timestamp ? 0 : 1; j < data.length; j++) {
|
|
|
|
/* skip overlapping and empty entries */
|
|
if(data[j][0] <= ctx.timestamp) {
|
|
continue;
|
|
};
|
|
|
|
if(i == 0) {
|
|
ctx.fill++;
|
|
last_timestamp = data[j][0];
|
|
};
|
|
|
|
info.line_current[i] = data[j][di + 1];
|
|
values[i].push(info.line_current[i]);
|
|
};
|
|
|
|
i++;
|
|
};
|
|
|
|
/* cut off outdated entries */
|
|
ctx.fill = Math.min(ctx.fill, data_wanted);
|
|
|
|
for(let i = 0; i < values.length; i++) {
|
|
let len = values[i].length;
|
|
values[i] = values[i].slice(len - data_wanted, len);
|
|
|
|
/* find peaks, averages */
|
|
info.line_peak[i] = NaN;
|
|
info.line_average[i] = 0;
|
|
|
|
let nonempty = 0;
|
|
for(let j = 0; j < values[i].length; j++) {
|
|
info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]);
|
|
info.line_peak[i] = Number(info.line_peak[i].toFixed(1));
|
|
|
|
if(!isNaN(values[i][j])) {
|
|
nonempty++;
|
|
info.line_average[i] += values[i][j];
|
|
};
|
|
};
|
|
|
|
info.line_average[i] = info.line_average[i] / nonempty;
|
|
info.line_average[i] = Number(info.line_average[i].toFixed(1));
|
|
};
|
|
|
|
/* remember current timestamp, calculate horizontal scale */
|
|
if(!isNaN(last_timestamp)) {
|
|
ctx.timestamp = last_timestamp;
|
|
};
|
|
|
|
let size = Math.floor(Math.log2(info.peak));
|
|
let div = Math.pow(2, size - (size % 10));
|
|
data_scale = ctx.height / peak;
|
|
|
|
/* plot data */
|
|
for(let i = 0, di = 0; di < lines.length; di++) {
|
|
if(lines[di] == null) {
|
|
continue;
|
|
};
|
|
|
|
let el = ctx.svg.firstElementChild.getElementById(lines[di].line);
|
|
let pt = '0,' + ctx.height;
|
|
let y = 0;
|
|
|
|
if(!el) {
|
|
continue;
|
|
};
|
|
|
|
for(let j = 0; j < values[i].length; j++) {
|
|
let x = j * ctx.step;
|
|
|
|
y = ctx.height - Math.floor(values[i][j] * data_scale);
|
|
//y -= Math.floor(y % (1 / data_scale));
|
|
y = isNaN(y) ? ctx.height + 1 : y;
|
|
pt += ` ${x},${y}`;
|
|
};
|
|
|
|
pt += ` ${ctx.width},${y} ${ctx.width},${ctx.height}`;
|
|
el.setAttribute('points', pt);
|
|
|
|
i++;
|
|
};
|
|
|
|
info.label_25 = 0.25 * peak;
|
|
info.label_50 = 0.50 * peak;
|
|
info.label_75 = 0.75 * peak;
|
|
|
|
if(typeof(ctx.cb) == 'function') {
|
|
ctx.cb(ctx.svg, info);
|
|
};
|
|
};
|
|
}, this));
|
|
}, this), this.pollInterval);
|
|
},
|
|
|
|
load() {
|
|
return Promise.all([
|
|
this.loadSVG(L.resource('svg/cpu.svg')),
|
|
this.getCPUData(),
|
|
]);
|
|
},
|
|
|
|
render(data) {
|
|
let svg = data[0];
|
|
let cpus = data[1];
|
|
let map = E('div', { 'class': 'cbi-map', 'id': 'map' });
|
|
let tabs = E('div');
|
|
map.append(tabs);
|
|
|
|
for(let i of Object.values(cpus)) {
|
|
let cpu_num = i.num;
|
|
let cpu_name = (Number(i.name) >= 0 ) ? `${_('CPU')} ${i.name}` : _('Total');
|
|
let csvg = svg.cloneNode(true);
|
|
|
|
tabs.append(E('div', { 'class': 'cbi-section', 'data-tab': cpu_num, 'data-tab-title': cpu_name }, [
|
|
csvg,
|
|
E('div', { 'class': 'right' }, E('small', { 'data-graph': 'scale' }, '-')),
|
|
E('br'),
|
|
E('table', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend total' }, _('Total') + ':')),
|
|
E('td', { 'class': 'td', 'data-graph': 'total_cur' }, '-'),
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'total_avg' }, '-'),
|
|
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'total_peak' }, '-'),
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend user' }, 'User' + ':')),
|
|
E('td', { 'class': 'td', 'data-graph': 'user_cur' }, '-'),
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'user_avg' }, '-'),
|
|
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'user_peak' }, '-'),
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend sys' }, 'System' + ':')),
|
|
E('td', { 'class': 'td', 'data-graph': 'sys_cur' }, '-'),
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'sys_avg' }, '-'),
|
|
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'sys_peak' }, '-'),
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend sirq' }, 'Sirq' + ':')),
|
|
E('td', { 'class': 'td', 'data-graph': 'sirq_cur' }, '-'),
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'sirq_avg' }, '-'),
|
|
|
|
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
|
|
E('td', { 'class': 'td', 'data-graph': 'sirq_peak' }, '-'),
|
|
]),
|
|
|
|
]),
|
|
E('br'),
|
|
]));
|
|
|
|
this.updateGraph(
|
|
cpu_num,
|
|
csvg,
|
|
100,
|
|
[ { 'line': 'total_line' }, { 'line': 'user_line' }, { 'line': 'sys_line' }, { 'line': 'sirq_line' } ],
|
|
(svg, info) => {
|
|
let G = svg.firstElementChild, tab = svg.parentNode;
|
|
|
|
G.getElementById('label_25').firstChild.data = '%d %%'.format(info.label_25);
|
|
G.getElementById('label_50').firstChild.data = '%d %%'.format(info.label_50);
|
|
G.getElementById('label_75').firstChild.data = '%d %%'.format(info.label_75);
|
|
|
|
tab.querySelector('[data-graph="scale"]').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval);
|
|
|
|
dom.content(tab.querySelector('[data-graph="total_cur"]'), info.line_current[0] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="total_avg"]'), info.line_average[0] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="total_peak"]'), info.line_peak[0] + ' %');
|
|
|
|
dom.content(tab.querySelector('[data-graph="user_cur"]'), info.line_current[1] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="user_avg"]'), info.line_average[1] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="user_peak"]'), info.line_peak[1] + ' %');
|
|
|
|
dom.content(tab.querySelector('[data-graph="sys_cur"]'), info.line_current[2] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="sys_avg"]'), info.line_average[2] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="sys_peak"]'), info.line_peak[2] + ' %');
|
|
|
|
dom.content(tab.querySelector('[data-graph="sirq_cur"]'), info.line_current[3] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="sirq_avg"]'), info.line_average[3] + ' %');
|
|
dom.content(tab.querySelector('[data-graph="sirq_peak"]'), info.line_peak[3] + ' %');
|
|
}
|
|
);
|
|
};
|
|
|
|
ui.tabs.initTabGroup(tabs.childNodes);
|
|
this.pollData();
|
|
|
|
return E([], [
|
|
E('h2', _('CPU load')),
|
|
E('div', {'class': 'cbi-map-descr'},
|
|
_('This page displays the average CPU load at %d second interval.').format(this.pollInterval)),
|
|
map,
|
|
]);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave : null,
|
|
handleReset : null,
|
|
});
|