feat(host): /api/host CPU/mem/disk/net from /proc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-02 22:34:04 +10:00
parent 03803f39f0
commit 3492b24dac
4 changed files with 71 additions and 0 deletions

42
lib/host/resources.js Normal file
View File

@@ -0,0 +1,42 @@
// lib/host/resources.js — reads CT 311's own /proc + statfs. Node 22 fs.statfs.
import { readFile } from 'node:fs/promises';
import { statfs } from 'node:fs/promises';
async function cpuSample() {
const line = (await readFile('/proc/stat', 'utf8')).split('\n')[0]; // "cpu u n s i ..."
const v = line.trim().split(/\s+/).slice(1).map(Number);
const idle = v[3] + (v[4] || 0);
const total = v.reduce((a, b) => a + b, 0);
return { idle, total };
}
export async function snapshot() {
// CPU%: two samples ~100ms apart.
const a = await cpuSample();
await new Promise(r => setTimeout(r, 100));
const b = await cpuSample();
const dTotal = b.total - a.total, dIdle = b.idle - a.idle;
const cpu_pct = dTotal > 0 ? Math.round((1 - dIdle / dTotal) * 100) : 0;
// Memory from /proc/meminfo (kB).
const mem = Object.fromEntries(
(await readFile('/proc/meminfo', 'utf8')).split('\n').filter(Boolean).map(l => {
const [k, val] = l.split(':'); return [k.trim(), parseInt(val) * 1024];
})
);
const total = mem.MemTotal, avail = mem.MemAvailable ?? mem.MemFree;
const memOut = { total, used: total - avail, pct: Math.round((1 - avail / total) * 100) };
// Disk for / via statfs.
const fs = await statfs('/');
const dTotalB = fs.blocks * fs.bsize, dFree = fs.bavail * fs.bsize;
const disk = { total: dTotalB, free: dFree, pct: Math.round((1 - dFree / dTotalB) * 100) };
// Net totals from /proc/net/dev (sum non-lo interfaces).
let rx = 0, tx = 0;
for (const l of (await readFile('/proc/net/dev', 'utf8')).split('\n')) {
const m = l.match(/^\s*([^:]+):\s+(\d+)(?:\s+\d+){7}\s+(\d+)/);
if (m && m[1].trim() !== 'lo') { rx += Number(m[2]); tx += Number(m[3]); }
}
return { cpu_pct, mem: memOut, disk, net: { rx_bytes: rx, tx_bytes: tx }, at: Date.now() };
}