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(); } }