import { execSync } from "node:child_process"; import { diskUsageMounts } from "./schema.ts"; const DF_CMD = "df -B1 --output=source,target,fstype,size,used,avail,pcent"; /** fstype-based exclusions to avoid pseudo / volatile filesystem noise */ const EXCLUDED_FSTYPES = new Set([ "tmpfs", "devtmpfs", "proc", "sysfs", "cgroup2", "cgroup", ]); function round2(n) { return Math.round(n * 100) / 100; } function parseUIntField(s) { if (s === "-") return null; const n = Number.parseInt(String(s), 10); if (!Number.isFinite(n) || n < 0) return null; return n; } function parsePcent(tok) { if (!tok || typeof tok !== "string") return null; const t = tok.trim(); if (!/^[\d.]+%$/.test(t)) return null; const raw = Number.parseFloat(t.replace("%", "")); return Number.isFinite(raw) ? round2(raw) : null; } /** * Parse one `df --output=source,target,fstype,size,used,avail,pcent` data line. * Mount may contain spaces; last five logical columns are fixed. */ function parseDfLine(line) { const parts = line.trim().split(/\s+/); if (parts.length < 7) return null; const pcentTok = parts[parts.length - 1]; const availBytes = parseUIntField(parts[parts.length - 2]); const usedBytes = parseUIntField(parts[parts.length - 3]); const totalBytes = parseUIntField(parts[parts.length - 4]); const fstype = parts[parts.length - 5]; const device = parts[0]; const mount = parts.slice(1, parts.length - 5).join(" "); if (!device || !mount || !fstype) return null; if (totalBytes === null || usedBytes === null || availBytes === null) return null; const pcentFromDf = parsePcent(pcentTok); const computed = totalBytes > 0 ? round2((usedBytes / totalBytes) * 100) : 0; let usedPercent = computed; if (pcentFromDf !== null) { const diff = Math.abs(computed - pcentFromDf); usedPercent = diff > 1 ? computed : pcentFromDf; } return { device, mount, fstype, totalBytes, usedBytes, availBytes, usedPercent, }; } function parseDfOutput(text) { const rows = []; const lines = text.split("\n"); for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; if (/^Filesystem\s+/.test(trimmed) || trimmed.startsWith("Filesystem")) continue; const row = parseDfLine(line); if (row && !EXCLUDED_FSTYPES.has(row.fstype)) rows.push(row); } return rows; } export async function compute(db, _peers) { const ts = Date.now(); let mounts = []; try { const out = execSync(DF_CMD, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, }); mounts = parseDfOutput(out); } catch { mounts = []; } if (mounts.length > 0) { await db.insert(diskUsageMounts).values( mounts.map((m) => ({ ts, device: m.device, mount: m.mount, fstype: m.fstype, totalBytes: m.totalBytes, usedBytes: m.usedBytes, availBytes: m.availBytes, usedPercent: m.usedPercent, })), ); } return { ts, mounts: mounts.map((m) => ({ device: m.device, mount: m.mount, fstype: m.fstype, totalBytes: m.totalBytes, usedBytes: m.usedBytes, availBytes: m.availBytes, usedPercent: m.usedPercent, })), }; }