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