clusev/app/Console/Commands/PollMetrics.php

91 lines
3.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Events\MetricsTicked;
use App\Models\Server;
use App\Services\FleetService;
use App\Support\Ssh\CredentialVault;
use App\Support\Ssh\SshClient;
use Illuminate\Console\Command;
use Throwable;
/**
* Replaces the dev mock emitter: polls REAL CPU/MEM/disk over SSH for every
* server that has a stored credential, persists the values onto the Server row,
* and broadcasts MetricsTicked so the dashboard chart moves with real data.
*
* One long-lived SSH connection is kept per server and reused across ticks (a
* single login, not one per interval — avoids fail2ban/auth-log churn). A dead
* connection is dropped and re-established on the next tick; one server failing
* never kills the loop.
*/
class PollMetrics extends Command
{
protected $signature = 'clusev:poll-metrics
{--interval=15 : Seconds between polls}
{--once : Poll a single round and exit}';
protected $description = 'Poll real metrics over SSH for credentialed servers, persist + broadcast';
public function handle(FleetService $fleet, CredentialVault $vault): int
{
$interval = max(2, (int) $this->option('interval'));
/** @var array<int, SshClient> $clients */
$clients = [];
do {
// Only servers with a PRESENT, non-disabled credential. A credential disabled
// between ticks drops the server here, so its cached connection is no longer polled
// (the vault refuses a fresh connect, but a reused long-lived session would not
// re-check disabled_at). Prune any cached client whose server just left the set so
// the revoked server's open session is closed promptly, not left idle until exit.
$servers = Server::withActiveCredential()->get();
$activeIds = $servers->pluck('id')->all();
foreach (array_keys($clients) as $id) {
if (! in_array($id, $activeIds, true)) {
$clients[$id]->disconnect();
unset($clients[$id]);
}
}
if ($servers->isEmpty()) {
$this->warn('Keine Server mit aktivem Credential — nichts zu pollen.');
}
foreach ($servers as $server) {
try {
$clients[$server->id] ??= (new SshClient($vault, timeout: 12))->connect($server);
$m = $fleet->readMetrics($clients[$server->id]);
$fleet->applyMetrics($server, $m);
broadcast(new MetricsTicked($m['cpu'], $m['mem'], $server->name));
$this->line("ok {$server->name} cpu={$m['cpu']} mem={$m['mem']} disk={$m['disk']} load={$m['load']}");
} catch (Throwable $e) {
// drop the (possibly dead) connection so the next tick reconnects
if (isset($clients[$server->id])) {
$clients[$server->id]->disconnect();
unset($clients[$server->id]);
}
$server->forceFill(['status' => 'offline'])->save();
$this->warn("offline {$server->name}: ".$e->getMessage());
}
}
if ($this->option('once')) {
break;
}
sleep($interval);
} while (true);
foreach ($clients as $client) {
$client->disconnect();
}
return self::SUCCESS;
}
}