153 lines
4.9 KiB
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();
|
|
}
|
|
}
|