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.
116 lines
5.8 KiB
PHP
116 lines
5.8 KiB
PHP
<?php
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
// PURPOSE
|
|
// Header snapshot. The small always-visible summary carried across every tab — CPU, RAM,
|
|
// fallback state, partner peers, and active stream and transcode counts.
|
|
//
|
|
// OPERATIONAL MODEL
|
|
// This is the most frequently requested endpoint in the plugin: it is polled from whichever
|
|
// tab happens to be open, continuously, for as long as the page is open. Everything about
|
|
// it is shaped by that. Each field is the cheapest available answer to its question, not
|
|
// the most complete one — monitor.php exists for the complete one.
|
|
//
|
|
// DESIGN PRINCIPLES
|
|
// Shares the CPU baseline rather than sampling its own.
|
|
// vv_cpu_per_core() keeps its counters in /tmp/vv_cpu_stat.json, and reading through it
|
|
// means the header and the monitor page report the same number. Two independent
|
|
// samplers would drift apart and produce a visible disagreement between the header and
|
|
// the page directly below it.
|
|
//
|
|
// Fallback state is read from the file, never derived.
|
|
// A direct read of fallback_state.db with no exec and no SSH. The authoritative answer
|
|
// is the one fallback.sh wrote; recomputing it here would be both slower and capable of
|
|
// disagreeing with the process that actually controls failover.
|
|
//
|
|
// Media counts are cached 30 seconds, everything else is live.
|
|
// Streams are the only field that costs HTTP round trips to another service. Caching
|
|
// just that keeps the poll cheap without making CPU or RAM stale, and 30s is well under
|
|
// the time it takes anyone to notice a stream started.
|
|
//
|
|
// Peers exclude self.
|
|
// is_me is filtered out here rather than in the UI, so every consumer of this payload
|
|
// gets the same definition of "peers" and none of them can forget to apply it.
|
|
//
|
|
// OPERATIONAL SAFEGUARDS
|
|
// Read-only. Nothing here starts, stops, or changes anything.
|
|
//
|
|
// The fallback read degrades to a named unknown.
|
|
// @file_get_contents with a ?: '' fallback, and the parsed state defaults to 'UNKNOWN'.
|
|
// A missing or unreadable state file shows UNKNOWN in the header rather than NORMAL —
|
|
// reporting healthy for a fallback process that is not running is the one error this
|
|
// field must never make.
|
|
//
|
|
// Division is guarded.
|
|
// ram_total_mb > 0 is checked before the percentage, so a failed meminfo read yields 0
|
|
// rather than a division by zero that would fatal on every tab at once.
|
|
//
|
|
// Every count is cast on the way out.
|
|
// (int) on the cached stream and transcode counts with ?? 0 defaults, so a cache file
|
|
// written by an older schema cannot put a null or a string into the header payload.
|
|
//
|
|
// Media failures are already contained upstream — vv_media_sessions() time-boxes each
|
|
// request at 3s and returns an empty session list on any failure, so an unreachable Emby
|
|
// costs this poll nothing beyond that timeout, once every 30 seconds.
|
|
//
|
|
// REQUEST
|
|
// GET, no parameters
|
|
//
|
|
// RESPONSE
|
|
// {"cpu_pct","ram_pct","ram_used_mb","ram_total_mb","fallback","partner_enabled",
|
|
// "peers":[…],"stream_count","transcode_count"}
|
|
//
|
|
// DEPENDS ON
|
|
// include/common.php vv_cpu_per_core(), vv_system_resources()
|
|
// (loaded transitively through include/monitor.php)
|
|
// include/monitor.php vv_partner_state()
|
|
// include/config.php vv_parse_kv_db(), vv_cache_read(), vv_cache_write(), STATE_DIR
|
|
// include/media.php vv_media_sessions()
|
|
// STATE_DIR fallback_state.db — written by Fallback/fallback.sh
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
header('Content-Type: application/json');
|
|
require_once dirname(__DIR__) . '/include/monitor.php';
|
|
require_once dirname(__DIR__) . '/include/media.php';
|
|
|
|
// CPU% — shares /tmp/vv_cpu_stat.json with vv_cpu_per_core() so both read the same baseline
|
|
$cpuPct = vv_cpu_per_core()['overall'] ?? 0;
|
|
|
|
// RAM%
|
|
$res = vv_system_resources();
|
|
$ramTotalMb = $res['ram_total_mb'];
|
|
$ramUsedMb = $ramTotalMb - $res['ram_free_mb'];
|
|
$ramPct = $ramTotalMb > 0 ? (int)round($ramUsedMb / $ramTotalMb * 100) : 0;
|
|
|
|
// Fallback state (fast file read, no exec)
|
|
$fbRaw = @file_get_contents(STATE_DIR . '/fallback_state.db') ?: '';
|
|
$fbData = vv_parse_kv_db($fbRaw);
|
|
$fallbackState = $fbData['state'] ?? 'UNKNOWN';
|
|
|
|
// Partner
|
|
$partner = vv_partner_state();
|
|
$peers = array_values(array_filter($partner['hosts'], fn($h) => !$h['is_me']));
|
|
|
|
// Media sessions — cached 30s so the HTTP calls don't hold up every snapshot poll
|
|
$mediaCache = vv_cache_read('snap_media', 30);
|
|
if (!$mediaCache) {
|
|
$media = vv_media_sessions();
|
|
$mediaCache = [
|
|
'stream_count' => count($media['sessions']),
|
|
'transcode_count' => count(array_filter($media['sessions'], fn($s) => !empty($s['is_tc']))),
|
|
];
|
|
vv_cache_write('snap_media', $mediaCache);
|
|
}
|
|
$streamCount = (int)($mediaCache['stream_count'] ?? 0);
|
|
$transcodeCount = (int)($mediaCache['transcode_count'] ?? 0);
|
|
|
|
echo json_encode([
|
|
'cpu_pct' => $cpuPct,
|
|
'ram_pct' => $ramPct,
|
|
'ram_used_mb' => $ramUsedMb,
|
|
'ram_total_mb' => $ramTotalMb,
|
|
'fallback' => $fallbackState,
|
|
'partner_enabled' => $partner['enabled'],
|
|
'peers' => $peers,
|
|
'stream_count' => $streamCount,
|
|
'transcode_count' => $transcodeCount,
|
|
]);
|