Files
2026-08-31 11:33:59 +08:00

389 lines
14 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
namespace core\monitor;
/**
* 系统监控服务类
*
* @author Mr.April
* @since 1.0
*/
class ServerMonitor
{
/**
* 获取磁盘信息
*
* @return array
*/
public function getDiskInfo(): array
{
$disk = [];
if (stristr(PHP_OS, 'WIN')) {
// Windows 系统
$drives = shell_exec('wmic logicaldisk get size,freespace,caption');
$lines = explode("\n", trim($drives ?? ''));
foreach ($lines as $line) {
if (preg_match('/^([A-Z]:)/', $line, $matches)) {
$parts = preg_split('/\s+/', trim($line));
if (count($parts) >= 3) {
$disk[] = [
'filesystem' => $matches[1],
'size' => $this->formatBytes(intval($parts[1])),
'available' => $this->formatBytes(intval($parts[2])),
'used' => $this->formatBytes(intval($parts[1]) - intval($parts[2])),
'use_percentage' => sprintf('%.2f', (intval($parts[1]) - intval($parts[2])) / intval($parts[1]) * 100) . '%',
'mounted_on' => $matches[1],
];
}
}
}
} else {
// Linux 系统
$diskInfo = shell_exec('df -h');
$lines = explode("\n", trim($diskInfo ?? ''));
foreach ($lines as $line) {
if (preg_match('/^\/dev\/\w+/', $line)) {
$parts = preg_split('/\s+/', $line);
$disk[] = [
'filesystem' => $parts[0],
'size' => $parts[1],
'used' => $parts[2],
'available' => $parts[3],
'use_percentage' => $parts[4],
'mounted_on' => $parts[5],
];
}
}
}
return $disk;
}
/**
* 获取cpu信息
*
* @return array
*/
public function getCpuInfo(): array
{
try {
if (PHP_OS == 'Linux') {
$cpu = $this->getCpuUsage();
preg_match('/(\d+)/', shell_exec('cat /proc/cpuinfo | grep "cache size"') ?? '', $cache);
if (count($cache) == 0) {
// aarch64 有可能是arm架构
$cache = trim(shell_exec("lscpu | grep L3 | awk '{print \$NF}'") ?? '');
if ($cache == '') {
$cache = trim(shell_exec("lscpu | grep L2 | awk '{print \$NF}'") ?? '');
}
if ($cache != '') {
$cache = [0, intval(str_replace(['K', 'B'], '', strtoupper($cache)))];
}
}
} else {
$cpu = shell_exec('wmic cpu get LoadPercentage | findstr /V "LoadPercentage"');
$cpu = intval(trim($cpu ?? '0'));
$cache = shell_exec('wmic cpu get L3CacheSize | findstr /V "L3CacheSize"');
$cache = trim($cache ?? '');
if ($cache == '') {
$cache = shell_exec('wmic cpu get L2CacheSize | findstr /V "L2CacheSize"');
$cache = trim($cache ?? '');
}
if ($cache != '') {
$cache = [0, intval($cache) * 1024];
}
}
return [
'cpu_name' => $this->getCpuName(), // CPU 名称
'physical_cores' => $this->getCpuPhysicsCores(), // 物理核心数
'logical_cores' => $this->getCpuLogicCores(), // 逻辑核心数
'cache_size_mb' => $cache[1] ? $cache[1] / 1024 : 0, // 缓存大小(MB)
'cpu_usage_percentage' => $cpu, // CPU 使用率(%)
'free_cpu_percentage' => round(100 - $cpu, 2), // 可用 CPU 百分比(%)
];
} catch (\Exception $e) {
return [
'cpu_name' => '获取失败',
'physical_cores' => '获取失败',
'logical_cores' => '获取失败',
'cache_size_mb' => '获取失败',
'cpu_usage_percentage' => '获取失败',
'free_cpu_percentage' => '获取失败',
];
}
}
/**
* 获取内存信息
*
* @return array
*/
public function getMemoryInfo(): array
{
$result = [];
if (stristr(PHP_OS, 'WIN')) {
// Windows 系统
$cap = shell_exec('wmic Path Win32_PhysicalMemory Get Capacity | findstr /V "Capacity"');
$caps = array_filter(array_map('trim', explode("\n", $cap)));
$result['total_memory'] = round(array_sum(array_map('intval', $caps)) / 1024 / 1024 / 1024, 2); // 总内存(GB)
$free = shell_exec('wmic OS get FreePhysicalMemory | findstr /V "FreePhysicalMemory"');
$result['available_memory'] = round(intval(trim($free)) / 1024, 2); // 可用内存(MB)
$result['used_memory'] = round($result['total_memory'] - $result['available_memory'], 2); // 使用内存(GB)
$result['php_memory_usage'] = round(memory_get_usage(true) / 1024 / 1024, 2); // PHP 使用内存(MB)
} else {
// Linux 系统
$memInfo = shell_exec('cat /proc/meminfo');
$memInfoArray = array_filter(array_map('trim', explode("\n", $memInfo)));
$total = $available = 0;
foreach ($memInfoArray as $line) {
if (str_starts_with($line, 'MemTotal:')) {
preg_match('/(\d+)/', $line, $matches);
$total = intval($matches[1]);
}
if (str_starts_with($line, 'MemAvailable:')) {
preg_match('/(\d+)/', $line, $matches);
$available = intval($matches[1]);
}
}
$result['total_memory'] = sprintf('%.2f', $total / 1024 / 1024); // 总内存(GB)
$result['available_memory'] = sprintf('%.2f', $available / 1024 / 1024); // 可用内存(GB)
$result['used_memory'] = sprintf('%.2f', ($total - $available) / 1024 / 1024); // 使用内存(GB)
$result['php_memory_usage'] = round(memory_get_usage(true) / 1024 / 1024, 2); // PHP 使用内存(MB)
}
// 计算内存使用率
$result['memory_usage_rate'] = sprintf('%.2f', ($result['used_memory'] / $result['total_memory']) * 100);
return $result;
}
/**
* 获取Redis详情
*
* @return array
* @throws \RedisException
*/
public function getRedisInfo(): array
{
$config = config('redis.default');
$redis = new \Redis();
$redis->connect($config['host'], $config['port']);
if (!empty($config['password'])) {
$redis->auth($config['password']);
}
$info = $redis->info(); // 获取 Redis 监控信息
return [
'uptime_in_seconds' => $info['uptime_in_seconds'], // 运行时间(秒)
'connected_clients' => $info['connected_clients'], // 连接的客户端数量
'used_memory' => $this->formatBytes($info['used_memory']), // 使用的内存
'memory_fragmentation_ratio' => $info['mem_fragmentation_ratio'], // 内存碎片比率
'total_commands_processed' => $info['total_commands_processed'], // 处理的命令总数
'total_connections_received' => $info['total_connections_received'], // 接收的连接总数
'keyspace_hits' => $info['keyspace_hits'], // 键空间命中次数
'keyspace_misses' => $info['keyspace_misses'], // 键空间未命中次数
'hit_rate' => $this->calculateHitRate($info), // 命中率
'variable' => $info ?? [],
'data' => $this->getRedisKeysAndValues($redis),
];
}
/**
* 获取PHP环境信息
*
* @return array
*/
public function getPhpInfo(): 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()),
];
}
/**
* 扫描redis数据
*
* @param $redis
*
* @return array
*/
private function getRedisKeysAndValues($redis): array
{
$keyValues = [];
$iterator = null;
do {
$keys = $redis->scan($iterator);
foreach ($keys as $key) {
$keyValues[$key] = $redis->get($key);
}
} while ($iterator > 0);
return $keyValues;
}
/**
* 获取CPU名称
*
* @return string
*/
private function getCpuName(): string
{
if (PHP_OS == 'Linux') {
preg_match('/^\s+\d\s+(.+)/', shell_exec('cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c') ?? '', $matches);
if (count($matches) == 0) {
// aarch64 有可能是arm架构
$name = trim(shell_exec("lscpu| grep Architecture | awk '{print $2}'") ?? '');
if ($name != '') {
$mfMhz = trim(shell_exec("lscpu| grep 'MHz' | awk '{print \$NF}' | head -n1") ?? '');
$mfGhz = trim(shell_exec("lscpu| grep 'GHz' | awk '{print \$NF}' | head -n1") ?? '');
if ($mfMhz == '' && $mfGhz == '') {
return $name;
} else if ($mfGhz != '') {
return $name . ' @ ' . $mfGhz . 'GHz';
} else if ($mfMhz != '') {
return $name . ' @ ' . round(intval($mfMhz) / 1000, 2) . 'GHz';
}
} else {
return '未知';
}
}
return $matches[1] ?? "未知";
} else {
$name = shell_exec('wmic cpu get Name | findstr /V "Name"');
return trim($name);
}
}
/**
* 获取cpu物理核心数
*
* @return string
*/
private function getCpuPhysicsCores(): string
{
if (PHP_OS == 'Linux') {
$num = str_replace("\n", '', shell_exec('cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l'));
return intval($num) == 0 ? '1' : $num;
} else {
$num = shell_exec('wmic cpu get NumberOfCores | findstr /V "NumberOfCores"');
$num = trim($num ?? '1');
$nums = explode("\n", $num);
$num = 0;
foreach ($nums as $n) {
$num += intval(trim($n));
}
return strval($num);
}
}
/**
* 获取cpu逻辑核心数
*
* @return string
*/
private function getCpuLogicCores(): string
{
if (PHP_OS == 'Linux') {
return str_replace("\n", '', shell_exec('cat /proc/cpuinfo |grep "processor"|wc -l'));
} else {
$num = shell_exec('wmic cpu get NumberOfLogicalProcessors | findstr /V "NumberOfLogicalProcessors"');
$num = trim($num ?? '1');
$nums = explode("\n", $num);
$num = 0;
foreach ($nums as $n) {
$num += intval(trim($n));
}
return strval($num);
}
}
/**
* 获取CPU使用率
*
* @return string
*/
private function getCpuUsage(): string
{
$start = $this->calculationCpu();
sleep(1);
$end = $this->calculationCpu();
$totalStart = $start['total'];
$totalEnd = $end['total'];
$timeStart = $start['time'];
$timeEnd = $end['time'];
return sprintf('%.2f', ($timeEnd - $timeStart) / ($totalEnd - $totalStart) * 100);
}
/**
* 计算CPU
*
* @return array
*/
private function calculationCpu(): array
{
$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]+)/';
$string = shell_exec('cat /proc/stat | grep cpu');
preg_match_all($mode, $string, $matches);
$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];
$time = $matches[2][0] + $matches[3][0] + $matches[4][0] + $matches[6][0] + $matches[7][0] + $matches[8][0] + $matches[9][0];
return ['total' => $total, 'time' => $time];
}
/**
* 格式化字节可读格式
*
* @param $bytes
* @param $decimals
*
* @return string
*/
private function formatBytes($bytes, $decimals = 2): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$units[$factor];
}
/**
* 计算Redis命中率
*
* @param array $info
*
* @return float
*/
private function calculateHitRate(array $info): float
{
if ($info['keyspace_hits'] + $info['keyspace_misses'] == 0) {
return 0; // 避免除以零
}
return sprintf('%.2f', ($info['keyspace_hits'] / ($info['keyspace_hits'] + $info['keyspace_misses'])) * 100); // 命中率(%)
}
}