<?php/** * SysMonitor Web — Background Collector Daemon * * Runs CPU and network sampling in the background so the API endpoint * responds instantly (no blocking `top -l 1` or netstat delta calc). * * Usage: php collector.php & * Output: writes /tmp/sysmonitor_web_state.json atomically every 1 s. */declare(ticks = 1);$stateFile = '/tmp/sysmonitor_web_state.json';$pidFile = '/tmp/sysmonitor_web_collector.pid';$interval = 1; // seconds between samples// ── Singleton check ──────────────────────────────────────────────────if (file_exists($pidFile)) { $oldPid = (int) @file_get_contents($pidFile); if ($oldPid > 0 && posix_kill($oldPid, 0)) { fwrite(STDERR, "Collector already running (PID $oldPid)\n"); exit(1); }}@file_put_contents($pidFile, (string) getmypid());// Cleanup on exit$shutdown = function () use ($pidFile) { @unlink($pidFile); fwrite(STDERR, "Collector stopped.\n"); exit(0);};pcntl_signal(SIGINT, function () use ($shutdown) { $shutdown(); });pcntl_signal(SIGTERM, function () use ($shutdown) { $shutdown(); });// ── Initial sample (seeds the state immediately) ─────────────────────fwrite(STDERR, "Collector started (PID " . getmypid() . "), sampling every {$interval}s\n");// ── Main loop ────────────────────────────────────────────────────────$prevNet = null;$prevNetTs = 0;while (true) { $now = microtime(true); $data = []; // ── CPU usage via top (blocking ~2 s, but runs in background) ──── $topOut = @shell_exec('top -l 1 -n 0 2>/dev/null'); $cpuUsage = 0; if ($topOut && preg_match('/CPU usage:\s*([\d.]+)%\s+user,\s*([\d.]+)%\s+sys/', $topOut, $m)) { $cpuUsage = round((float) $m[1] + (float) $m[2], 1); } $cores = (int) @trim(shell_exec('sysctl -n hw.ncpu 2>/dev/null') ?: '0'); $data['cpu'] = ['overall' => $cpuUsage, 'cores' => max($cores, 0)]; // ── Network rate via netstat (delta from previous sample) ──────── $netOut = @shell_exec('netstat -ibn 2>/dev/null'); $netLines = preg_split('/\r?\n/', $netOut ?: ''); $curNet = ['in' => 0, 'out' => 0]; foreach ($netLines as $line) { $parts = preg_split('/\s+/', trim($line)); if (count($parts) < 10) continue; $name = $parts[0]; if (str_starts_with($name, 'lo') || $name === 'Name') continue; $curNet['in'] += (int) ($parts[6] ?? 0); $curNet['out'] += (int) ($parts[9] ?? 0); } $rateIn = 0; $rateOut = 0; if ($prevNet && ($now - $prevNetTs) > 0.3) { $dt = $now - $prevNetTs; if ($dt > 0) { $rateIn = max(0, ($curNet['in'] - $prevNet['in']) / $dt); $rateOut = max(0, ($curNet['out'] - $prevNet['out']) / $dt); } } $prevNet = $curNet; $prevNetTs = $now; $data['network'] = [ 'rate_in' => round($rateIn), 'rate_out' => round($rateOut), 'total_in' => $curNet['in'], 'total_out' => $curNet['out'], ]; // ── Atomic write ───────────────────────────────────────────────── $tmpFile = $stateFile . '.' . getmypid() . '.tmp'; @file_put_contents($tmpFile, json_encode($data), LOCK_EX); @rename($tmpFile, $stateFile); // ── Sleep until next interval (account for time spent sampling) ── $elapsed = microtime(true) - $now; $sleepMs = max(100, (int)(($interval - $elapsed) * 1_000_000)); usleep($sleepMs);}