mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 04:03:56 +08:00
144 lines
4.0 KiB
Plaintext
144 lines
4.0 KiB
Plaintext
'use strict';
|
|
|
|
import { readfile } from 'fs';
|
|
import * as uloop from 'uloop';
|
|
import { connect } from 'ubus';
|
|
|
|
const STEP = 5000, SLOTS = 60;
|
|
|
|
let ring = [], seq = 0, timer, selectedDevice;
|
|
const bus = connect();
|
|
|
|
// Observe one main-table default-route device. Prefer IPv4, then route metric;
|
|
// keep the existing device on equal priority to avoid polling-order switches.
|
|
function sources(interfaces, previous) {
|
|
let best = null;
|
|
for (let iface in interfaces) {
|
|
if (!iface.up)
|
|
continue;
|
|
let name = iface.device;
|
|
if (!name || name == 'lo' || substr(name, 0, 1) == '@')
|
|
name = iface.l3_device;
|
|
if (!name || name == 'lo' || substr(name, 0, 1) == '@')
|
|
continue;
|
|
|
|
for (let route in iface.route ?? []) {
|
|
if (+route.mask != 0)
|
|
continue;
|
|
const family = route.target == '0.0.0.0' ? 4 : route.target == '::' ? 6 : 0;
|
|
if (!family)
|
|
continue;
|
|
// netifd omits route.table when it is inherited from the interface.
|
|
const table = route.table ?? (family == 4 ? iface.ip4table : iface.ip6table);
|
|
if (table && table != 254)
|
|
continue;
|
|
const metric = +(route.metric ?? iface.metric ?? 0);
|
|
if (!best || family < best.family ||
|
|
(family == best.family && (metric < best.metric ||
|
|
(metric == best.metric && (name == previous ||
|
|
(best.device != previous && name < best.device))))))
|
|
best = { device: name, family, metric };
|
|
}
|
|
}
|
|
|
|
// Use the configured carrier (including a VLAN) without walking to its
|
|
// physical parent, which may also carry LAN or IPTV traffic.
|
|
return best ? [ best.device ] : [];
|
|
}
|
|
|
|
function traffic_boundary() {
|
|
const dump = bus.call('network.interface', 'dump');
|
|
if (type(dump?.interface) != 'array')
|
|
return null;
|
|
const devices = sources(dump.interface, selectedDevice);
|
|
selectedDevice = devices[0];
|
|
return { devices };
|
|
}
|
|
|
|
function now() {
|
|
const c = clock(true);
|
|
|
|
return c[0] * 1000 + int(c[1] / 1000000);
|
|
}
|
|
|
|
// Tells the instances apart, as the sequence starts over with rpcd.
|
|
const id = trim(readfile('/proc/sys/kernel/random/uuid'));
|
|
|
|
// Counters as the kernel has them. Whoever draws them takes the differences,
|
|
// over the time between two samples on the clock they were read by.
|
|
function sample() {
|
|
const cpus = {}, net = {};
|
|
let boundary = null;
|
|
try { boundary = traffic_boundary(); }
|
|
catch (e) { warn(`luci.dashboard: traffic discovery: ${e}\n`); }
|
|
|
|
for (let line in split(readfile('/proc/stat') ?? '', '\n')) {
|
|
if (substr(line, 0, 3) != 'cpu')
|
|
break;
|
|
|
|
const fields = split(line, /\s+/);
|
|
|
|
cpus[fields[0]] = map(slice(fields, 1, 9), v => +v);
|
|
}
|
|
|
|
const mem = match(readfile('/proc/meminfo', 256) ?? '', /MemAvailable:\s+(\d+)/);
|
|
|
|
// Only the selected WAN consumes network history. Check its identity
|
|
// on both sides of the counter read to detect device recreation.
|
|
const name = boundary?.devices?.[0];
|
|
const before = name ? int(readfile(`/sys/class/net/${name}/ifindex`)) : null;
|
|
const t = now();
|
|
|
|
if (name) {
|
|
for (let line in slice(split(readfile('/proc/net/dev') ?? '', '\n'), 2)) {
|
|
const fields = split(trim(line), /[: ]+/);
|
|
if (length(fields) <= 9 || fields[0] != name)
|
|
continue;
|
|
|
|
const after = int(readfile(`/sys/class/net/${name}/ifindex`));
|
|
net[name] = [ +fields[1], +fields[9],
|
|
after > 0 && after == before ? after : null ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
push(ring, [ ++seq, t, cpus, mem ? +mem[1] * 1024 : null, net, boundary ]);
|
|
|
|
while (length(ring) > SLOTS)
|
|
shift(ring);
|
|
}
|
|
|
|
// Sampling never stops, so the charts are full whenever somebody looks.
|
|
function tick() {
|
|
timer.set(STEP);
|
|
|
|
// An exception here would end rpcd's uloop, not just this timer.
|
|
try {
|
|
sample();
|
|
}
|
|
catch (e) {
|
|
warn(`luci.dashboard: ${e}\n`);
|
|
}
|
|
}
|
|
|
|
timer = uloop.timer(0, tick);
|
|
|
|
const methods = {
|
|
history: {
|
|
args: { since: 0, id: 'id' },
|
|
call: function(request) {
|
|
const since = (request.args?.id == id) ? +(request.args?.since ?? 0) : 0;
|
|
|
|
return {
|
|
id,
|
|
now: now(),
|
|
step: STEP,
|
|
slots: SLOTS,
|
|
samples: filter(ring, entry => entry[0] > since)
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
return { 'luci.dashboard': methods };
|