feat(metrics): build and dispatch fleet trend chart payloads

This commit is contained in:
Aditya Tripathi
2026-09-16 08:01:11 +00:00
parent fb021c3d9d
commit 5273a4c035
3 changed files with 112 additions and 9 deletions
@@ -0,0 +1,25 @@
<?php
namespace App\Livewire\Concerns;
use App\Services\FleetMetricsAggregator;
trait BuildsMetricsChartPayload
{
/**
* @param array{cpu: array, memory: array, disk: array, load: array, networkRx: array, networkTx: array} $seriesByServer
* @return array<string, mixed>
*/
protected function buildChartPayload(array $seriesByServer): array
{
return [
'range' => $this->range,
'cpu' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['cpu'], 'avg'),
'memory' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['memory'], 'sum'),
'disk' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['disk'], 'avg'),
'load' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['load'], 'avg'),
'networkRx' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['networkRx'], 'sum'),
'networkTx' => FleetMetricsAggregator::sumSeriesByBucket($seriesByServer['networkTx'], 'sum'),
];
}
}
+14 -9
View File
@@ -2,6 +2,7 @@
namespace App\Livewire\Fleet;
use App\Livewire\Concerns\BuildsMetricsChartPayload;
use App\Models\Application;
use App\Models\Server;
use App\Services\FleetMetricsAggregator;
@@ -15,6 +16,8 @@ use Livewire\Component;
#[Lazy]
class Metrics extends Component
{
use BuildsMetricsChartPayload;
public string $chartId = 'fleet-metrics';
/** Metrics-enabled servers owned by the current team. */
@@ -103,6 +106,8 @@ class Metrics extends Component
$rows = [];
$containers = [];
$series = ['cpu' => [], 'memory' => [], 'disk' => [], 'load' => [], 'networkRx' => [], 'networkTx' => []];
$from = SentinelMetricsClient::rangeFrom($this->range);
foreach ($this->targetServers() as $server) {
$client = $this->metricsClient($server);
@@ -129,13 +134,21 @@ class Metrics extends Component
$meta = $this->resolveContainer($server, $c['id']);
$containers[] = array_merge($c, ['server' => $server->name, 'name' => $meta['name'], 'link' => $meta['link']]);
}
$series['cpu'][] = $client->history('cpu', $from);
$series['memory'][] = $client->history('memory', $from);
$series['disk'][] = $client->history('disk', $from);
$series['load'][] = $client->history('load', $from);
$net = $client->networkHistory($from);
$series['networkRx'][] = $net['rx'];
$series['networkTx'][] = $net['tx'];
}
$this->serverRows = $rows;
$this->kpis = FleetMetricsAggregator::fleetKpis($rows);
$this->topContainers = FleetMetricsAggregator::rankContainers($containers, $this->containerMetric);
$this->dispatch("refreshChartData-{$this->chartId}", $this->chartPayload());
$this->dispatch("refreshChartData-{$this->chartId}", $this->buildChartPayload($series));
}
/**
@@ -186,14 +199,6 @@ class Metrics extends Component
];
}
/**
* @return array<string, mixed>
*/
protected function chartPayload(): array
{
return ['range' => $this->range];
}
public function placeholder(): View
{
return view('livewire.fleet.metrics-placeholder');
@@ -0,0 +1,73 @@
<?php
use App\Livewire\Fleet\Metrics;
use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use App\Services\SentinelMetricsClient;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
class ChartStubClient extends SentinelMetricsClient
{
public function summary(): ?array
{
return ['online' => true, 'cpu' => 10.0, 'memUsed' => 1, 'memTotal' => 2,
'diskUsed' => 1, 'diskTotal' => 2, 'diskPercent' => 50.0, 'load1' => 0.1, 'netRx' => 0.0, 'netTx' => 0.0];
}
public function containersCurrent(): array
{
return [];
}
public function history(string $metric, string $from): array
{
return [[1000, 10.0], [2000, 20.0]];
}
public function networkHistory(string $from): array
{
return ['rx' => [[1000, 5.0]], 'tx' => [[1000, 1.0]]];
}
}
class ChartTestableMetrics extends Metrics
{
protected function metricsClient(Server $server): SentinelMetricsClient
{
return new ChartStubClient($server);
}
}
beforeEach(function () {
InstanceSettings::unguarded(fn () => InstanceSettings::query()->create(['id' => 0]));
$user = User::factory()->create();
$team = Team::factory()->create();
$user->teams()->attach($team, ['role' => 'owner']);
$this->actingAs($user);
session(['currentTeam' => $team]);
$this->privateKey = PrivateKey::factory()->create(['team_id' => $team->id]);
$this->team = $team;
});
it('dispatches a chart payload with every trend series', function () {
$s = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $this->privateKey->id]);
$s->settings->update(['is_metrics_enabled' => true]);
Livewire::test(ChartTestableMetrics::class)
->call('loadData')
->assertDispatched('refreshChartData-fleet-metrics', function ($event, $params) {
$payload = $params[0];
// Livewire JSON-serializes event params, so whole floats arrive as ints;
// compare loosely (10 == 10.0).
return $payload['cpu'] == [[1000, 10], [2000, 20]]
&& $payload['networkRx'] == [[1000, 5]]
&& array_key_exists('load', $payload);
});
});