Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.59% covered (success)
92.59%
100 / 108
71.43% covered (warning)
71.43%
10 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ServerMetricsDetector
92.52% covered (success)
92.52%
99 / 107
71.43% covered (warning)
71.43%
10 / 14
60.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 detect
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
14
 detectOperatingSystem
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 detectCpuProcessor
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 detectPhysicalRam
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 detectSystemLoad
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
6.97
 detectServerUptime
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 checkPathExists
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 detectLoadedPhpIni
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 detectScannedIniDir
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 detectProcessUser
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 checkDiskSpace
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
5.14
 checkDirWritable
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 detectRamMemory
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\About\Application\Service\Detector;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\About\Domain\Model\RequirementCheckResult;
12use 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 */
19final 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}{$hours}{$mins}m" : "{$hours}{$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}