11d05cddcSAtari911<?php 21d05cddcSAtari911/** 31d05cddcSAtari911 * System Stats API Endpoint 41d05cddcSAtari911 * Returns real-time CPU and memory usage 5*7e8ea635SAtari911 * 6*7e8ea635SAtari911 * Requires admin authentication 71d05cddcSAtari911 */ 81d05cddcSAtari911 9*7e8ea635SAtari911// Initialize DokuWiki environment for authentication 10*7e8ea635SAtari911if (!defined('DOKU_INC')) { 11*7e8ea635SAtari911 define('DOKU_INC', dirname(__FILE__) . '/../../../'); 12*7e8ea635SAtari911} 13*7e8ea635SAtari911require_once(DOKU_INC . 'inc/init.php'); 14*7e8ea635SAtari911 15*7e8ea635SAtari911// Require admin privileges 16*7e8ea635SAtari911if (!auth_isadmin()) { 17*7e8ea635SAtari911 header('Content-Type: application/json'); 18*7e8ea635SAtari911 http_response_code(403); 19*7e8ea635SAtari911 echo json_encode(['error' => 'Admin access required']); 20*7e8ea635SAtari911 exit; 21*7e8ea635SAtari911} 22*7e8ea635SAtari911 231d05cddcSAtari911header('Content-Type: application/json'); 241d05cddcSAtari911header('Cache-Control: no-cache, must-revalidate'); 251d05cddcSAtari911 261d05cddcSAtari911$stats = [ 271d05cddcSAtari911 'cpu' => 0, 281d05cddcSAtari911 'cpu_5min' => 0, 291d05cddcSAtari911 'memory' => 0, 301d05cddcSAtari911 'timestamp' => time(), 311d05cddcSAtari911 'load' => ['1min' => 0, '5min' => 0, '15min' => 0], 321d05cddcSAtari911 'uptime' => '', 331d05cddcSAtari911 'memory_details' => [], 341d05cddcSAtari911 'top_processes' => [] 351d05cddcSAtari911]; 361d05cddcSAtari911 371d05cddcSAtari911// Get CPU usage and load averages 381d05cddcSAtari911if (function_exists('sys_getloadavg')) { 391d05cddcSAtari911 $load = sys_getloadavg(); 401d05cddcSAtari911 if ($load !== false) { 411d05cddcSAtari911 // Use 1-minute load average for real-time feel 421d05cddcSAtari911 // Normalize to percentage (assuming max load of 2.0 = 100%) 431d05cddcSAtari911 $stats['cpu'] = min(100, ($load[0] / 2.0) * 100); 441d05cddcSAtari911 451d05cddcSAtari911 // 5-minute average for green bar 461d05cddcSAtari911 $stats['cpu_5min'] = min(100, ($load[1] / 2.0) * 100); 471d05cddcSAtari911 481d05cddcSAtari911 // Store all three load averages for tooltip 491d05cddcSAtari911 $stats['load'] = [ 501d05cddcSAtari911 '1min' => round($load[0], 2), 511d05cddcSAtari911 '5min' => round($load[1], 2), 521d05cddcSAtari911 '15min' => round($load[2], 2) 531d05cddcSAtari911 ]; 541d05cddcSAtari911 } 551d05cddcSAtari911} 561d05cddcSAtari911 571d05cddcSAtari911// Get memory usage 581d05cddcSAtari911if (stristr(PHP_OS, 'linux')) { 591d05cddcSAtari911 // Linux: Read from /proc/meminfo 601d05cddcSAtari911 $meminfo = file_get_contents('/proc/meminfo'); 611d05cddcSAtari911 if ($meminfo) { 621d05cddcSAtari911 preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total); 631d05cddcSAtari911 preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available); 641d05cddcSAtari911 651d05cddcSAtari911 if (isset($total[1]) && isset($available[1])) { 661d05cddcSAtari911 $totalMem = $total[1]; 671d05cddcSAtari911 $availableMem = $available[1]; 681d05cddcSAtari911 $usedMem = $totalMem - $availableMem; 691d05cddcSAtari911 $stats['memory'] = ($usedMem / $totalMem) * 100; 701d05cddcSAtari911 } 711d05cddcSAtari911 } 721d05cddcSAtari911} elseif (stristr(PHP_OS, 'darwin') || stristr(PHP_OS, 'bsd')) { 731d05cddcSAtari911 // macOS/BSD: Use vm_stat 741d05cddcSAtari911 $vm_stat = shell_exec('vm_stat'); 751d05cddcSAtari911 if ($vm_stat) { 761d05cddcSAtari911 preg_match('/Pages free:\s+(\d+)\./', $vm_stat, $free); 771d05cddcSAtari911 preg_match('/Pages active:\s+(\d+)\./', $vm_stat, $active); 781d05cddcSAtari911 preg_match('/Pages inactive:\s+(\d+)\./', $vm_stat, $inactive); 791d05cddcSAtari911 preg_match('/Pages wired down:\s+(\d+)\./', $vm_stat, $wired); 801d05cddcSAtari911 811d05cddcSAtari911 if (isset($free[1], $active[1], $inactive[1], $wired[1])) { 821d05cddcSAtari911 $pageSize = 4096; // bytes 831d05cddcSAtari911 $totalPages = $free[1] + $active[1] + $inactive[1] + $wired[1]; 841d05cddcSAtari911 $usedPages = $active[1] + $inactive[1] + $wired[1]; 851d05cddcSAtari911 861d05cddcSAtari911 if ($totalPages > 0) { 871d05cddcSAtari911 $stats['memory'] = ($usedPages / $totalPages) * 100; 881d05cddcSAtari911 } 891d05cddcSAtari911 } 901d05cddcSAtari911 } 911d05cddcSAtari911} elseif (stristr(PHP_OS, 'win')) { 921d05cddcSAtari911 // Windows: Use wmic 931d05cddcSAtari911 $wmic = shell_exec('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value'); 941d05cddcSAtari911 if ($wmic) { 951d05cddcSAtari911 preg_match('/FreePhysicalMemory=(\d+)/', $wmic, $free); 961d05cddcSAtari911 preg_match('/TotalVisibleMemorySize=(\d+)/', $wmic, $total); 971d05cddcSAtari911 981d05cddcSAtari911 if (isset($free[1]) && isset($total[1])) { 991d05cddcSAtari911 $freeMem = $free[1]; 1001d05cddcSAtari911 $totalMem = $total[1]; 1011d05cddcSAtari911 $usedMem = $totalMem - $freeMem; 1021d05cddcSAtari911 $stats['memory'] = ($usedMem / $totalMem) * 100; 1031d05cddcSAtari911 } 1041d05cddcSAtari911 } 1051d05cddcSAtari911} 1061d05cddcSAtari911 1071d05cddcSAtari911// Fallback: Use PHP memory if system memory unavailable 1081d05cddcSAtari911if ($stats['memory'] == 0) { 1091d05cddcSAtari911 $memLimit = ini_get('memory_limit'); 1101d05cddcSAtari911 if ($memLimit != '-1') { 1111d05cddcSAtari911 $memLimitBytes = return_bytes($memLimit); 1121d05cddcSAtari911 $memUsage = memory_get_usage(true); 1131d05cddcSAtari911 $stats['memory'] = ($memUsage / $memLimitBytes) * 100; 1141d05cddcSAtari911 } 1151d05cddcSAtari911} 1161d05cddcSAtari911 1171d05cddcSAtari911// Get uptime (Linux/Unix) 1181d05cddcSAtari911if (file_exists('/proc/uptime')) { 1191d05cddcSAtari911 $uptime = file_get_contents('/proc/uptime'); 1201d05cddcSAtari911 if ($uptime) { 1211d05cddcSAtari911 $uptimeSeconds = floatval(explode(' ', $uptime)[0]); 1221d05cddcSAtari911 $days = floor($uptimeSeconds / 86400); 1231d05cddcSAtari911 $hours = floor(($uptimeSeconds % 86400) / 3600); 1241d05cddcSAtari911 $minutes = floor(($uptimeSeconds % 3600) / 60); 1251d05cddcSAtari911 $stats['uptime'] = sprintf('%dd %dh %dm', $days, $hours, $minutes); 1261d05cddcSAtari911 } 1271d05cddcSAtari911} elseif (stristr(PHP_OS, 'win')) { 1281d05cddcSAtari911 // Windows uptime 1291d05cddcSAtari911 $wmic = shell_exec('wmic os get lastbootuptime'); 1301d05cddcSAtari911 if ($wmic && preg_match('/(\d{14})/', $wmic, $matches)) { 1311d05cddcSAtari911 $bootTime = DateTime::createFromFormat('YmdHis', $matches[1]); 1321d05cddcSAtari911 $now = new DateTime(); 1331d05cddcSAtari911 $diff = $now->diff($bootTime); 1341d05cddcSAtari911 $stats['uptime'] = sprintf('%dd %dh %dm', $diff->days, $diff->h, $diff->i); 1351d05cddcSAtari911 } 1361d05cddcSAtari911} 1371d05cddcSAtari911 1381d05cddcSAtari911// Get detailed memory info (Linux) 1391d05cddcSAtari911if (stristr(PHP_OS, 'linux') && file_exists('/proc/meminfo')) { 1401d05cddcSAtari911 $meminfo = file_get_contents('/proc/meminfo'); 1411d05cddcSAtari911 if ($meminfo) { 1421d05cddcSAtari911 preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total); 1431d05cddcSAtari911 preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available); 1441d05cddcSAtari911 preg_match('/MemFree:\s+(\d+)/', $meminfo, $free); 1451d05cddcSAtari911 preg_match('/Buffers:\s+(\d+)/', $meminfo, $buffers); 1461d05cddcSAtari911 preg_match('/Cached:\s+(\d+)/', $meminfo, $cached); 1471d05cddcSAtari911 1481d05cddcSAtari911 if (isset($total[1])) { 1491d05cddcSAtari911 $totalMB = round($total[1] / 1024, 1); 1501d05cddcSAtari911 $availableMB = isset($available[1]) ? round($available[1] / 1024, 1) : 0; 1511d05cddcSAtari911 $usedMB = round(($total[1] - ($available[1] ?? $free[1] ?? 0)) / 1024, 1); 1521d05cddcSAtari911 $buffersMB = isset($buffers[1]) ? round($buffers[1] / 1024, 1) : 0; 1531d05cddcSAtari911 $cachedMB = isset($cached[1]) ? round($cached[1] / 1024, 1) : 0; 1541d05cddcSAtari911 1551d05cddcSAtari911 $stats['memory_details'] = [ 1561d05cddcSAtari911 'total' => $totalMB . ' MB', 1571d05cddcSAtari911 'used' => $usedMB . ' MB', 1581d05cddcSAtari911 'available' => $availableMB . ' MB', 1591d05cddcSAtari911 'buffers' => $buffersMB . ' MB', 1601d05cddcSAtari911 'cached' => $cachedMB . ' MB' 1611d05cddcSAtari911 ]; 1621d05cddcSAtari911 } 1631d05cddcSAtari911 } 1641d05cddcSAtari911} 1651d05cddcSAtari911 1661d05cddcSAtari911// Get top 5 processes by CPU (Linux/Unix) 1671d05cddcSAtari911if (stristr(PHP_OS, 'linux') || stristr(PHP_OS, 'darwin')) { 1681d05cddcSAtari911 $ps = shell_exec('ps aux --sort=-%cpu | head -6 | tail -5 2>/dev/null'); 1691d05cddcSAtari911 if (!$ps) { 1701d05cddcSAtari911 // Try BSD/macOS format 1711d05cddcSAtari911 $ps = shell_exec('ps aux -r | head -6 | tail -5 2>/dev/null'); 1721d05cddcSAtari911 } 1731d05cddcSAtari911 if ($ps) { 1741d05cddcSAtari911 $lines = explode("\n", trim($ps)); 1751d05cddcSAtari911 foreach ($lines as $line) { 1761d05cddcSAtari911 if (empty($line)) continue; 1771d05cddcSAtari911 $parts = preg_split('/\s+/', $line, 11); 1781d05cddcSAtari911 if (count($parts) >= 11) { 1791d05cddcSAtari911 $stats['top_processes'][] = [ 1801d05cddcSAtari911 'cpu' => $parts[2] . '%', 1811d05cddcSAtari911 'mem' => $parts[3] . '%', 1821d05cddcSAtari911 'command' => substr($parts[10], 0, 30) 1831d05cddcSAtari911 ]; 1841d05cddcSAtari911 } 1851d05cddcSAtari911 } 1861d05cddcSAtari911 } 1871d05cddcSAtari911} elseif (stristr(PHP_OS, 'win')) { 1881d05cddcSAtari911 // Windows top processes 1891d05cddcSAtari911 $wmic = shell_exec('wmic process get Caption,KernelModeTime /format:csv | findstr /V "^$" | sort /R /+1 | more +1 | findstr /N "^" | findstr "^[1-5]:"'); 1901d05cddcSAtari911 if ($wmic) { 1911d05cddcSAtari911 $lines = explode("\n", trim($wmic)); 1921d05cddcSAtari911 foreach ($lines as $line) { 1931d05cddcSAtari911 if (preg_match('/^\d+:(.+),(.+),(\d+)/', $line, $matches)) { 1941d05cddcSAtari911 $stats['top_processes'][] = [ 1951d05cddcSAtari911 'command' => substr($matches[2], 0, 30), 1961d05cddcSAtari911 'cpu' => '-' 1971d05cddcSAtari911 ]; 1981d05cddcSAtari911 } 1991d05cddcSAtari911 } 2001d05cddcSAtari911 } 2011d05cddcSAtari911} 2021d05cddcSAtari911 2031d05cddcSAtari911echo json_encode($stats); 2041d05cddcSAtari911 2051d05cddcSAtari911function return_bytes($val) { 2061d05cddcSAtari911 $val = trim($val); 2071d05cddcSAtari911 $last = strtolower($val[strlen($val)-1]); 2081d05cddcSAtari911 $val = (int)$val; 209*7e8ea635SAtari911 // Intentional fallthrough for unit conversion cascade 2101d05cddcSAtari911 switch($last) { 2111d05cddcSAtari911 case 'g': 2121d05cddcSAtari911 $val *= 1024; 213*7e8ea635SAtari911 // fallthrough intentional 2141d05cddcSAtari911 case 'm': 2151d05cddcSAtari911 $val *= 1024; 216*7e8ea635SAtari911 // fallthrough intentional 2171d05cddcSAtari911 case 'k': 2181d05cddcSAtari911 $val *= 1024; 2191d05cddcSAtari911 } 2201d05cddcSAtari911 return $val; 2211d05cddcSAtari911} 222