Files
jabali-panel/app/Console/Commands/CollectServerMetrics.php

153 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\ServerMetric;
use App\Services\Agent\AgentClient;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class CollectServerMetrics extends Command
{
protected $signature = 'server-metrics:collect';
protected $description = 'Collect server metrics for historical charts';
public function handle(): int
{
$agent = new AgentClient;
try {
$overview = $agent->metricsOverview();
$diskData = $agent->metricsDisk()['data'] ?? [];
} catch (\Throwable $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
$capturedAt = now()->second(0);
$load = (float) ($overview['load']['1min'] ?? 0);
$memory = $this->calculateMemoryUsage($overview['memory'] ?? []);
$swap = (float) ($overview['memory']['swap']['usage_percent'] ?? 0);
$partitions = $diskData['partitions'] ?? [];
$this->storeMetric('load', '1m', $load, $capturedAt);
$this->storeMetric('memory', '1m', $memory, $capturedAt);
$this->storeMetric('swap', '1m', $swap, $capturedAt);
foreach ($partitions as $partition) {
$mount = $partition['mount'] ?? null;
if ($mount === null) {
continue;
}
$used = (int) ($partition['used'] ?? 0);
$this->storeMetric('disk_gb', '1m', $this->bytesToGb($used), $capturedAt, $mount);
}
if ($capturedAt->minute % 5 === 0) {
$this->storeAggregated('load', '1m', '5m', 5, $capturedAt);
$this->storeAggregated('memory', '1m', '5m', 5, $capturedAt);
$this->storeAggregated('swap', '1m', '5m', 5, $capturedAt);
$this->storeAggregatedDisk('5m', 5, $capturedAt);
}
if ($capturedAt->minute % 30 === 0) {
$this->storeAggregated('load', '1m', '30m', 30, $capturedAt);
$this->storeAggregated('memory', '1m', '30m', 30, $capturedAt);
$this->storeAggregated('swap', '1m', '30m', 30, $capturedAt);
$this->storeAggregatedDisk('30m', 30, $capturedAt);
}
$this->pruneResolution('1m', $capturedAt->copy()->subDay());
$this->pruneResolution('5m', $capturedAt->copy()->subDays(7));
$this->pruneResolution('30m', $capturedAt->copy()->subDays(31));
return self::SUCCESS;
}
private function calculateMemoryUsage(array $memory): float
{
$usage = (float) ($memory['usage_percent'] ?? $memory['usage'] ?? 0);
$total = (float) ($memory['total'] ?? 0);
if ($usage === 0.0 && $total > 0) {
$usage = ((float) ($memory['used'] ?? 0) / $total) * 100;
}
return round($usage, 1);
}
private function storeMetric(string $metric, string $resolution, float $value, Carbon $capturedAt, ?string $label = null): void
{
ServerMetric::create([
'metric' => $metric,
'label' => $label,
'resolution' => $resolution,
'value' => round($value, 3),
'captured_at' => $capturedAt,
]);
}
private function storeAggregated(
string $metric,
string $sourceResolution,
string $targetResolution,
int $minutes,
Carbon $capturedAt,
?string $label = null
): void {
$since = $capturedAt->copy()->subMinutes($minutes)->subSeconds(30);
$values = ServerMetric::query()
->where('metric', $metric)
->when($label !== null, fn ($query) => $query->where('label', $label))
->where('resolution', $sourceResolution)
->where('captured_at', '>=', $since)
->where('captured_at', '<=', $capturedAt)
->orderBy('captured_at')
->pluck('value')
->all();
if ($values === []) {
return;
}
$average = array_sum($values) / count($values);
$this->storeMetric($metric, $targetResolution, (float) $average, $capturedAt, $label);
}
private function storeAggregatedDisk(string $targetResolution, int $minutes, Carbon $capturedAt): void
{
$labels = ServerMetric::query()
->where('metric', 'disk_gb')
->where('resolution', '1m')
->distinct()
->pluck('label')
->filter()
->all();
foreach ($labels as $label) {
$this->storeAggregated('disk_gb', '1m', $targetResolution, $minutes, $capturedAt, $label);
}
}
private function bytesToGb(int $bytes): float
{
if ($bytes <= 0) {
return 0.0;
}
return round($bytes / 1024 / 1024 / 1024, 2);
}
private function pruneResolution(string $resolution, Carbon $before): void
{
ServerMetric::query()
->where('resolution', $resolution)
->where('captured_at', '<', $before)
->delete();
}
}