ServerMonitor.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. private $cache = [];
  14. private $cacheTime = 2; // 缓存时间(秒)
  15. /**
  16. * 获取缓存数据
  17. * @param string $key
  18. * @return mixed|null
  19. */
  20. private function getCache(string $key)
  21. {
  22. if (isset($this->cache[$key]) && (time() - $this->cache[$key]['time']) < $this->cacheTime) {
  23. return $this->cache[$key]['data'];
  24. }
  25. return null;
  26. }
  27. /**
  28. * 设置缓存数据
  29. * @param string $key
  30. * @param mixed $data
  31. */
  32. private function setCache(string $key, $data)
  33. {
  34. $this->cache[$key] = [
  35. 'time' => time(),
  36. 'data' => $data
  37. ];
  38. }
  39. /**
  40. * 获取Windows系统信息
  41. * @return array
  42. */
  43. private function getWindowsSystemInfo(): array
  44. {
  45. $cacheKey = 'windows_system_info';
  46. $cached = $this->getCache($cacheKey);
  47. if ($cached !== null) {
  48. return $cached;
  49. }
  50. // 设置默认值
  51. $defaultData = [
  52. 'cpu_usage' => 0,
  53. 'cpu_name' => 'Unknown',
  54. 'cpu_cores' => 1,
  55. 'cpu_logical_cores' => 1,
  56. 'cpu_l3_cache' => 0,
  57. 'cpu_l2_cache' => 0,
  58. 'total_memory' => 0,
  59. 'available_memory' => 0
  60. ];
  61. try {
  62. $data = $defaultData;
  63. $usePowerShell = true;
  64. // 尝试使用 PowerShell 获取信息
  65. if ($usePowerShell) {
  66. // 使用 PowerShell 获取 CPU 信息
  67. $cpuInfo = shell_exec('powershell -NoProfile -NonInteractive -Command "Get-WmiObject -Class Win32_Processor | Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, L2CacheSize, L3CacheSize | ConvertTo-Json"');
  68. // 使用 PowerShell 获取 CPU 使用率
  69. $cpuUsage = shell_exec('powershell -NoProfile -NonInteractive -Command "Get-Counter -Counter \'\\Processor(_Total)\\% Processor Time\' -SampleInterval 1 -MaxSamples 1 | Select-Object -ExpandProperty CounterSamples | Select-Object -ExpandProperty CookedValue"');
  70. // 检查 PowerShell 命令是否成功
  71. if ($cpuInfo && $cpuUsage !== null) {
  72. $cpuData = json_decode($cpuInfo, true);
  73. if ($cpuData) {
  74. $data['cpu_name'] = $cpuData['Name'] ?? 'Unknown';
  75. $data['cpu_cores'] = intval($cpuData['NumberOfCores'] ?? 1);
  76. $data['cpu_logical_cores'] = intval($cpuData['NumberOfLogicalProcessors'] ?? 1);
  77. $data['cpu_l2_cache'] = intval($cpuData['L2CacheSize'] ?? 0);
  78. $data['cpu_l3_cache'] = intval($cpuData['L3CacheSize'] ?? 0);
  79. $data['cpu_usage'] = round(floatval($cpuUsage), 2);
  80. }
  81. } else {
  82. $usePowerShell = false;
  83. }
  84. }
  85. // 如果 PowerShell 失败,回退到使用 systeminfo
  86. if (!$usePowerShell) {
  87. $systemInfo = shell_exec('systeminfo');
  88. if ($systemInfo) {
  89. // 解析 CPU 信息
  90. if (preg_match('/Processor\(s\):\s+([^\n]+)/', $systemInfo, $matches)) {
  91. $data['cpu_name'] = trim($matches[1]);
  92. }
  93. if (preg_match('/Number of Processors:\s+(\d+)/', $systemInfo, $matches)) {
  94. $data['cpu_cores'] = intval($matches[1]);
  95. $data['cpu_logical_cores'] = intval($matches[1]);
  96. }
  97. // 获取 CPU 使用率(使用 tasklist 作为备选方案)
  98. $tasklist = shell_exec('tasklist /FI "IMAGENAME eq System" /FO CSV /NH');
  99. if ($tasklist) {
  100. $data['cpu_usage'] = 0; // 暂时设为0,因为 tasklist 不提供 CPU 使用率
  101. }
  102. }
  103. }
  104. // 获取内存信息
  105. $systemInfo = shell_exec('systeminfo');
  106. if ($systemInfo) {
  107. if (preg_match('/Total Physical Memory:\s+([\d,]+)/', $systemInfo, $matches)) {
  108. $memory = str_replace(',', '', $matches[1]);
  109. $data['total_memory'] = intval($memory) * 1024 * 1024; // Convert to bytes
  110. }
  111. if (preg_match('/Available Physical Memory:\s+([\d,]+)/', $systemInfo, $matches)) {
  112. $memory = str_replace(',', '', $matches[1]);
  113. $data['available_memory'] = intval($memory) * 1024 * 1024; // Convert to bytes
  114. }
  115. }
  116. // 验证数据
  117. foreach ($data as $key => $value) {
  118. if ($key !== 'cpu_name' && (!is_numeric($value) || $value < 0)) {
  119. $data[$key] = $defaultData[$key];
  120. }
  121. }
  122. $this->setCache($cacheKey, $data);
  123. return $data;
  124. } catch (\Throwable $e) {
  125. error_log("ServerMonitor error: " . $e->getMessage());
  126. }
  127. return $defaultData;
  128. }
  129. /**
  130. * 获取cpu信息
  131. * @return array
  132. */
  133. public function getCpuInfo(): array
  134. {
  135. try {
  136. if (PHP_OS == 'Linux') {
  137. $cpu = $this->getCpuUsage();
  138. preg_match('/(\d+)/', shell_exec('cat /proc/cpuinfo | grep "cache size"') ?? '', $cache);
  139. if (count($cache) == 0) {
  140. // aarch64 有可能是arm架构
  141. $cache = trim(shell_exec("lscpu | grep L3 | awk '{print \$NF}'") ?? '');
  142. if ($cache == '') {
  143. $cache = trim(shell_exec("lscpu | grep L2 | awk '{print \$NF}'") ?? '');
  144. }
  145. if ($cache != '') {
  146. $cache = [0, intval(str_replace(['K', 'B'], '', strtoupper($cache)))];
  147. }
  148. }
  149. } elseif (PHP_OS == 'Darwin') { // macOS
  150. $cpu = trim(shell_exec("ps -A -o %cpu | awk '{s+=$1} END {print s}'"));
  151. $cpu = sprintf("%.2f",intval($cpu) / shell_exec("sysctl -n hw.ncpu"));
  152. $cache = shell_exec("sysctl -n hw.l3cachesize");
  153. if ($cache == '') {
  154. $cache = shell_exec("sysctl -n hw.l2cachesize");
  155. }
  156. if ($cache != '') {
  157. $cache = [0, intval($cache)];
  158. }
  159. } else {
  160. $info = $this->getWindowsSystemInfo();
  161. $cache = $info['cpu_l3_cache'] ?: $info['cpu_l2_cache'];
  162. return [
  163. 'name' => $info['cpu_name'],
  164. 'cores' => '物理核心数:' . $info['cpu_cores'] . '个,逻辑核心数:' . $info['cpu_logical_cores'] . '个',
  165. 'cache' => $cache ? $cache / 1024 : 0,
  166. 'usage' => $info['cpu_usage'],
  167. 'free' => sprintf("%.2f", round(100 - $info['cpu_usage'], 2))
  168. ];
  169. }
  170. return [
  171. 'name' => $this->getCpuName(),
  172. 'cores' => '物理核心数:' . $this->getCpuPhysicsCores() . '个,逻辑核心数:' . $this->getCpuLogicCores() . '个',
  173. 'cache' => $cache[1] ? $cache[1] / 1024 : 0,
  174. 'usage' => $cpu,
  175. 'free' => sprintf("%.2f",round(100 - $cpu, 2))
  176. ];
  177. } catch (\Throwable $e) {
  178. $res = '无法获取';
  179. echo $e->getMessage(), "\n";
  180. return [
  181. 'name' => $res,
  182. 'cores' => $res,
  183. 'cache' => $res,
  184. 'usage' => $res,
  185. 'free' => $res,
  186. ];
  187. }
  188. }
  189. /**
  190. * 获取CPU名称
  191. * @return string
  192. */
  193. public function getCpuName(): string
  194. {
  195. if (PHP_OS == 'Linux') {
  196. preg_match('/^\s+\d\s+(.+)/', shell_exec('cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c') ?? '', $matches);
  197. if (count($matches) == 0) {
  198. // aarch64 有可能是arm架构
  199. $name = trim(shell_exec("lscpu| grep Architecture | awk '{print $2}'") ?? '');
  200. if ($name != '') {
  201. $mfMhz = trim(shell_exec("lscpu| grep 'MHz' | awk '{print \$NF}' | head -n1") ?? '');
  202. $mfGhz = trim(shell_exec("lscpu| grep 'GHz' | awk '{print \$NF}' | head -n1") ?? '');
  203. if ($mfMhz == '' && $mfGhz == '') {
  204. return $name;
  205. } else if ($mfGhz != '') {
  206. return $name . ' @ ' . $mfGhz . 'GHz';
  207. } else if ($mfMhz != '') {
  208. return $name . ' @ ' . round(intval($mfMhz) / 1000, 2) . 'GHz';
  209. }
  210. } else {
  211. return '未知';
  212. }
  213. }
  214. return $matches[1] ?? "未知";
  215. } elseif (PHP_OS == 'Darwin') { // macOS
  216. $name = shell_exec("sysctl -n machdep.cpu.brand_string");
  217. return trim($name);
  218. } else {
  219. $info = $this->getWindowsSystemInfo();
  220. return $info['cpu_name'];
  221. }
  222. }
  223. /**
  224. * 获取cpu物理核心数
  225. */
  226. public function getCpuPhysicsCores(): string
  227. {
  228. if (PHP_OS == 'Linux') {
  229. $num = str_replace("\n", '', shell_exec('cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l'));
  230. return intval($num) == 0 ? '1' : $num;
  231. } elseif (PHP_OS == 'Darwin') { // macOS
  232. $num = shell_exec('sysctl -n hw.physicalcpu');
  233. return trim(strval($num));
  234. } else {
  235. $info = $this->getWindowsSystemInfo();
  236. return strval($info['cpu_cores']);
  237. }
  238. }
  239. /**
  240. * 获取cpu逻辑核心数
  241. */
  242. public function getCpuLogicCores(): string
  243. {
  244. if (PHP_OS == 'Linux') {
  245. return str_replace("\n", '', shell_exec('cat /proc/cpuinfo |grep "processor"|wc -l'));
  246. } elseif (PHP_OS == 'Darwin') { // macOS
  247. return trim(strval(shell_exec('sysctl -n hw.logicalcpu')));
  248. } else {
  249. $info = $this->getWindowsSystemInfo();
  250. return strval($info['cpu_logical_cores']);
  251. }
  252. }
  253. /**
  254. * 获取CPU使用率
  255. * @return string
  256. */
  257. public function getCpuUsage(): string
  258. {
  259. if (PHP_OS == 'Linux') {
  260. $start = $this->calculationCpu();
  261. sleep(1);
  262. $end = $this->calculationCpu();
  263. $totalStart = $start['total'];
  264. $totalEnd = $end['total'];
  265. $timeStart = $start['time'];
  266. $timeEnd = $end['time'];
  267. return sprintf('%.2f', ($timeEnd - $timeStart) / ($totalEnd - $totalStart) * 100);
  268. } elseif (PHP_OS == 'Darwin') { // macOS
  269. $usage = shell_exec("ps -A -o %cpu | awk '{s+=$1} END {print s}'");
  270. return sprintf('%.2f', $usage / shell_exec("sysctl -n hw.ncpu"));
  271. } else {
  272. $info = $this->getWindowsSystemInfo();
  273. return sprintf('%.2f', $info['cpu_usage']);
  274. }
  275. }
  276. /**
  277. * 计算CPU
  278. * @return array
  279. */
  280. protected function calculationCpu(): array
  281. {
  282. $mode = '/(cpu)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)/';
  283. $string = shell_exec('cat /proc/stat | grep cpu');
  284. preg_match_all($mode, $string, $matches);
  285. $total = $matches[2][0] + $matches[3][0] + $matches[4][0] + $matches[5][0] + $matches[6][0] + $matches[7][0] + $matches[8][0] + $matches[9][0];
  286. $time = $matches[2][0] + $matches[3][0] + $matches[4][0] + $matches[6][0] + $matches[7][0] + $matches[8][0] + $matches[9][0];
  287. return ['total' => $total, 'time' => $time];
  288. }
  289. /**
  290. * 获取内存信息
  291. * @return array
  292. */
  293. public function getMemInfo(): array
  294. {
  295. if (PHP_OS == 'Linux') {
  296. $string = shell_exec('cat /proc/meminfo | grep MemTotal');
  297. preg_match('/(\d+)/', $string, $total);
  298. $result['total'] = sprintf('%.2f', $total[1] / 1024 / 1024);
  299. $string = shell_exec('cat /proc/meminfo | grep MemAvailable');
  300. preg_match('/(\d+)/', $string, $available);
  301. $result['free'] = sprintf('%.2f', $available[1] / 1024 / 1024);
  302. $result['usage'] = sprintf('%.2f', ($total[1] - $available[1]) / 1024 / 1024);
  303. $result['php'] = round(memory_get_usage() / 1024 / 1024, 2);
  304. $result['rate'] = sprintf(
  305. '%.2f',
  306. (sprintf('%.2f', $result['usage']) / sprintf('%.2f', $result['total'])) * 100
  307. );
  308. } elseif (PHP_OS == 'Darwin') { // macOS
  309. $result['total'] = round(intval(shell_exec('sysctl -n hw.memsize')) / 1024 / 1024 / 1024, 2);
  310. $free = shell_exec('vm_stat | grep "Pages free"');
  311. preg_match('/(\d+)/', $free, $matches);
  312. $result['free'] = round(intval($matches[1]) * 4096 / 1024 / 1024 / 1024, 2);
  313. $result['usage'] = round($result['total'] - $result['free'], 2);
  314. $result['php'] = round(memory_get_usage() / 1024 / 1024, 2);
  315. $result['rate'] = sprintf(
  316. '%.2f',
  317. (sprintf('%.2f', $result['usage']) / sprintf('%.2f', $result['total'])) * 100
  318. );
  319. } else {
  320. $info = $this->getWindowsSystemInfo();
  321. $result['total'] = round($info['total_memory'] / 1024 / 1024 / 1024, 2);
  322. $result['free'] = round($info['available_memory'] / 1024 / 1024 / 1024, 2);
  323. $result['usage'] = round($result['total'] - $result['free'], 2);
  324. $result['php'] = round(memory_get_usage() / 1024 / 1024, 2);
  325. // 防止除以0错误
  326. if ($result['total'] > 0) {
  327. $result['rate'] = sprintf(
  328. '%.2f',
  329. (sprintf('%.2f', $result['usage']) / sprintf('%.2f', $result['total'])) * 100
  330. );
  331. } else {
  332. $result['rate'] = '0.00';
  333. }
  334. return $result;
  335. }
  336. return $result;
  337. }
  338. /**
  339. * 获取PHP及环境信息
  340. * @return array
  341. */
  342. public function getPhpAndEnvInfo(): array
  343. {
  344. $result['php_version'] = PHP_VERSION;
  345. $result['os'] = PHP_OS_FAMILY;
  346. $result['project_path'] = BASE_PATH;
  347. return $result;
  348. }
  349. }