Files
Varaverk/Plugin/unraid/Tools/api_cache_writer.php
T
Gmer4Lfe 96b907acbc Say what Varaverk itself is using on the System card
The card described the machine and said nothing about the thing whose dashboard
it is. Cache size matters most: VV_CACHE_ROOT is under /tmp, which on Unraid is
RAM, so it is shown against the rootfs percentage it counts against. Jobs are
locks with a live process — four lock files were sitting there and one job was
actually running.
2026-08-21 09:39:52 -04:00

160 lines
8.4 KiB
PHP

<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Background cache writer. Builds the full monitor payload and the arrs payload once a
// minute and writes them to the cache, so page loads serve from a file instead of paying
// for collection.
//
// OPERATIONAL MODEL
// This is what makes the monitor and arrs tabs fast. api/monitor.php and api/arrs.php read
// the files this process writes and only fall back to collecting for themselves on a miss.
// The expensive work — GraphQL, docker stats, SSH to partners, HTTP to every arr instance —
// happens here, on a schedule, off the request path.
//
// Runs from cron every minute via api_cache_writer.sh, a bash wrapper the scheduler
// requires. Not reachable over HTTP, and refuses to run if it ever is.
//
// The payload assembled here is deliberately identical to api/monitor.php's. The two are
// maintained together: a field added there and not here is a field that is only ever served
// on a cache miss.
//
// DESIGN PRINCIPLES
// One API round trip for the whole payload.
// vv_api_data() is called once up front and static-cached for the life of the process,
// so the API-first collectors below share a single GraphQL query rather than issuing
// one each.
//
// Writes two caches, not one.
// monitor and arrs have different consumers and different costs, so they are written
// under separate keys and either can be served while the other is stale.
//
// Every payload carries its own timestamp, so consumers can render age rather than
// presenting minute-old numbers as current.
//
// Reports its own duration on stdout.
// The elapsed time goes to the job log, which is the only place a slow collection cycle
// becomes visible — this process has no other output and no failure anyone would see.
//
// OPERATIONAL SAFEGUARDS
// Refuses to run under a web server.
// PHP_SAPI is checked first and a non-CLI invocation is answered with a 404 and no
// output. Otherwise a browser hitting this path would trigger the full expensive
// collection — including SSH to every partner — outside any cache or rate limit, once
// per request.
//
// Read-only with respect to the system. Every collector observes; none start, stop, or
// change anything. The only writes are the two cache files.
//
// Cache writes are atomic.
// vv_cache_write() writes a .tmp and renames, so a page load landing mid-write reads
// the previous complete payload rather than a truncated one.
//
// Every collector degrades to empty rather than fatal.
// The library suppresses its filesystem reads and redirects stderr on every shell call,
// so absent hardware yields an empty section. On a payload this wide that property is
// what keeps one missing subsystem from failing the whole cycle and leaving both caches
// to expire.
//
// A failed cycle is survivable by design.
// Nothing here clears the previous cache before building the new one. A run that dies
// partway leaves the last good payload in place, and the readers' own age windows —
// 300s for monitor, 300s for arrs — are several cycles wide, so a single missed minute
// is invisible.
//
// OUTPUT
// VV_CACHE_DIR/monitor.json consumed by api/monitor.php
// VV_CACHE_DIR/arrs.json consumed by api/arrs.php
// stdout one timing line, captured into the job log
//
// DEPENDS ON
// include/monitor.php, include/common.php, include/unraid_api.php,
// include/vms.php, include/docker_folders.php, include/arrs.php
// Tools/api_cache_writer.sh the bash wrapper cron actually invokes
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// Collecting this payload means GraphQL, docker stats and SSH to every partner. It must never
// be triggerable by an HTTP request.
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit(1);
}
$_base = dirname(__DIR__);
require_once $_base . '/include/monitor.php';
require_once $_base . '/include/vms.php';
require_once $_base . '/include/docker_folders.php';
require_once $_base . '/include/arrs.php';
$t = microtime(true);
// ── Monitor payload ───────────────────────────────────────────────────────────
// Call vv_api_data() once — result is static-cached for the rest of this process.
vv_api_data();
// ── AI ────────────────────────────────────────────────────────────────────────
// One collection, two consumers. vv_ai_stats() is the expensive part of the AI subsystem —
// roughly a second, most of it waiting on Ollama and nvidia-smi — and it is written to its own
// cache here so the AI tab's banner, the Scheduler dock and the Monitor row all read the same
// numbers from the same moment instead of each paying for their own.
//
// The monitor block is derived from that same array rather than collected again. Must stay in
// step with api/monitor.php's own block: this file is what the Monitor tab normally reads, since
// the endpoint only assembles a payload on a cache miss, so a key added there and not here
// leaves the card that consumes it loading forever on every ordinary page load and working only
// on the one request that happens to miss.
$_vv_ai = null;
if (vv_ai_ui_on()) {
require_once $_base . '/include/ai.php';
$_vv_ai_stats = vv_ai_stats();
vv_cache_write('ai', $_vv_ai_stats);
$_vv_ai = vv_ai_monitor_block($_vv_ai_stats);
}
$monitor = [
'system' => vv_system_info(),
'varaverk' => vv_varaverk_state(),
'fallback' => vv_fallback_state(),
'fallback_active' => vv_fallback_active(),
'partner' => vv_partner_state(),
'resources' => vv_system_resources(),
'cpu' => vv_cpu_per_core(),
'mem' => vv_memory_breakdown(),
'net' => vv_network_stats(),
'gpu' => vv_gpu_stats(),
'gpus' => vv_gpu_stats_all(),
'gpu_procs' => vv_gpu_processes(),
'containers' => vv_docker_containers(),
'stopped' => vv_docker_stopped(),
'transcode' => vv_transcode_sessions(),
'ups' => vv_ups_stats(),
'parity' => vv_parity_status(),
'storage' => vv_storage_pools(),
'array_disks' => vv_array_disks(),
'disk_io' => vv_disk_io_rates(),
'watchdog' => vv_watchdog_summary(),
'scripts' => vv_scripts_status(),
'rsync' => vv_rsync_status(),
'thresholds' => vv_disk_thresholds(),
'vms' => vv_get_vms(),
'docker_folders' => vv_get_docker_folders(),
'remote_hosts' => vv_remote_hosts_stats(),
'ai' => $_vv_ai,
'_api_status' => vv_api_get_status(),
'ts' => time(),
];
vv_cache_write('monitor', $monitor);
// ── Arrs payload ──────────────────────────────────────────────────────────────
$arrs = vv_arrs_all();
vv_cache_write('arrs', $arrs);
// ── Watchdog payload ──────────────────────────────────────────────────────────
// Here for the same reason as the other two: vv_wd_all() SSHes to every partner, which cost 8.3s
// on this host with the partner unreachable, and the Watchdog tab polls every 30 seconds. Collected off the
// request path it is paid once a minute by cron instead of by whoever has the tab open.
require_once $_base . '/include/watchdog.php';
vv_cache_write('watchdog', vv_wd_all());
$elapsed = round((microtime(true) - $t) * 1000);
echo "Cache written in {$elapsed}ms — monitor + arrs + watchdog\n";