| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <?php
- // +----------------------------------------------------------------------
- // | saiadmin [ saiadmin快速开发框架 ]
- // +----------------------------------------------------------------------
- // | Author: sai <1430792918@qq.com>
- // +----------------------------------------------------------------------
- namespace plugin\saiadmin\utils;
- /**
- * 服务器监控信息
- */
- class ServerMonitor
- {
- /**
- * 获取内存信息
- * @return array
- */
- public function getMemoryInfo(): array
- {
- $totalMem = 0; // 总内存 (Bytes)
- $freeMem = 0; // 可用/剩余内存 (Bytes)
- if (stristr(PHP_OS, 'WIN')) {
- // Windows 系统
- // 一次性获取 总可见内存 和 空闲物理内存 (单位都是 KB)
- // TotalVisibleMemorySize: 操作系统可识别的内存总数 (比物理内存条总数略少,更准确反映可用上限)
- // FreePhysicalMemory: 当前可用物理内存
- $cmd = 'wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /format:csv';
- $output = shell_exec($cmd);
- $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');
- $lines = explode("\n", trim($output));
- foreach ($lines as $line) {
- $line = trim($line);
- if (empty($line)) continue;
- // CSV 格式: Node,FreePhysicalMemory,TotalVisibleMemorySize
- $parts = str_getcsv($line);
- // 确保解析正确且排除标题行 (通常索引1是Free, 2是Total)
- if (count($parts) >= 3 && is_numeric($parts[1])) {
- $freeMem = floatval($parts[1]) * 1024; // KB -> Bytes
- $totalMem = floatval($parts[2]) * 1024; // KB -> Bytes
- break;
- }
- }
- } else {
- // Linux 系统
- // 读取 /proc/meminfo,效率远高于 shell_exec('cat ...')
- $memInfo = @file_get_contents('/proc/meminfo');
- if ($memInfo) {
- // 使用正则提取 MemTotal 和 MemAvailable (单位 kB)
- // MemAvailable 是较新的内核指标,比单纯的 MemFree 更准确(包含可回收的缓存)
- if (preg_match('/^MemTotal:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
- $totalMem = floatval($matches[1]) * 1024;
- }
- if (preg_match('/^MemAvailable:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
- $freeMem = floatval($matches[1]) * 1024;
- } else {
- // 如果内核太老没有 MemAvailable,退化使用 MemFree
- if (preg_match('/^MemFree:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
- $freeMem = floatval($matches[1]) * 1024;
- }
- }
- }
- }
- // 计算已用内存
- $usedMem = $totalMem - $freeMem;
- // 避免除以0
- $rate = ($totalMem > 0) ? ($usedMem / $totalMem) * 100 : 0;
- // PHP 自身占用
- $phpMem = memory_get_usage(true);
- return [
- // 人类可读格式 (String)
- 'total' => $this->formatBytes($totalMem),
- 'free' => $this->formatBytes($freeMem),
- 'used' => $this->formatBytes($usedMem),
- 'php' => $this->formatBytes($phpMem),
- 'rate' => sprintf('%.2f', $rate) . '%',
- // 原始数值 (Float/Int),方便前端图表使用或逻辑判断,统一单位 Bytes
- 'raw' => [
- 'total' => $totalMem,
- 'free' => $freeMem,
- 'used' => $usedMem,
- 'php' => $phpMem,
- 'rate' => round($rate, 2)
- ]
- ];
- }
- /**
- * 获取PHP及环境信息
- * @return array
- */
- public function getPhpAndEnvInfo(): array
- {
- return [
- 'php_version' => PHP_VERSION,
- 'os' => PHP_OS,
- 'project_path' => BASE_PATH,
- 'memory_limit' => ini_get('memory_limit'),
- 'max_execution_time' => ini_get('max_execution_time'),
- 'error_reporting' => ini_get('error_reporting'),
- 'display_errors' => ini_get('display_errors'),
- 'upload_max_filesize' => ini_get('upload_max_filesize'),
- 'post_max_size' => ini_get('post_max_size'),
- 'extension_dir' => ini_get('extension_dir'),
- 'loaded_extensions' => implode(', ', get_loaded_extensions()),
- ];
- }
- /**
- * 获取磁盘信息
- * @return array
- */
- public function getDiskInfo(): array
- {
- $disk = [];
- if (stristr(PHP_OS, 'WIN')) {
- // Windows 系统
- // 使用 CSV 格式输出,避免空格解析错误;SkipTop=1 跳过空行
- // LogicalDisk 包含: Caption(盘符), FreeSpace(剩余字节), Size(总字节)
- $cmd = 'wmic logicaldisk get Caption,FreeSpace,Size /format:csv';
- $output = shell_exec($cmd);
- // 转换编码,防止中文乱码(视服务器环境而定,通常 Windows CMD 输出为 GBK)
- $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');
- $lines = explode("\n", trim($output));
- foreach ($lines as $line) {
- $line = trim($line);
- if (empty($line)) continue;
- // CSV 格式: Node,Caption,FreeSpace,Size
- $parts = str_getcsv($line);
- // 确保数据列足够且是一个盘符 (例如 "C:")
- // 索引通常是: 1=>Caption, 2=>FreeSpace, 3=>Size (索引0通常是计算机名)
- if (count($parts) >= 4 && preg_match('/^[A-Z]:$/', $parts[1])) {
- $caption = $parts[1];
- $freeSpace = floatval($parts[2]);
- $totalSize = floatval($parts[3]);
- // 避免除以 0 错误(如光驱未放入光盘时 Size 可能为 0 或 null)
- if ($totalSize <= 0) continue;
- $usedSpace = $totalSize - $freeSpace;
- $disk[] = [
- 'filesystem' => $caption,
- 'mounted_on' => $caption,
- 'size' => $this->formatBytes($totalSize),
- 'available' => $this->formatBytes($freeSpace),
- 'used' => $this->formatBytes($usedSpace),
- 'use_percentage' => sprintf('%.2f', ($usedSpace / $totalSize) * 100) . '%',
- 'raw' => [ // 保留原始数据以便前端或其他逻辑使用
- 'size' => $totalSize,
- 'available' => $freeSpace,
- 'used' => $usedSpace
- ]
- ];
- }
- }
- } else {
- // Linux 系统
- // -P: POSIX 输出格式(强制在一行显示,防止长挂载点换行)
- // -T: 显示文件系统类型
- // 默认单位是 1K-blocks (1024字节)
- $output = shell_exec('df -TP 2>/dev/null');
- $lines = explode("\n", trim($output ?? ''));
- // 过滤表头
- array_shift($lines);
- foreach ($lines as $line) {
- $line = trim($line);
- if (empty($line)) continue;
- // 限制分割数量,防止挂载点名称中有空格导致解析错位(虽然 -P 很大程度避免了这个问题,但仍需谨慎)
- $parts = preg_split('/\s+/', $line);
- // df -TP 输出列: Filesystem(0), Type(1), 1024-blocks(2), Used(3), Available(4), Capacity(5), Mounted on(6)
- if (count($parts) >= 7) {
- $filesystem = $parts[0];
- $type = $parts[1];
- $totalKB = floatval($parts[2]); // 单位是 KB
- $usedKB = floatval($parts[3]);
- $availKB = floatval($parts[4]);
- $mountedOn = $parts[6];
- // 过滤逻辑:只显示物理硬盘或特定挂载点
- // 通常过滤掉 tmpfs, devtmpfs, overlay, squashfs(snap) 等
- // 如果你只想看 /dev/ 开头的物理盘,保留原来的正则即可
- if (!preg_match('/^\/dev\//', $filesystem)) {
- // continue; // 根据需求决定是否取消注释此行
- }
- // 过滤掉 Docker overlay 或 kubelet 等产生的繁杂挂载
- if (strpos($filesystem, 'overlay') !== false) continue;
- // 转换为字节
- $totalSize = $totalKB * 1024;
- $usedSize = $usedKB * 1024;
- $freeSize = $availKB * 1024;
- if ($totalSize <= 0) continue;
- $disk[] = [
- 'filesystem' => $filesystem,
- 'type' => $type,
- 'mounted_on' => $mountedOn,
- 'size' => $this->formatBytes($totalSize),
- 'available' => $this->formatBytes($freeSize),
- 'used' => $this->formatBytes($usedSize),
- 'use_percentage' => sprintf('%.2f', ($usedSize / $totalSize) * 100) . '%',
- 'raw' => [
- 'size' => $totalSize,
- 'available' => $freeSize,
- 'used' => $usedSize
- ]
- ];
- }
- }
- }
- return $disk;
- }
- /**
- * 格式化字节为可读格式 (B, KB, MB, GB, TB...)
- * @param int|float $bytes 字节数
- * @param int $precision 小数点后保留位数
- * @return string
- */
- private function formatBytes($bytes, int $precision = 2): string
- {
- if ($bytes <= 0) {
- return '0 B';
- }
- $base = log($bytes, 1024);
- $suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
- // 确保不会数组越界
- $class = min((int)floor($base), count($suffixes) - 1);
- return sprintf("%." . $precision . "f", $bytes / pow(1024, $class)) . ' ' . $suffixes[$class];
- }
- }
|