Document the PHP api layer and fix what documenting it exposed

Writing down what each endpoint actually guarantees made the places it
didn't obvious — shell arguments reaching a crontab or a bash -c
unescaped, master.conf written without tmp+rename, and conf edits that
could be saved without ever being parsed.
This commit is contained in:
Gmer4Lfe
2026-08-02 10:11:39 -04:00
parent 6a959fb5e4
commit 987313e7dc
55 changed files with 3972 additions and 95 deletions
+71
View File
@@ -1,4 +1,75 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// High-frequency monitor poll. CPU, memory and network only, cheap enough to fetch every
// second — the live-updating subset of what monitor.php returns in full.
//
// OPERATIONAL MODEL
// Split from monitor.php on refresh rate. The fields here move second to second and are
// readable from /proc alone. Everything that needs `docker stats`, a GraphQL call, or an
// SSH round trip stays in the full payload, because those cannot be sampled at this rate.
//
// The expensive memory breakdown is borrowed rather than recomputed. Docker and VM memory,
// top processes and swap come from the last full cache — a 600s window, wide enough that
// the card stays populated even when the background writer falls behind. Stale is the right
// trade here: per-container memory does not move meaningfully between seconds, and paying
// for it would defeat the point of this endpoint.
//
// DESIGN PRINCIPLES
// Reads /proc directly, no shell.
// /proc/meminfo and the ZFS arcstats file are parsed inline. At this poll rate a single
// fork per request would dominate the cost of the endpoint.
//
// Shares the CPU baseline with everything else.
// vv_cpu_per_core() keeps its counters in a shared state file, so this endpoint, the
// header snapshot, and the full monitor payload all report the same number rather than
// three independent samples that visibly disagree.
//
// Free means available, not unused.
// MemAvailable, not MemFree — reclaimable page cache is not memory pressure, and
// reporting MemFree would show a healthy machine as nearly full.
//
// ZFS ARC is broken out of system memory.
// On a host with ZFS cache pools the ARC is most of the "used" figure and is fully
// reclaimable. Folding it into system memory would make every reading alarming and
// none of them actionable.
//
// OPERATIONAL SAFEGUARDS
// Read-only.
//
// Every source read has a fallback.
// file() with a ?: [] fallback, @file() for the arcstats path that does not exist on a
// host without ZFS, and ?? 0 on every extracted key. A missing subsystem contributes
// zero rather than a warning or a fatal — and at one request per second, a fatal here
// would be a page that never stops erroring.
//
// The borrowed fields degrade to zero independently.
// Each ?? default is applied per field, so an absent or expired full cache costs those
// four values and leaves CPU, memory and network — the reason to call this endpoint —
// intact.
//
// Swap has its own fallback path.
// When the cached figures are absent, swap is recomputed from /proc/meminfo rather than
// reported as zero, because zero swap used and unknown swap used look identical in the
// UI and mean very different things.
//
// Derived values are clamped.
// max(0, …) on used and system memory, so the subtraction cannot go negative when the
// cached docker and VM figures were sampled against a different total.
//
// REQUEST
// GET, no parameters
//
// RESPONSE
// {"cpu":{…},"mem":{total_kb,free_kb,used_kb,arc_kb,docker_kb,vm_kb,system_kb,
// swap_total_kb,swap_used_kb,top_procs},"net":{…},"ts":epoch}
//
// DEPENDS ON
// include/config.php vv_cache_read()
// include/common.php vv_cpu_per_core(), vv_network_stats()
// /proc/meminfo, /proc/spl/kstat/zfs/arcstats
// monitor cache written by Tools/api_cache_writer.sh — source of the borrowed fields
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';
require_once dirname(__DIR__) . '/include/common.php';