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.
125 lines
6.5 KiB
PHP
125 lines
6.5 KiB
PHP
<?php
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
// PURPOSE
|
|
// Full monitor payload. Every metric the monitor page renders — system, CPU, memory,
|
|
// network, GPUs, disks and pools, array, parity, UPS, containers, VMs, transcodes, watchdog
|
|
// and rsync summaries, and remote node roll-ups — in one document.
|
|
//
|
|
// OPERATIONAL MODEL
|
|
// Deliberately one large response rather than many small ones. Most of these metrics share
|
|
// an underlying source — the Unraid API, /proc, the same emhttp ini files — so collecting
|
|
// them together means one pass over each source instead of one per endpoint. The page
|
|
// renders from a single consistent snapshot; twenty parallel fetches would render from
|
|
// twenty slightly different moments.
|
|
//
|
|
// Served from a 300s cache written by Tools/api_cache_writer.sh, which runs every minute.
|
|
// The page therefore almost never pays for collection — the background writer does. ?live
|
|
// forces a fresh build for the refresh button.
|
|
//
|
|
// DESIGN PRINCIPLES
|
|
// The cache check happens before the heavy includes.
|
|
// Only include/config.php is loaded to reach vv_cache_read(). monitor.php, vms.php and
|
|
// docker_folders.php are required only after a miss, so a cache hit costs one file read
|
|
// rather than parsing three libraries.
|
|
//
|
|
// The API cache is pre-warmed once, on purpose.
|
|
// vv_api_data() is called before the payload is assembled so the API-first functions
|
|
// below it share a single GraphQL round trip instead of each making their own.
|
|
//
|
|
// Every field is a named function call, in render order.
|
|
// The payload is a flat map of key to collector. Adding a metric is adding a line, and
|
|
// nothing in the assembly depends on anything else in it — so one expensive or broken
|
|
// collector can be moved or removed without touching the others.
|
|
//
|
|
// Reports the API's own health alongside the data.
|
|
// _api_status travels with the payload, so the page can show that a section degraded to
|
|
// its local fallback rather than silently presenting less detail.
|
|
//
|
|
// OPERATIONAL SAFEGUARDS
|
|
// Read-only. Every function here observes; none of them start, stop, or change anything.
|
|
//
|
|
// Cache miss is distinguished from empty payload.
|
|
// vv_cache_read() returns null on a miss, expiry, or unparseable file, and the check is
|
|
// an explicit !== null — so a legitimately sparse payload is served from cache instead
|
|
// of being mistaken for a miss and forced onto the expensive path on every poll.
|
|
//
|
|
// Every collector degrades to empty rather than fatal.
|
|
// The library suppresses its filesystem reads and redirects stderr on every shell call,
|
|
// so absent hardware — no GPU, no UPS, no ZFS, no VMs — yields an empty section and an
|
|
// unrendered card. On a payload this wide that property is what keeps one missing
|
|
// subsystem from blanking the entire page.
|
|
//
|
|
// Remote collection degrades per node, so one dark partner costs its own card and nothing
|
|
// else.
|
|
//
|
|
// Known cost: a cache miss on a host with an unreachable partner pays the remote SSH
|
|
// timeouts inline. That is why the background writer exists and why the cache window is
|
|
// long — the miss path is the exception, not the design.
|
|
//
|
|
// REQUEST
|
|
// GET served from the 300s cache when one is present
|
|
// GET ?live bypass the cache and collect everything fresh
|
|
//
|
|
// RESPONSE
|
|
// A flat object of the keys listed in the assembly below, plus _api_status and ts.
|
|
//
|
|
// DEPENDS ON
|
|
// include/config.php vv_cache_read()
|
|
// include/common.php raw hardware metrics — system, cpu, mem, net, gpu, disks,
|
|
// ups, parity, containers, transcodes, remote roll-ups
|
|
// (loaded transitively through include/monitor.php)
|
|
// include/monitor.php the "is anything wrong" roll-ups — vv_partner_state(),
|
|
// vv_fallback_state(), vv_watchdog_summary(),
|
|
// vv_scripts_status(), vv_rsync_status()
|
|
// include/unraid_api.php vv_api_data(), vv_api_get_status()
|
|
// include/vms.php vv_get_vms()
|
|
// include/docker_folders.php vv_get_docker_folders()
|
|
// Tools/api_cache_writer.sh writes the cache this endpoint normally serves
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
header('Content-Type: application/json');
|
|
require_once dirname(__DIR__) . '/include/config.php';
|
|
|
|
if (!isset($_GET['live'])) {
|
|
$_vv_cached = vv_cache_read('monitor', 300);
|
|
if ($_vv_cached !== null) { echo json_encode($_vv_cached); exit; }
|
|
unset($_vv_cached);
|
|
}
|
|
|
|
require_once dirname(__DIR__) . '/include/monitor.php';
|
|
require_once dirname(__DIR__) . '/include/vms.php';
|
|
require_once dirname(__DIR__) . '/include/docker_folders.php';
|
|
|
|
// Pre-warm the API cache with one request (shared by all API-first functions below).
|
|
vv_api_data();
|
|
|
|
echo json_encode([
|
|
'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(),
|
|
'_api_status' => vv_api_get_status(),
|
|
'ts' => time(),
|
|
]);
|