Zwei Symbole zwischen Bluetooth-Tab und Hell/Dunkel-Schalter: ein Ring mit der Plattenbelegung in Prozent und eine Turbine, deren Drehzahl der CPU-Last folgt. Beide klappen bei Mauskontakt eine Karte mit Zahlen auf (belegt/frei/ gesamt bzw. CPU, RAM, Temperatur). Neues Modul sysstat.js liest alles aus /proc bzw. fs.statfsSync, damit die GUI im Sekundentakt fragen kann, ohne df/top-Prozesse zu starten. CPU-% aus zwei /proc/stat-Messpunkten mit eigenem 2-s-Ticker, RAM ueber MemAvailable, Temperatur als waermste SoC-/Core-Zone. Doku: Kapitel 07 + Changelog.
106 lines
4.0 KiB
JavaScript
106 lines
4.0 KiB
JavaScript
// Systemwerte für die Kopfzeile: Plattenbelegung, CPU-Last, RAM, Temperatur.
|
|
// Bewusst ohne Shell-Aufrufe (kein df/top) — alles aus /proc bzw. statfs, damit
|
|
// die GUI im Sekundentakt fragen darf, ohne Prozesse zu starten.
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const DISK_PATH = path.join(__dirname, 'recordings');
|
|
|
|
// ---- CPU ----------------------------------------------------------------
|
|
// Auslastung braucht zwei Messpunkte. Ein eigener Ticker hält den Wert bereit,
|
|
// sonst wäre die erste Antwort nach jedem Start immer 0 %.
|
|
let prevCpu = null;
|
|
let cpuPct = 0;
|
|
|
|
function sampleCpu() {
|
|
try {
|
|
const line = fs.readFileSync('/proc/stat', 'utf8').split('\n', 1)[0];
|
|
const v = line.trim().split(/\s+/).slice(1).map(Number).filter(n => Number.isFinite(n));
|
|
if (v.length < 5) return;
|
|
const idle = v[3] + v[4]; // idle + iowait
|
|
const total = v.reduce((a, b) => a + b, 0);
|
|
if (prevCpu) {
|
|
const dTotal = total - prevCpu.total;
|
|
const dIdle = idle - prevCpu.idle;
|
|
if (dTotal > 0) cpuPct = Math.max(0, Math.min(100, (1 - dIdle / dTotal) * 100));
|
|
}
|
|
prevCpu = { total, idle };
|
|
} catch { /* ohne /proc bleibt der letzte Wert stehen */ }
|
|
}
|
|
sampleCpu();
|
|
setInterval(sampleCpu, 2000).unref();
|
|
|
|
// ---- Temperatur ---------------------------------------------------------
|
|
// Interessant sind die SoC-/Kern-Zonen; WLAN-Karte & Co. bleiben draußen, sonst
|
|
// zeigt die Anzeige eine Temperatur, die nichts mit der Rechenlast zu tun hat.
|
|
const TEMP_ZONES = (() => {
|
|
try {
|
|
return fs.readdirSync('/sys/class/thermal')
|
|
.filter(d => /^thermal_zone\d+$/.test(d))
|
|
.map(d => {
|
|
const dir = `/sys/class/thermal/${d}`;
|
|
let type = d;
|
|
try { type = fs.readFileSync(`${dir}/type`, 'utf8').trim(); } catch { }
|
|
return { type, file: `${dir}/temp` };
|
|
})
|
|
.filter(z => /soc|core|center|npu|gpu/i.test(z.type));
|
|
} catch { return []; }
|
|
})();
|
|
|
|
function readTemp() {
|
|
let best = null;
|
|
for (const z of TEMP_ZONES) {
|
|
try {
|
|
const c = Number(fs.readFileSync(z.file, 'utf8').trim()) / 1000;
|
|
if (Number.isFinite(c) && (!best || c > best.c)) best = { c, zone: z.type };
|
|
} catch { }
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// ---- RAM ----------------------------------------------------------------
|
|
// MemAvailable statt MemFree: Cache zählt nicht als belegt, sonst stünde das
|
|
// Board dauernd auf 95 %.
|
|
function readMem() {
|
|
try {
|
|
const txt = fs.readFileSync('/proc/meminfo', 'utf8');
|
|
const kb = k => {
|
|
const m = txt.match(new RegExp(`^${k}:\\s+(\\d+) kB`, 'm'));
|
|
return m ? Number(m[1]) * 1024 : null;
|
|
};
|
|
const total = kb('MemTotal');
|
|
const avail = kb('MemAvailable') ?? kb('MemFree');
|
|
if (!total || avail == null) return null;
|
|
return { totalBytes: total, freeBytes: avail, usedBytes: total - avail, usedPct: (1 - avail / total) * 100 };
|
|
} catch { return null; }
|
|
}
|
|
|
|
// ---- Platte -------------------------------------------------------------
|
|
// Gemessen wird die Partition, auf der die Aufnahmen liegen — das ist der
|
|
// Platz, der der App tatsächlich ausgeht. Die Prozentzahl folgt df: der für
|
|
// root reservierte Anteil zählt als belegt.
|
|
function readDisk() {
|
|
try {
|
|
const st = fs.statfsSync(fs.existsSync(DISK_PATH) ? DISK_PATH : __dirname);
|
|
const bs = st.bsize;
|
|
const totalBytes = st.blocks * bs;
|
|
const freeBytes = st.bavail * bs;
|
|
const usedBytes = (st.blocks - st.bfree) * bs;
|
|
const denom = usedBytes + freeBytes;
|
|
return { totalBytes, freeBytes, usedBytes, usedPct: denom > 0 ? (usedBytes / denom) * 100 : 0 };
|
|
} catch { return null; }
|
|
}
|
|
|
|
export function status() {
|
|
const t = readTemp();
|
|
return {
|
|
cpu: { pct: Math.round(cpuPct * 10) / 10, cores: os.cpus().length, load1: Math.round(os.loadavg()[0] * 100) / 100 },
|
|
ram: readMem(),
|
|
disk: readDisk(),
|
|
temp: t ? { c: Math.round(t.c * 10) / 10, zone: t.zone } : null
|
|
};
|
|
}
|