ServerMonitor.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | saiadmin [ saiadmin快速开发框架 ]
  4. // +----------------------------------------------------------------------
  5. // | Author: sai <1430792918@qq.com>
  6. // +----------------------------------------------------------------------
  7. namespace plugin\saiadmin\utils;
  8. /**
  9. * 服务器监控信息
  10. */
  11. class ServerMonitor
  12. {
  13. /**
  14. * 获取内存信息
  15. * @return array
  16. */
  17. public function getMemoryInfo(): array
  18. {
  19. $totalMem = 0; // 总内存 (Bytes)
  20. $freeMem = 0; // 可用/剩余内存 (Bytes)
  21. if (stristr(PHP_OS, 'WIN')) {
  22. // Windows 系统
  23. // 一次性获取 总可见内存 和 空闲物理内存 (单位都是 KB)
  24. // TotalVisibleMemorySize: 操作系统可识别的内存总数 (比物理内存条总数略少,更准确反映可用上限)
  25. // FreePhysicalMemory: 当前可用物理内存
  26. $cmd = 'wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /format:csv';
  27. $output = shell_exec($cmd);
  28. $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');
  29. $lines = explode("\n", trim($output));
  30. foreach ($lines as $line) {
  31. $line = trim($line);
  32. if (empty($line)) continue;
  33. // CSV 格式: Node,FreePhysicalMemory,TotalVisibleMemorySize
  34. $parts = str_getcsv($line);
  35. // 确保解析正确且排除标题行 (通常索引1是Free, 2是Total)
  36. if (count($parts) >= 3 && is_numeric($parts[1])) {
  37. $freeMem = floatval($parts[1]) * 1024; // KB -> Bytes
  38. $totalMem = floatval($parts[2]) * 1024; // KB -> Bytes
  39. break;
  40. }
  41. }
  42. } else {
  43. // Linux 系统
  44. // 读取 /proc/meminfo,效率远高于 shell_exec('cat ...')
  45. $memInfo = @file_get_contents('/proc/meminfo');
  46. if ($memInfo) {
  47. // 使用正则提取 MemTotal 和 MemAvailable (单位 kB)
  48. // MemAvailable 是较新的内核指标,比单纯的 MemFree 更准确(包含可回收的缓存)
  49. if (preg_match('/^MemTotal:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
  50. $totalMem = floatval($matches[1]) * 1024;
  51. }
  52. if (preg_match('/^MemAvailable:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
  53. $freeMem = floatval($matches[1]) * 1024;
  54. } else {
  55. // 如果内核太老没有 MemAvailable,退化使用 MemFree
  56. if (preg_match('/^MemFree:\s+(\d+)\s+kB/m', $memInfo, $matches)) {
  57. $freeMem = floatval($matches[1]) * 1024;
  58. }
  59. }
  60. }
  61. }
  62. // 计算已用内存
  63. $usedMem = $totalMem - $freeMem;
  64. // 避免除以0
  65. $rate = ($totalMem > 0) ? ($usedMem / $totalMem) * 100 : 0;
  66. // PHP 自身占用
  67. $phpMem = memory_get_usage(true);
  68. return [
  69. // 人类可读格式 (String)
  70. 'total' => $this->formatBytes($totalMem),
  71. 'free' => $this->formatBytes($freeMem),
  72. 'used' => $this->formatBytes($usedMem),
  73. 'php' => $this->formatBytes($phpMem),
  74. 'rate' => sprintf('%.2f', $rate) . '%',
  75. // 原始数值 (Float/Int),方便前端图表使用或逻辑判断,统一单位 Bytes
  76. 'raw' => [
  77. 'total' => $totalMem,
  78. 'free' => $freeMem,
  79. 'used' => $usedMem,
  80. 'php' => $phpMem,
  81. 'rate' => round($rate, 2)
  82. ]
  83. ];
  84. }
  85. /**
  86. * 获取PHP及环境信息
  87. * @return array
  88. */
  89. public function getPhpAndEnvInfo(): array
  90. {
  91. return [
  92. 'php_version' => PHP_VERSION,
  93. 'os' => PHP_OS,
  94. 'project_path' => BASE_PATH,
  95. 'memory_limit' => ini_get('memory_limit'),
  96. 'max_execution_time' => ini_get('max_execution_time'),
  97. 'error_reporting' => ini_get('error_reporting'),
  98. 'display_errors' => ini_get('display_errors'),
  99. 'upload_max_filesize' => ini_get('upload_max_filesize'),
  100. 'post_max_size' => ini_get('post_max_size'),
  101. 'extension_dir' => ini_get('extension_dir'),
  102. 'loaded_extensions' => implode(', ', get_loaded_extensions()),
  103. ];
  104. }
  105. /**
  106. * 获取磁盘信息
  107. * @return array
  108. */
  109. public function getDiskInfo(): array
  110. {
  111. $disk = [];
  112. if (stristr(PHP_OS, 'WIN')) {
  113. // Windows 系统
  114. // 使用 CSV 格式输出,避免空格解析错误;SkipTop=1 跳过空行
  115. // LogicalDisk 包含: Caption(盘符), FreeSpace(剩余字节), Size(总字节)
  116. $cmd = 'wmic logicaldisk get Caption,FreeSpace,Size /format:csv';
  117. $output = shell_exec($cmd);
  118. // 转换编码,防止中文乱码(视服务器环境而定,通常 Windows CMD 输出为 GBK)
  119. $output = mb_convert_encoding($output ?? '', 'UTF-8', 'GBK, UTF-8, ASCII');
  120. $lines = explode("\n", trim($output));
  121. foreach ($lines as $line) {
  122. $line = trim($line);
  123. if (empty($line)) continue;
  124. // CSV 格式: Node,Caption,FreeSpace,Size
  125. $parts = str_getcsv($line);
  126. // 确保数据列足够且是一个盘符 (例如 "C:")
  127. // 索引通常是: 1=>Caption, 2=>FreeSpace, 3=>Size (索引0通常是计算机名)
  128. if (count($parts) >= 4 && preg_match('/^[A-Z]:$/', $parts[1])) {
  129. $caption = $parts[1];
  130. $freeSpace = floatval($parts[2]);
  131. $totalSize = floatval($parts[3]);
  132. // 避免除以 0 错误(如光驱未放入光盘时 Size 可能为 0 或 null)
  133. if ($totalSize <= 0) continue;
  134. $usedSpace = $totalSize - $freeSpace;
  135. $disk[] = [
  136. 'filesystem' => $caption,
  137. 'mounted_on' => $caption,
  138. 'size' => $this->formatBytes($totalSize),
  139. 'available' => $this->formatBytes($freeSpace),
  140. 'used' => $this->formatBytes($usedSpace),
  141. 'use_percentage' => sprintf('%.2f', ($usedSpace / $totalSize) * 100) . '%',
  142. 'raw' => [ // 保留原始数据以便前端或其他逻辑使用
  143. 'size' => $totalSize,
  144. 'available' => $freeSpace,
  145. 'used' => $usedSpace
  146. ]
  147. ];
  148. }
  149. }
  150. } else {
  151. // Linux 系统
  152. // -P: POSIX 输出格式(强制在一行显示,防止长挂载点换行)
  153. // -T: 显示文件系统类型
  154. // 默认单位是 1K-blocks (1024字节)
  155. $output = shell_exec('df -TP 2>/dev/null');
  156. $lines = explode("\n", trim($output ?? ''));
  157. // 过滤表头
  158. array_shift($lines);
  159. foreach ($lines as $line) {
  160. $line = trim($line);
  161. if (empty($line)) continue;
  162. // 限制分割数量,防止挂载点名称中有空格导致解析错位(虽然 -P 很大程度避免了这个问题,但仍需谨慎)
  163. $parts = preg_split('/\s+/', $line);
  164. // df -TP 输出列: Filesystem(0), Type(1), 1024-blocks(2), Used(3), Available(4), Capacity(5), Mounted on(6)
  165. if (count($parts) >= 7) {
  166. $filesystem = $parts[0];
  167. $type = $parts[1];
  168. $totalKB = floatval($parts[2]); // 单位是 KB
  169. $usedKB = floatval($parts[3]);
  170. $availKB = floatval($parts[4]);
  171. $mountedOn = $parts[6];
  172. // 过滤逻辑:只显示物理硬盘或特定挂载点
  173. // 通常过滤掉 tmpfs, devtmpfs, overlay, squashfs(snap) 等
  174. // 如果你只想看 /dev/ 开头的物理盘,保留原来的正则即可
  175. if (!preg_match('/^\/dev\//', $filesystem)) {
  176. // continue; // 根据需求决定是否取消注释此行
  177. }
  178. // 过滤掉 Docker overlay 或 kubelet 等产生的繁杂挂载
  179. if (strpos($filesystem, 'overlay') !== false) continue;
  180. // 转换为字节
  181. $totalSize = $totalKB * 1024;
  182. $usedSize = $usedKB * 1024;
  183. $freeSize = $availKB * 1024;
  184. if ($totalSize <= 0) continue;
  185. $disk[] = [
  186. 'filesystem' => $filesystem,
  187. 'type' => $type,
  188. 'mounted_on' => $mountedOn,
  189. 'size' => $this->formatBytes($totalSize),
  190. 'available' => $this->formatBytes($freeSize),
  191. 'used' => $this->formatBytes($usedSize),
  192. 'use_percentage' => sprintf('%.2f', ($usedSize / $totalSize) * 100) . '%',
  193. 'raw' => [
  194. 'size' => $totalSize,
  195. 'available' => $freeSize,
  196. 'used' => $usedSize
  197. ]
  198. ];
  199. }
  200. }
  201. }
  202. return $disk;
  203. }
  204. /**
  205. * 格式化字节为可读格式 (B, KB, MB, GB, TB...)
  206. * @param int|float $bytes 字节数
  207. * @param int $precision 小数点后保留位数
  208. * @return string
  209. */
  210. private function formatBytes($bytes, int $precision = 2): string
  211. {
  212. if ($bytes <= 0) {
  213. return '0 B';
  214. }
  215. $base = log($bytes, 1024);
  216. $suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
  217. // 确保不会数组越界
  218. $class = min((int)floor($base), count($suffixes) - 1);
  219. return sprintf("%." . $precision . "f", $bytes / pow(1024, $class)) . ' ' . $suffixes[$class];
  220. }
  221. }