mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 13:23:56 +08:00
🛸 Sync 2026-09-22 19:25:09
This commit is contained in:
1 parent
8197f31b41
commit
402920f93d
19 files changed
+1022
-139
No files matched your search
+205
@@ -0,0 +1,205 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require view.dashboard.lib.charts as charts';
|
||||
'require view.dashboard.lib.history as history';
|
||||
'require view.dashboard.lib.system as system';
|
||||
|
||||
// The first eight fields of a /proc/stat cpu line. Guest time is already
|
||||
// part of user and nice, so it must not be added again.
|
||||
const CPUS = 2, MEMORY = 3;
|
||||
const USER = 0, NICE = 1, SYSTEM = 2, IDLE = 3, IOWAIT = 4, IRQ = 5, SOFTIRQ = 6, STEAL = 7;
|
||||
|
||||
// Shares of the time between two samples, in percent. Both are in jiffies,
|
||||
// so no clock is involved. Waiting for I/O is idle time. The iowait counter
|
||||
// may step back, hence the clamp.
|
||||
function usage(prev, cur) {
|
||||
const d = cur.map((value, i) => Math.max(0, value - (prev[i] || 0)));
|
||||
const total = d.reduce((sum, value) => sum + value, 0);
|
||||
|
||||
if (total <= 0)
|
||||
return null;
|
||||
|
||||
const share = value => 100 * value / total;
|
||||
|
||||
return {
|
||||
busy: share(total - d[IDLE] - d[IOWAIT]),
|
||||
user: share(d[USER] + d[NICE]),
|
||||
system: share(d[SYSTEM]),
|
||||
iowait: share(d[IOWAIT]),
|
||||
irq: share(d[IRQ]),
|
||||
softirq: share(d[SOFTIRQ]),
|
||||
steal: share(d[STEAL])
|
||||
};
|
||||
}
|
||||
|
||||
function progressbar(percent, text) {
|
||||
return E('div', { 'class': 'cbi-progressbar', 'title': text }, [
|
||||
E('div', { 'style': 'width:%.2f%%'.format(Math.max(0, Math.min(100, percent))) })
|
||||
]);
|
||||
}
|
||||
|
||||
return baseclass.extend({
|
||||
widgets: [
|
||||
{ id: 'cpu', slot: 'cards', title: _('CPU usage'), order: 20 },
|
||||
{ id: 'memory', slot: 'cards', title: _('Memory'), order: 30 },
|
||||
{ id: 'load', slot: 'cards', title: _('Load average'), order: 80, hidden: true },
|
||||
{ id: 'system', slot: 'charts', title: _('System load'), order: 20 },
|
||||
{ id: 'resources', slot: 'tabs', title: _('Resources'), order: 30 }
|
||||
],
|
||||
|
||||
load() {
|
||||
return Promise.all([ system.info(), history.load() ]);
|
||||
},
|
||||
|
||||
// Per-CPU usage over the latest interval, keyed by the kernel's names so
|
||||
// that a CPU going offline does not shift the others.
|
||||
cpu() {
|
||||
const pair = history.pair();
|
||||
const result = {};
|
||||
|
||||
if (pair == null)
|
||||
return null;
|
||||
|
||||
for (const name in pair[1][CPUS])
|
||||
if (pair[0][CPUS][name] != null)
|
||||
result[name] = usage(pair[0][CPUS][name], pair[1][CPUS][name]);
|
||||
|
||||
return (result.cpu != null) ? result : null;
|
||||
},
|
||||
|
||||
memory(info) {
|
||||
const mem = L.isObject(info.memory) ? info.memory : {};
|
||||
|
||||
if (!mem.total)
|
||||
return null;
|
||||
|
||||
// What the kernel reckons can be had without swapping. Counting
|
||||
// everything but "free" as used would include the page cache.
|
||||
const available = (mem.available != null) ? mem.available : (mem.free || 0) + (mem.buffered || 0) + (mem.cached || 0);
|
||||
const used = Math.max(0, mem.total - available);
|
||||
|
||||
return { total: mem.total, used: used, percent: 100 * used / mem.total, buffered: mem.buffered, cached: mem.cached };
|
||||
},
|
||||
|
||||
renderCpuCard(cpu, cores, load) {
|
||||
return charts.kpi({
|
||||
icon: 'cpu',
|
||||
title: _('CPU usage'),
|
||||
value: [ cpu ? '%d%%'.format(Math.round(cpu.cpu.busy)) : '-' ],
|
||||
sub: cpu ? [ N_(cores, '%d core', '%d cores').format(cores), load ? _('load %.2f').format(load[0]) : null ]
|
||||
: [ E('em', {}, [ history.status() ]) ]
|
||||
});
|
||||
},
|
||||
|
||||
renderMemoryCard(mem) {
|
||||
return charts.kpi({
|
||||
icon: 'memory',
|
||||
title: _('Memory'),
|
||||
value: [ mem ? '%d%%'.format(Math.round(mem.percent)) : '-' ],
|
||||
sub: [ mem ? '%1024.1mB / %1024.1mB'.format(mem.used, mem.total) : '' ]
|
||||
});
|
||||
},
|
||||
|
||||
renderLoadCard(load) {
|
||||
return charts.kpi({
|
||||
icon: 'load',
|
||||
title: _('Load average'),
|
||||
value: [ load ? '%.2f'.format(load[0]) : '-' ],
|
||||
sub: load ? [ _('5 min: %.2f').format(load[1]), _('15 min: %.2f').format(load[2]) ] : []
|
||||
});
|
||||
},
|
||||
|
||||
renderChart(cores, mem) {
|
||||
const busy = history.series((cur, prev) => {
|
||||
const share = (prev != null && prev[CPUS].cpu != null && cur[CPUS].cpu != null) ? usage(prev[CPUS].cpu, cur[CPUS].cpu) : null;
|
||||
|
||||
return share ? share.busy : null;
|
||||
});
|
||||
const used = history.series(cur => (mem != null && cur[MEMORY] != null) ? 100 * (1 - cur[MEMORY] / mem.total) : null);
|
||||
const last = points => points.length ? points[points.length - 1].v : null;
|
||||
const percent = value => (value != null) ? '%d%%'.format(Math.round(value)) : '-';
|
||||
const desc = [];
|
||||
|
||||
if (cores)
|
||||
desc.push(N_(cores, '%d core', '%d cores').format(cores));
|
||||
|
||||
if (mem)
|
||||
desc.push('%1024.0mB'.format(mem.total));
|
||||
|
||||
return charts.card({
|
||||
title: _('System load'),
|
||||
desc: desc.join(' · '),
|
||||
body: busy.length ? [
|
||||
charts.lines({
|
||||
span: history.span(),
|
||||
max: 100,
|
||||
ticks: [ 0, 25, 50, 75, 100 ].map(value => ({ value: value, label: '%d%%'.format(value) })),
|
||||
ariaLabel: _('System load'),
|
||||
series: [ { values: busy, area: true }, { values: used } ]
|
||||
}),
|
||||
charts.legend([
|
||||
{ className: 'dashboard-series-1', label: _('CPU'), value: percent(last(busy)) },
|
||||
{ className: 'dashboard-series-2', label: _('Memory'), value: percent(last(used)) }
|
||||
])
|
||||
] : charts.empty(history.status())
|
||||
});
|
||||
},
|
||||
|
||||
renderTab(cpu, mem, load, swap) {
|
||||
const rows = [];
|
||||
const pending = () => E('em', {}, [ history.status() ]);
|
||||
const bytes = (value, total) => progressbar(100 * value / total, '%1024.1mB / %1024.1mB (%d%%)'.format(value, total, Math.round(100 * value / total)));
|
||||
|
||||
if (load)
|
||||
rows.push([ _('Load average'), '%.2f, %.2f, %.2f'.format(load[0], load[1], load[2]) ]);
|
||||
|
||||
const core = (title, share) => [
|
||||
E('span', { 'data-tooltip': 'user %.1f · system %.1f · iowait %.1f · irq %.1f · softirq %.1f · steal %.1f'.format(
|
||||
share.user, share.system, share.iowait, share.irq, share.softirq, share.steal) }, [ title ]),
|
||||
progressbar(share.busy, '%.1f%%'.format(share.busy))
|
||||
];
|
||||
|
||||
rows.push(cpu ? core(_('CPU usage'), cpu.cpu) : [ _('CPU usage'), pending() ]);
|
||||
|
||||
if (cpu && Object.keys(cpu).length > 2)
|
||||
Object.keys(cpu).filter(name => name != 'cpu' && cpu[name] != null).forEach(name => {
|
||||
rows.push(core(name.replace(/^cpu/, _('CPU') + ' '), cpu[name]));
|
||||
});
|
||||
|
||||
if (mem) {
|
||||
rows.push([ _('Memory used'), bytes(mem.used, mem.total) ]);
|
||||
|
||||
if (mem.buffered)
|
||||
rows.push([ _('Buffered'), bytes(mem.buffered, mem.total) ]);
|
||||
|
||||
if (mem.cached)
|
||||
rows.push([ _('Cached'), bytes(mem.cached, mem.total) ]);
|
||||
}
|
||||
|
||||
if (swap && swap.total > 0)
|
||||
rows.push([ _('Swap used'), bytes(swap.total - swap.free, swap.total) ]);
|
||||
|
||||
return E('table', { 'class': 'table' }, rows.map(row => E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left', 'width': '33%' }, [ row[0] ]),
|
||||
E('td', { 'class': 'td left' }, [ row[1] ])
|
||||
])));
|
||||
},
|
||||
|
||||
render([info]) {
|
||||
const cpu = this.cpu();
|
||||
const latest = history.samples[history.samples.length - 1];
|
||||
const cores = latest ? Math.max(1, Object.keys(latest[CPUS]).length - 1) : 0;
|
||||
const mem = this.memory(info);
|
||||
const load = Array.isArray(info.load) ? info.load.map(value => value / 65536) : null;
|
||||
|
||||
return {
|
||||
cards: [
|
||||
{ id: 'cpu', node: () => this.renderCpuCard(cpu, cores, load) },
|
||||
{ id: 'memory', node: () => this.renderMemoryCard(mem) },
|
||||
{ id: 'load', node: () => this.renderLoadCard(load) }
|
||||
],
|
||||
charts: [ { id: 'system', node: () => this.renderChart(cores, mem) } ],
|
||||
tabs: [ { id: 'resources', title: _('Resources'), content: () => this.renderTab(cpu, mem, load, info.swap) } ]
|
||||
};
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user