1<?php 2/** 3 * System Stats API Endpoint 4 * Returns real-time CPU and memory usage 5 * 6 * Requires admin authentication 7 */ 8 9// Initialize DokuWiki environment for authentication 10if (!defined('DOKU_INC')) { 11 define('DOKU_INC', dirname(__FILE__) . '/../../../'); 12} 13require_once(DOKU_INC . 'inc/init.php'); 14 15// Require admin privileges 16if (!auth_isadmin()) { 17 header('Content-Type: application/json'); 18 http_response_code(403); 19 echo json_encode(['error' => 'Admin access required']); 20 exit; 21} 22 23header('Content-Type: application/json'); 24header('Cache-Control: no-cache, must-revalidate'); 25 26$stats = [ 27 'cpu' => 0, 28 'cpu_5min' => 0, 29 'memory' => 0, 30 'timestamp' => time(), 31 'load' => ['1min' => 0, '5min' => 0, '15min' => 0], 32 'uptime' => '', 33 'memory_details' => [], 34 'top_processes' => [] 35]; 36 37// Get CPU usage and load averages 38if (function_exists('sys_getloadavg')) { 39 $load = sys_getloadavg(); 40 if ($load !== false) { 41 // Use 1-minute load average for real-time feel 42 // Normalize to percentage (assuming max load of 2.0 = 100%) 43 $stats['cpu'] = min(100, ($load[0] / 2.0) * 100); 44 45 // 5-minute average for green bar 46 $stats['cpu_5min'] = min(100, ($load[1] / 2.0) * 100); 47 48 // Store all three load averages for tooltip 49 $stats['load'] = [ 50 '1min' => round($load[0], 2), 51 '5min' => round($load[1], 2), 52 '15min' => round($load[2], 2) 53 ]; 54 } 55} 56 57// Get memory usage 58if (stristr(PHP_OS, 'linux')) { 59 // Linux: Read from /proc/meminfo 60 $meminfo = file_get_contents('/proc/meminfo'); 61 if ($meminfo) { 62 preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total); 63 preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available); 64 65 if (isset($total[1]) && isset($available[1])) { 66 $totalMem = $total[1]; 67 $availableMem = $available[1]; 68 $usedMem = $totalMem - $availableMem; 69 $stats['memory'] = ($usedMem / $totalMem) * 100; 70 } 71 } 72} elseif (stristr(PHP_OS, 'darwin') || stristr(PHP_OS, 'bsd')) { 73 // macOS/BSD: Use vm_stat 74 $vm_stat = shell_exec('vm_stat'); 75 if ($vm_stat) { 76 preg_match('/Pages free:\s+(\d+)\./', $vm_stat, $free); 77 preg_match('/Pages active:\s+(\d+)\./', $vm_stat, $active); 78 preg_match('/Pages inactive:\s+(\d+)\./', $vm_stat, $inactive); 79 preg_match('/Pages wired down:\s+(\d+)\./', $vm_stat, $wired); 80 81 if (isset($free[1], $active[1], $inactive[1], $wired[1])) { 82 $pageSize = 4096; // bytes 83 $totalPages = $free[1] + $active[1] + $inactive[1] + $wired[1]; 84 $usedPages = $active[1] + $inactive[1] + $wired[1]; 85 86 if ($totalPages > 0) { 87 $stats['memory'] = ($usedPages / $totalPages) * 100; 88 } 89 } 90 } 91} elseif (stristr(PHP_OS, 'win')) { 92 // Windows: Use wmic 93 $wmic = shell_exec('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value'); 94 if ($wmic) { 95 preg_match('/FreePhysicalMemory=(\d+)/', $wmic, $free); 96 preg_match('/TotalVisibleMemorySize=(\d+)/', $wmic, $total); 97 98 if (isset($free[1]) && isset($total[1])) { 99 $freeMem = $free[1]; 100 $totalMem = $total[1]; 101 $usedMem = $totalMem - $freeMem; 102 $stats['memory'] = ($usedMem / $totalMem) * 100; 103 } 104 } 105} 106 107// Fallback: Use PHP memory if system memory unavailable 108if ($stats['memory'] == 0) { 109 $memLimit = ini_get('memory_limit'); 110 if ($memLimit != '-1') { 111 $memLimitBytes = return_bytes($memLimit); 112 $memUsage = memory_get_usage(true); 113 $stats['memory'] = ($memUsage / $memLimitBytes) * 100; 114 } 115} 116 117// Get uptime (Linux/Unix) 118if (file_exists('/proc/uptime')) { 119 $uptime = file_get_contents('/proc/uptime'); 120 if ($uptime) { 121 $uptimeSeconds = floatval(explode(' ', $uptime)[0]); 122 $days = floor($uptimeSeconds / 86400); 123 $hours = floor(($uptimeSeconds % 86400) / 3600); 124 $minutes = floor(($uptimeSeconds % 3600) / 60); 125 $stats['uptime'] = sprintf('%dd %dh %dm', $days, $hours, $minutes); 126 } 127} elseif (stristr(PHP_OS, 'win')) { 128 // Windows uptime 129 $wmic = shell_exec('wmic os get lastbootuptime'); 130 if ($wmic && preg_match('/(\d{14})/', $wmic, $matches)) { 131 $bootTime = DateTime::createFromFormat('YmdHis', $matches[1]); 132 $now = new DateTime(); 133 $diff = $now->diff($bootTime); 134 $stats['uptime'] = sprintf('%dd %dh %dm', $diff->days, $diff->h, $diff->i); 135 } 136} 137 138// Get detailed memory info (Linux) 139if (stristr(PHP_OS, 'linux') && file_exists('/proc/meminfo')) { 140 $meminfo = file_get_contents('/proc/meminfo'); 141 if ($meminfo) { 142 preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total); 143 preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available); 144 preg_match('/MemFree:\s+(\d+)/', $meminfo, $free); 145 preg_match('/Buffers:\s+(\d+)/', $meminfo, $buffers); 146 preg_match('/Cached:\s+(\d+)/', $meminfo, $cached); 147 148 if (isset($total[1])) { 149 $totalMB = round($total[1] / 1024, 1); 150 $availableMB = isset($available[1]) ? round($available[1] / 1024, 1) : 0; 151 $usedMB = round(($total[1] - ($available[1] ?? $free[1] ?? 0)) / 1024, 1); 152 $buffersMB = isset($buffers[1]) ? round($buffers[1] / 1024, 1) : 0; 153 $cachedMB = isset($cached[1]) ? round($cached[1] / 1024, 1) : 0; 154 155 $stats['memory_details'] = [ 156 'total' => $totalMB . ' MB', 157 'used' => $usedMB . ' MB', 158 'available' => $availableMB . ' MB', 159 'buffers' => $buffersMB . ' MB', 160 'cached' => $cachedMB . ' MB' 161 ]; 162 } 163 } 164} 165 166// Get top 5 processes by CPU (Linux/Unix) 167if (stristr(PHP_OS, 'linux') || stristr(PHP_OS, 'darwin')) { 168 $ps = shell_exec('ps aux --sort=-%cpu | head -6 | tail -5 2>/dev/null'); 169 if (!$ps) { 170 // Try BSD/macOS format 171 $ps = shell_exec('ps aux -r | head -6 | tail -5 2>/dev/null'); 172 } 173 if ($ps) { 174 $lines = explode("\n", trim($ps)); 175 foreach ($lines as $line) { 176 if (empty($line)) continue; 177 $parts = preg_split('/\s+/', $line, 11); 178 if (count($parts) >= 11) { 179 $stats['top_processes'][] = [ 180 'cpu' => $parts[2] . '%', 181 'mem' => $parts[3] . '%', 182 'command' => substr($parts[10], 0, 30) 183 ]; 184 } 185 } 186 } 187} elseif (stristr(PHP_OS, 'win')) { 188 // Windows top processes 189 $wmic = shell_exec('wmic process get Caption,KernelModeTime /format:csv | findstr /V "^$" | sort /R /+1 | more +1 | findstr /N "^" | findstr "^[1-5]:"'); 190 if ($wmic) { 191 $lines = explode("\n", trim($wmic)); 192 foreach ($lines as $line) { 193 if (preg_match('/^\d+:(.+),(.+),(\d+)/', $line, $matches)) { 194 $stats['top_processes'][] = [ 195 'command' => substr($matches[2], 0, 30), 196 'cpu' => '-' 197 ]; 198 } 199 } 200 } 201} 202 203echo json_encode($stats); 204 205function return_bytes($val) { 206 $val = trim($val); 207 $last = strtolower($val[strlen($val)-1]); 208 $val = (int)$val; 209 // Intentional fallthrough for unit conversion cascade 210 switch($last) { 211 case 'g': 212 $val *= 1024; 213 // fallthrough intentional 214 case 'm': 215 $val *= 1024; 216 // fallthrough intentional 217 case 'k': 218 $val *= 1024; 219 } 220 return $val; 221} 222