62 lines
2.7 KiB
PHP
62 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\MetricSample;
|
|
use App\Models\Server;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
/**
|
|
* Persist a once-a-minute resource sample per server — the long history the Server-Details graph
|
|
* plots. The source of truth is the `servers` DB ROW the 15s poller forceFills every tick (cpu/mem/
|
|
* disk + last_seen_at), NOT a cache: the poller and this command run in SEPARATE containers in prod,
|
|
* so a cache-backed read only worked when the cache store happened to be shared. Reading the row
|
|
* means history is collected whenever the live gauges have data (they read the same row), regardless
|
|
* of the cache driver. A server not freshly polled (never polled / offline — last_seen_at stale)
|
|
* simply gets no sample (an honest gap). `load` is not on the row, so it is taken from the live cache
|
|
* best-effort when present (stored but not plotted). Prunes beyond the retention window. Scheduled
|
|
* everyMinute in routes/console.php.
|
|
*/
|
|
class SampleMetrics extends Command
|
|
{
|
|
protected $signature = 'clusev:sample-metrics {--retention=30 : Days of history to keep}';
|
|
|
|
protected $description = 'Persist a one-per-minute metric history sample per server (and prune old samples)';
|
|
|
|
public function handle(): int
|
|
{
|
|
$now = now();
|
|
// Only servers the 15s poller updated recently — a stale row means offline / no credential,
|
|
// and sampling it would plot a flatline of its last-known values. 90s tolerates a missed tick.
|
|
$fresh = $now->copy()->subSeconds(90);
|
|
$rows = [];
|
|
|
|
foreach (Server::query()->get(['id', 'cpu', 'mem', 'disk', 'last_seen_at']) as $server) {
|
|
if ($server->last_seen_at === null || $server->last_seen_at->lt($fresh)) {
|
|
continue;
|
|
}
|
|
// load is not persisted on the row; best-effort from the live cache when the store is shared.
|
|
$cached = Cache::get("metrics:latest:{$server->id}");
|
|
$rows[] = [
|
|
'server_id' => $server->id,
|
|
// Percentages — clamp to 0..100 so a stray reading can never plot outside the chart.
|
|
'cpu' => min(100, max(0, (int) $server->cpu)),
|
|
'mem' => min(100, max(0, (int) $server->mem)),
|
|
'disk' => min(100, max(0, (int) $server->disk)),
|
|
'load' => is_array($cached) ? round((float) ($cached['load'] ?? 0), 2) : 0.0,
|
|
'sampled_at' => $now,
|
|
];
|
|
}
|
|
|
|
if ($rows !== []) {
|
|
MetricSample::insert($rows);
|
|
}
|
|
|
|
$retention = max(1, (int) $this->option('retention'));
|
|
MetricSample::where('sampled_at', '<', now()->subDays($retention))->delete();
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|