FreeBSD 不使用 /proc,而是通過 sysctl 命令獲取內核參數,並結合 vmstat、swapinfo 等工具獲取運行時指標。
<?php
/**
* FreeBSD 系統信息採集器
* 需 PHP 有權限執行 sysctl、vmstat、swapinfo 命令
*/
class FreeBsdSysInfo {
public static function get(): ?array {
if (stripos(PHP_OS, 'FreeBSD') === false) {
return null;
}
$info = [];
// CPU
$info['cpu'] = self::getCpuInfo();
// 負載
$info['load'] = self::getLoadAvg();
// 運行時間
$info['uptime'] = self::getUptime();
// 內存
$info['memory'] = self::getMemoryInfo();
return $info;
}
private static function sysctl(string $key): ?string {
$cmd = "sysctl -n " . escapeshellarg($key);
$output = @shell_exec($cmd);
return $output ? trim($output) : null;
}
private static function getCpuInfo(): array {
$ncpu = self::sysctl('hw.ncpu');
$model = self::sysctl('hw.model');
return [
'cores' => $ncpu ? (int)$ncpu : 0,
'model' => $model ?: 'Unknown',
];
}
private static function getLoadAvg(): array {
$load = self::sysctl('vm.loadavg');
if (!$load) return ['error' => '無法獲取負載'];
// 格式:{ 0.52 0.48 0.45 }
$load = trim($load, '{}');
$parts = array_map('floatval', explode(' ', $load));
return [
'1min' => $parts[0] ?? 0,
'5min' => $parts[1] ?? 0,
'15min' => $parts[2] ?? 0,
];
}
private static function getUptime(): array {
$boottime = self::sysctl('kern.boottime');
if (!$boottime) return ['error' => '無法獲取啟動時間'];
// 格式:{ sec = 1700000000, usec = 123456 } Thu Jan 1 00:00:00 2024
if (preg_match('/sec\s*=\s*(\d+)/', $boottime, $m)) {
$bootSec = (int)$m[1];
$now = time();
$uptimeSec = $now - $bootSec;
$days = floor($uptimeSec / 86400);
$hours = floor(($uptimeSec % 86400) / 3600);
$minutes = floor(($uptimeSec % 3600) / 60);
return [
'seconds' => $uptimeSec,
'human' => sprintf('%d天 %d小時 %d分鐘', $days, $hours, $minutes),
'boot_time' => $bootSec,
];
}
return ['error' => '解析啟動時間失敗'];
}
private static function getMemoryInfo(): array {
// 物理內存
$physmem = self::sysctl('hw.physmem');
$totalMem = $physmem ? round($physmem / 1024 / 1024, 2) : 0; // MB
// vmstat 獲取空閒內存
$vmstat = @shell_exec('vmstat');
$freeMem = 0;
if ($vmstat) {
$lines = explode("\n", trim($vmstat));
if (count($lines) >= 3) {
$fields = preg_split('/\s+/', trim($lines[2]));
// free 列通常是第 5 欄(0-indexed 4)
$freeMem = isset($fields[4]) ? round($fields[4] / 1024, 2) : 0; // MB
}
}
// swapinfo 獲取交換分區
$swapinfo = @shell_exec('swapinfo -k');
$swap = ['total' => 0, 'used' => 0, 'free' => 0, 'percent' => 0];
if ($swapinfo) {
$lines = explode("\n", trim($swapinfo));
if (count($lines) >= 2) {
// 跳過標題行
$fields = preg_split('/\s+/', trim($lines[1]));
// 1K-blocks Used Avail Capacity
if (count($fields) >= 4) {
$swap['total'] = round($fields[0] / 1024, 2); // MB
$swap['used'] = round($fields[1] / 1024, 2);
$swap['free'] = round($fields[2] / 1024, 2);
$swap['percent'] = $swap['total'] > 0 ? round($swap['used'] / $swap['total'] * 100, 2) : 0;
}
}
}
$usedMem = $totalMem - $freeMem;
$memPercent = $totalMem > 0 ? round($usedMem / $totalMem * 100, 2) : 0;
return [
'memory' => [
'total' => $totalMem,
'free' => $freeMem,
'used' => $usedMem,
'percent' => $memPercent,
],
'swap' => $swap,
];
}
}
// 使用示例
$data = FreeBsdSysInfo::get();
if ($data) {
echo '<pre>';
print_r($data);
echo '</pre>';
} else {
echo '非 FreeBSD 環境';
}
?>
| 指標 | Linux (/proc) | FreeBSD (sysctl/命令) |
|---|---|---|
| CPU 核心數 | /proc/cpuinfo | sysctl hw.ncpu |
| CPU 型號 | /proc/cpuinfo | sysctl hw.model |
| 負載 | /proc/loadavg | sysctl vm.loadavg |
| 啟動時間 | /proc/uptime | sysctl kern.boottime |
| 物理內存 | /proc/meminfo | sysctl hw.physmem |
| 空閒內存 | /proc/meminfo | vmstat (free 欄) |
| Swap 信息 | /proc/meminfo | swapinfo -k |
sysctl、vmstat、swapinfo 在 PHP 的 PATH 中(通常在 /usr/sbin,可能需配置 safe_mode_exec_dir 或使用絕對路徑)。若項目同時部署在 Linux 和 FreeBSD,可編寫工廠模式統一調用:
class SysInfoFactory {
public static function create(): ?SysInfoInterface {
if (PHP_OS_FAMILY === 'Linux') return new LinuxSysInfo();
if (stripos(PHP_OS, 'FreeBSD') !== false) return new FreeBsdSysInfo();
return null;
}
}
interface SysInfoInterface {
public static function get(): ?array;
}
// LinuxSysInfo 和 FreeBsdSysInfo 實現此接口