// Task 627 — standing PTY+RAM census. No aggregate concurrent-session cap
// exists in this manager and no per-device RAM-per-session measurement
// exists, so the real ceiling is speculation. This is a standing periodic
// check (independent of any spawn/exit event) that logs the live-PTY count
// against per-process RSS and system RAM/fd every 60s, so an operator can
// plot live-count against free-RAM and derive the sustainable ceiling from
// the logs alone — without reproducing load. The relevant failure mode —
// "the device is approaching its RAM ceiling at the current live-PTY count"
// — emits no event and does not reproduce on demand, so it must be detected
// by an independent periodic audit, not a log hung on a spawn.
//
// Shape mirrors the standing-audit modules (public-agent-reachability.ts):
// a PURE formatter (no I/O, unit-tested) + an impure collector (reads the
// live tracker map and Linux /proc) + a start/stop factory whose setInterval
// is unref'd and cleared on shutdown.
import { readFileSync } from 'node:fs';
import { RC_DAEMON_CAPACITY } from './rc-daemon.js';
import { livePtyCount, forEachLivePty } from './pty-spawner.js';
const TAG = '[pty-census]';
const kbToMb = (kb) => Math.round(kb / 1024);
/** Pure renderer — no I/O. One structured line from an injected snapshot. */
export function formatPtyCensus(s) {
    const rssSumKb = s.rssListKb.reduce((a, b) => a + b, 0);
    const rssMaxKb = s.rssListKb.length > 0 ? Math.max(...s.rssListKb) : 0;
    const sysUsedPct = s.sysTotalKb > 0 ? Math.round(((s.sysTotalKb - s.sysAvailKb) / s.sysTotalKb) * 100) : 0;
    return (`${TAG} live=${s.live} capacity=${s.capacity} ` +
        `rssSumMb=${kbToMb(rssSumKb)} rssMaxMb=${kbToMb(rssMaxKb)} goneCount=${s.goneCount} ` +
        `sysTotalMb=${kbToMb(s.sysTotalKb)} sysAvailMb=${kbToMb(s.sysAvailKb)} sysUsedPct=${sysUsedPct} ` +
        `fdOpen=${s.fdOpen} fdMax=${s.fdMax}`);
}
/** Parse a `VmRSS:` line (kB) from /proc/<pid>/status. Returns null when the
 *  entry is gone or unreadable — the caller counts that pid as gone. */
function readVmRssKb(pid) {
    try {
        const status = readFileSync(`/proc/${pid}/status`, 'utf8');
        const m = status.match(/^VmRSS:\s+(\d+)\s+kB$/m);
        return m ? Number(m[1]) : null;
    }
    catch {
        return null;
    }
}
/** MemTotal / MemAvailable (kB) from /proc/meminfo. Missing field → 0. */
function readMeminfoKb() {
    try {
        const info = readFileSync('/proc/meminfo', 'utf8');
        const total = info.match(/^MemTotal:\s+(\d+)\s+kB$/m);
        const avail = info.match(/^MemAvailable:\s+(\d+)\s+kB$/m);
        return { sysTotalKb: total ? Number(total[1]) : 0, sysAvailKb: avail ? Number(avail[1]) : 0 };
    }
    catch {
        return { sysTotalKb: 0, sysAvailKb: 0 };
    }
}
/** allocated + max file handles from /proc/sys/fs/file-nr ("alloc unused max"). */
function readFileNr() {
    try {
        const parts = readFileSync('/proc/sys/fs/file-nr', 'utf8').trim().split(/\s+/);
        return { fdOpen: Number(parts[0] ?? 0), fdMax: Number(parts[2] ?? 0) };
    }
    catch {
        return { fdOpen: 0, fdMax: 0 };
    }
}
/** Impure — gathers a snapshot from the live tracker map and /proc. A pid
 *  whose /proc entry is gone contributes 0 to RSS and increments goneCount
 *  (never silently dropped). On a non-Linux host (no /proc) the memory and
 *  fd fields read 0; the pure formatter is the unit-tested surface and the
 *  collector is exercised on the Pi. */
export function collectPtyCensus() {
    const rssListKb = [];
    let goneCount = 0;
    forEachLivePty((t) => {
        const rss = readVmRssKb(t.pid);
        if (rss === null)
            goneCount++;
        else
            rssListKb.push(rss);
    });
    const { sysTotalKb, sysAvailKb } = readMeminfoKb();
    const { fdOpen, fdMax } = readFileNr();
    return {
        live: livePtyCount(),
        capacity: RC_DAEMON_CAPACITY,
        rssListKb,
        goneCount,
        sysTotalKb,
        sysAvailKb,
        fdOpen,
        fdMax,
    };
}
export function createPtyCensus(deps) {
    let timer = null;
    function tickOnce() {
        const snap = collectPtyCensus();
        deps.logger(formatPtyCensus(snap));
        return snap;
    }
    return {
        tickOnce,
        start() {
            if (timer)
                return;
            timer = setInterval(() => {
                try {
                    tickOnce();
                }
                catch (err) {
                    deps.logger(`${TAG} op=census-tick-failed message=${err instanceof Error ? err.message : String(err)}`);
                }
            }, deps.intervalMs);
            if (typeof timer.unref === 'function')
                timer.unref();
            deps.logger(`${TAG} op=census-started intervalMs=${deps.intervalMs} capacity=${RC_DAEMON_CAPACITY}`);
        },
        stop() {
            if (timer) {
                clearInterval(timer);
                timer = null;
            }
        },
    };
}
//# sourceMappingURL=pty-census.js.map