The tab collected over SSH on every 30s poll — 8.3s a load with HOST2 down. vv_parse_conf_scalar() captured to end of line, so a commented toggle parsed as "true # HOST2 back online": every threshold read right because (int) stops at the first non-digit, and 38 booleans read wrong. The Fallback tab has been showing failover disabled while it was on.
159 lines
8.3 KiB
PHP
159 lines
8.3 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(),
|
|
'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 HOST2 down, 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";
|