xref: /plugin/calendar/get_system_stats.php (revision 7e8ea635dd19058d6f7c428adbbe02d9702096d7)
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