389 lines
14 KiB
PHP
389 lines
14 KiB
PHP
<?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); // 命中率(%)
|
||
}
|
||
|
||
}
|