Passed
Push — master ( 1c5d75...6498f8 )
by Darko
09:30
created

SystemMetricsService::getHourlyMetrics()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 35
rs 9.6
cc 4
nc 6
nop 2
1
<?php
2
3
namespace App\Services;
4
5
use App\Models\SystemMetric;
6
use Illuminate\Support\Facades\Log;
7
8
class SystemMetricsService
9
{
10
    /**
11
     * Collect and store current system metrics
12
     */
13
    public function collectMetrics(): void
14
    {
15
        $cpuUsage = $this->getCpuUsage();
16
        $ramUsage = $this->getRamUsage();
17
18
        $now = now();
19
20
        // Store CPU metric
21
        SystemMetric::create([
22
            'metric_type' => 'cpu',
23
            'value' => $cpuUsage,
24
            'recorded_at' => $now,
25
        ]);
26
27
        // Store RAM metric (percentage)
28
        SystemMetric::create([
29
            'metric_type' => 'ram',
30
            'value' => $ramUsage['percentage'],
31
            'recorded_at' => $now,
32
        ]);
33
    }
34
35
    /**
36
     * Get historical metrics grouped by hour for the last N hours
37
     */
38
    public function getHourlyMetrics(string $type, int $hours = 24): array
39
    {
40
        $startDate = now()->subHours($hours);
41
42
        $metrics = SystemMetric::where('metric_type', $type)
43
            ->where('recorded_at', '>=', $startDate)
44
            ->orderBy('recorded_at')
45
            ->get();
46
47
        // Group by hour and calculate average
48
        $hourlyData = [];
49
        foreach ($metrics as $metric) {
50
            $hourKey = $metric->recorded_at->format('Y-m-d H:00');
51
            $hourLabel = $metric->recorded_at->format('H:i');
52
53
            if (! isset($hourlyData[$hourKey])) {
54
                $hourlyData[$hourKey] = [
55
                    'time' => $hourLabel,
56
                    'values' => [],
57
                ];
58
            }
59
            $hourlyData[$hourKey]['values'][] = $metric->value;
60
        }
61
62
        // Calculate averages
63
        $result = [];
64
        foreach ($hourlyData as $data) {
65
            $result[] = [
66
                'time' => $data['time'],
67
                'value' => round(array_sum($data['values']) / count($data['values']), 2),
68
            ];
69
        }
70
71
        // Fill missing hours with null or last known value
72
        return $this->fillMissingHours($result, $hours);
73
    }
74
75
    /**
76
     * Get historical metrics grouped by day for the last N days
77
     */
78
    public function getDailyMetrics(string $type, int $days = 30): array
79
    {
80
        $startDate = now()->subDays($days);
81
82
        $metrics = SystemMetric::where('metric_type', $type)
83
            ->where('recorded_at', '>=', $startDate)
84
            ->orderBy('recorded_at')
85
            ->get();
86
87
        // Group by day and calculate average
88
        $dailyData = [];
89
        foreach ($metrics as $metric) {
90
            $dayKey = $metric->recorded_at->format('Y-m-d');
91
            $dayLabel = $metric->recorded_at->format('M d');
92
93
            if (! isset($dailyData[$dayKey])) {
94
                $dailyData[$dayKey] = [
95
                    'time' => $dayLabel,
96
                    'values' => [],
97
                ];
98
            }
99
            $dailyData[$dayKey]['values'][] = $metric->value;
100
        }
101
102
        // Calculate averages
103
        $result = [];
104
        foreach ($dailyData as $data) {
105
            $result[] = [
106
                'time' => $data['time'],
107
                'value' => round(array_sum($data['values']) / count($data['values']), 2),
108
            ];
109
        }
110
111
        return $result;
112
    }
113
114
    /**
115
     * Fill missing hours with previous value or 0
116
     */
117
    protected function fillMissingHours(array $data, int $hours): array
118
    {
119
        if (empty($data)) {
120
            // Generate empty data points for all hours
121
            $result = [];
122
            for ($i = $hours - 1; $i >= 0; $i--) {
123
                $time = now()->subHours($i);
124
                $result[] = [
125
                    'time' => $time->format('H:i'),
126
                    'value' => 0,
127
                ];
128
            }
129
130
            return $result;
131
        }
132
133
        return $data;
134
    }
135
136
    /**
137
     * Get current CPU usage percentage
138
     */
139
    protected function getCpuUsage(): float
140
    {
141
        try {
142
            if (PHP_OS_FAMILY === 'Windows') {
143
                $output = shell_exec('wmic cpu get loadpercentage');
144
                if ($output) {
145
                    preg_match('/\d+/', $output, $matches);
146
147
                    return (float) ($matches[0] ?? 0);
148
                }
149
            } else {
150
                // Linux - use load average
151
                $load = sys_getloadavg();
152
                if ($load !== false) {
153
                    $cpuCount = $this->getCpuCount();
154
155
                    return round(($load[0] / $cpuCount) * 100, 2);
156
                }
157
            }
158
        } catch (\Exception $e) {
159
            Log::warning('Could not get CPU usage: '.$e->getMessage());
160
        }
161
162
        return 0;
163
    }
164
165
    /**
166
     * Get number of CPU cores
167
     */
168
    protected function getCpuCount(): int
169
    {
170
        try {
171
            if (PHP_OS_FAMILY === 'Windows') {
172
                $output = shell_exec('wmic cpu get NumberOfLogicalProcessors');
173
                if ($output) {
174
                    preg_match('/\d+/', $output, $matches);
175
176
                    return (int) ($matches[0] ?? 1);
177
                }
178
            } else {
179
                $cpuinfo = file_get_contents('/proc/cpuinfo');
180
                preg_match_all('/^processor/m', $cpuinfo, $matches);
181
182
                return count($matches[0]) ?: 1;
183
            }
184
        } catch (\Exception $e) {
185
            return 1;
186
        }
187
188
        return 1;
189
    }
190
191
    /**
192
     * Get RAM usage information
193
     */
194
    protected function getRamUsage(): array
195
    {
196
        try {
197
            if (PHP_OS_FAMILY === 'Windows') {
198
                $output = shell_exec('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value');
199
                if ($output) {
200
                    preg_match('/FreePhysicalMemory=(\d+)/', $output, $free);
201
                    preg_match('/TotalVisibleMemorySize=(\d+)/', $output, $total);
202
203
                    if (isset($free[1]) && isset($total[1])) {
204
                        $freeKb = (float) $free[1];
205
                        $totalKb = (float) $total[1];
206
                        $usedKb = $totalKb - $freeKb;
207
208
                        return [
209
                            'used' => round($usedKb / 1024 / 1024, 2),
210
                            'total' => round($totalKb / 1024 / 1024, 2),
211
                            'percentage' => round(($usedKb / $totalKb) * 100, 2),
212
                        ];
213
                    }
214
                }
215
            } else {
216
                $meminfo = file_get_contents('/proc/meminfo');
217
                preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total);
218
                preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available);
219
220
                if (isset($total[1]) && isset($available[1])) {
221
                    $totalKb = (float) $total[1];
222
                    $availableKb = (float) $available[1];
223
                    $usedKb = $totalKb - $availableKb;
224
225
                    return [
226
                        'used' => round($usedKb / 1024 / 1024, 2),
227
                        'total' => round($totalKb / 1024 / 1024, 2),
228
                        'percentage' => round(($usedKb / $totalKb) * 100, 2),
229
                    ];
230
                }
231
            }
232
        } catch (\Exception $e) {
233
            Log::warning('Could not get RAM usage: '.$e->getMessage());
234
        }
235
236
        return [
237
            'used' => 0,
238
            'total' => 0,
239
            'percentage' => 0,
240
        ];
241
    }
242
243
    /**
244
     * Clean up old metrics (keep last 60 days)
245
     */
246
    public function cleanupOldMetrics(int $daysToKeep = 60): int
247
    {
248
        return SystemMetric::cleanupOldMetrics($daysToKeep);
249
    }
250
}
251