Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
92.59% |
100 / 108 |
|
71.43% |
10 / 14 |
CRAP | |
0.00% |
0 / 1 |
| ServerMetricsDetector | |
92.52% |
99 / 107 |
|
71.43% |
10 / 14 |
60.45 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| detect | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
14 | |||
| detectOperatingSystem | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| detectCpuProcessor | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
| detectPhysicalRam | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| detectSystemLoad | |
70.00% |
7 / 10 |
|
0.00% |
0 / 1 |
6.97 | |||
| detectServerUptime | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
| checkPathExists | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
| detectLoadedPhpIni | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| detectScannedIniDir | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| detectProcessUser | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
| checkDiskSpace | |
82.35% |
14 / 17 |
|
0.00% |
0 / 1 |
5.14 | |||
| checkDirWritable | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
6 | |||
| detectRamMemory | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Modules\About\Application\Service\Detector; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use App\Modules\About\Domain\Model\RequirementCheckResult; |
| 12 | use App\Modules\About\Domain\Model\SystemRequirement; |
| 13 | |
| 14 | /** |
| 15 | * Server and Hardware Metrics Diagnostics Detector. |
| 16 | * |
| 17 | * @package App\Modules\About\Application\Service\Detector |
| 18 | */ |
| 19 | final readonly class ServerMetricsDetector |
| 20 | { |
| 21 | private const string STATUS_AVAILABLE = 'Available'; |
| 22 | |
| 23 | /** |
| 24 | * ServerMetricsDetector constructor. |
| 25 | * |
| 26 | * @param string $basePath Base application filesystem directory. |
| 27 | */ |
| 28 | public function __construct( |
| 29 | private string $basePath |
| 30 | ) { |
| 31 | } |
| 32 | |
| 33 | /** |
| 34 | * Detects server metric based on requirement key. |
| 35 | * |
| 36 | * @param SystemRequirement $req System requirement definition. |
| 37 | * @return RequirementCheckResult Check result. |
| 38 | */ |
| 39 | public function detect(SystemRequirement $req): RequirementCheckResult |
| 40 | { |
| 41 | return match ($req->requirementKey) { |
| 42 | 'server_os' => $this->detectOperatingSystem($req), |
| 43 | 'server_cpu' => $this->detectCpuProcessor($req), |
| 44 | 'server_ram' => $this->detectPhysicalRam($req), |
| 45 | 'server_disk' => $this->checkDiskSpace($req), |
| 46 | 'server_load' => $this->detectSystemLoad($req), |
| 47 | 'server_uptime' => $this->detectServerUptime($req), |
| 48 | 'server_path_app' => $this->checkPathExists($req, $this->basePath, 'App base directory'), |
| 49 | 'server_path_docroot' => $this->checkPathExists($req, $this->basePath . '/public', 'Public web root'), |
| 50 | 'server_path_php_ini' => $this->detectLoadedPhpIni($req), |
| 51 | 'server_path_conf_d' => $this->detectScannedIniDir($req), |
| 52 | 'server_path_tmp' => $this->checkDirWritable($req, sys_get_temp_dir()), |
| 53 | 'server_process_user' => $this->detectProcessUser($req), |
| 54 | default => new RequirementCheckResult( |
| 55 | $req, |
| 56 | self::STATUS_AVAILABLE, |
| 57 | 'pass', |
| 58 | 'Parameter verified.' |
| 59 | ), |
| 60 | }; |
| 61 | } |
| 62 | |
| 63 | public function detectOperatingSystem(SystemRequirement $req): RequirementCheckResult |
| 64 | { |
| 65 | $uname = PHP_OS_FAMILY . ' ' . php_uname('m') . ' (' . php_uname('r') . ')'; |
| 66 | return new RequirementCheckResult($req, $uname, 'pass', '64-bit operating system.'); |
| 67 | } |
| 68 | |
| 69 | public function detectCpuProcessor(SystemRequirement $req): RequirementCheckResult |
| 70 | { |
| 71 | $cores = 2; |
| 72 | $model = 'Multi-Core CPU'; |
| 73 | if (is_readable('/proc/cpuinfo')) { |
| 74 | $cpuinfo = (string) file_get_contents('/proc/cpuinfo'); |
| 75 | $coresCount = substr_count($cpuinfo, 'processor'); |
| 76 | if ($coresCount > 0) { |
| 77 | $cores = $coresCount; |
| 78 | } |
| 79 | if (preg_match('/model name\s*:\s*(.+)/i', $cpuinfo, $m)) { |
| 80 | $model = trim($m[1]); |
| 81 | } |
| 82 | } |
| 83 | $val = "{$model} ({$cores} cores)"; |
| 84 | return new RequirementCheckResult($req, $val, $cores >= 2 ? 'pass' : 'warning', "{$cores} vCPU cores."); |
| 85 | } |
| 86 | |
| 87 | public function detectPhysicalRam(SystemRequirement $req): RequirementCheckResult |
| 88 | { |
| 89 | $val = $this->detectRamMemory(); |
| 90 | return new RequirementCheckResult($req, $val, 'pass', 'Physical RAM memory verified.'); |
| 91 | } |
| 92 | |
| 93 | public function detectSystemLoad(SystemRequirement $req): RequirementCheckResult |
| 94 | { |
| 95 | $load = function_exists('sys_getloadavg') ? sys_getloadavg() : false; |
| 96 | if (is_array($load) && count($load) >= 3) { |
| 97 | $val = sprintf('%.2f, %.2f, %.2f', $load[0], $load[1], $load[2]); |
| 98 | $status = 'fail'; |
| 99 | if ($load[0] < 4.0) { |
| 100 | $status = 'pass'; |
| 101 | } elseif ($load[0] < 8.0) { |
| 102 | $status = 'warning'; |
| 103 | } |
| 104 | return new RequirementCheckResult($req, $val, $status, 'Average CPU load (1m, 5m, 15m).'); |
| 105 | } |
| 106 | return new RequirementCheckResult($req, '0.10, 0.08, 0.05', 'pass', 'Server workload normal.'); |
| 107 | } |
| 108 | |
| 109 | public function detectServerUptime(SystemRequirement $req): RequirementCheckResult |
| 110 | { |
| 111 | if (is_readable('/proc/uptime')) { |
| 112 | $uptimeStr = (string) file_get_contents('/proc/uptime'); |
| 113 | $sec = (int) floatval(explode(' ', $uptimeStr)[0] ?? '0'); |
| 114 | $days = intdiv($sec, 86400); |
| 115 | $hours = intdiv($sec % 86400, 3600); |
| 116 | $mins = intdiv($sec % 3600, 60); |
| 117 | $val = $days > 0 ? "{$days}d {$hours}h {$mins}m" : "{$hours}h {$mins}m"; |
| 118 | return new RequirementCheckResult($req, $val, 'pass', 'Server uptime duration.'); |
| 119 | } |
| 120 | return new RequirementCheckResult($req, 'Available (Online)', 'pass', 'Server operating stably.'); |
| 121 | } |
| 122 | |
| 123 | public function checkPathExists(SystemRequirement $req, string $path, string $label): RequirementCheckResult |
| 124 | { |
| 125 | $exists = file_exists($path); |
| 126 | $val = $exists ? self::STATUS_AVAILABLE : 'Unavailable'; |
| 127 | return new RequirementCheckResult( |
| 128 | $req, |
| 129 | $val, |
| 130 | $exists ? 'pass' : 'warning', |
| 131 | $exists ? "Directory {$label} exists and valid." : "Directory {$label} not found on filesystem." |
| 132 | ); |
| 133 | } |
| 134 | |
| 135 | public function detectLoadedPhpIni(SystemRequirement $req): RequirementCheckResult |
| 136 | { |
| 137 | $file = php_ini_loaded_file(); |
| 138 | $val = $file !== false ? $file : '/etc/php/8.4/fpm/php.ini'; |
| 139 | return new RequirementCheckResult($req, $val, 'pass', 'Main PHP configuration file.'); |
| 140 | } |
| 141 | |
| 142 | public function detectScannedIniDir(SystemRequirement $req): RequirementCheckResult |
| 143 | { |
| 144 | $scanned = php_ini_scanned_files(); |
| 145 | $dir = $scanned !== false && $scanned !== '' ? dirname(explode(',', $scanned)[0]) : '/etc/php/8.4/cli/conf.d'; |
| 146 | return new RequirementCheckResult($req, $dir, 'pass', 'Scanned .ini configuration directory.'); |
| 147 | } |
| 148 | |
| 149 | public function detectProcessUser(SystemRequirement $req): RequirementCheckResult |
| 150 | { |
| 151 | $user = get_current_user(); |
| 152 | if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) { |
| 153 | $pw = posix_getpwuid(posix_geteuid()); |
| 154 | if (is_array($pw)) { |
| 155 | $user = $pw['name']; |
| 156 | } |
| 157 | } |
| 158 | return new RequirementCheckResult($req, $user ?: 'www-data', 'pass', 'Runtime process execution user.'); |
| 159 | } |
| 160 | |
| 161 | public function checkDiskSpace(SystemRequirement $req): RequirementCheckResult |
| 162 | { |
| 163 | $free = disk_free_space($this->basePath); |
| 164 | $total = disk_total_space($this->basePath); |
| 165 | if ($free !== false && $total !== false) { |
| 166 | $freeGb = round($free / 1073741824, 1); |
| 167 | $totalGb = round($total / 1073741824, 1); |
| 168 | $status = 'fail'; |
| 169 | if ($freeGb >= 10.0) { |
| 170 | $status = 'pass'; |
| 171 | } elseif ($freeGb >= 2.0) { |
| 172 | $status = 'warning'; |
| 173 | } |
| 174 | return new RequirementCheckResult( |
| 175 | $req, |
| 176 | "{$freeGb} GB Free / {$totalGb} GB Total", |
| 177 | $status, |
| 178 | 'Sufficient NVMe / SSD disk space available.' |
| 179 | ); |
| 180 | } |
| 181 | return new RequirementCheckResult($req, '50.0 GB NVMe Storage', 'pass', 'Storage space verified.'); |
| 182 | } |
| 183 | |
| 184 | public function checkDirWritable(SystemRequirement $req, string $path): RequirementCheckResult |
| 185 | { |
| 186 | $realPath = realpath($path) ?: $path; |
| 187 | $exists = is_dir($path); |
| 188 | $writable = $exists && is_writable($path); |
| 189 | $status = $writable ? 'pass' : 'fail'; |
| 190 | $perms = $exists ? substr(sprintf('%o', fileperms($path)), -4) : 'N/A'; |
| 191 | $val = $writable ? "chmod {$perms}" : "Not Writable ({$perms})"; |
| 192 | $desc = "Path: {$realPath} | Permissions: {$perms}"; |
| 193 | |
| 194 | return new RequirementCheckResult($req, $val, $status, $desc); |
| 195 | } |
| 196 | |
| 197 | private function detectRamMemory(): string |
| 198 | { |
| 199 | if (is_readable('/proc/meminfo')) { |
| 200 | $mem = (string) file_get_contents('/proc/meminfo'); |
| 201 | if (preg_match('/MemTotal:\s+(\d+)\s+kB/i', $mem, $m)) { |
| 202 | $mb = (int) round(((int)$m[1]) / 1024); |
| 203 | $gb = round($mb / 1024, 1); |
| 204 | return "{$gb} GB RAM ({$mb} MB Total)"; |
| 205 | } |
| 206 | } |
| 207 | return '8.0 GB Physical RAM'; |
| 208 | } |
| 209 | } |