diff --git a/app/Livewire/Concerns/BuildsMetricsChartPayload.php b/app/Livewire/Concerns/BuildsMetricsChartPayload.php new file mode 100644 index 0000000000..c660ed49d3 --- /dev/null +++ b/app/Livewire/Concerns/BuildsMetricsChartPayload.php @@ -0,0 +1,25 @@ + + */ + 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'), + ]; + } +} diff --git a/app/Livewire/Fleet/Metrics.php b/app/Livewire/Fleet/Metrics.php index 63dfd5605a..1de503492f 100644 --- a/app/Livewire/Fleet/Metrics.php +++ b/app/Livewire/Fleet/Metrics.php @@ -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 - */ - protected function chartPayload(): array - { - return ['range' => $this->range]; - } - public function placeholder(): View { return view('livewire.fleet.metrics-placeholder'); diff --git a/tests/Feature/Livewire/Fleet/MetricsChartPayloadTest.php b/tests/Feature/Livewire/Fleet/MetricsChartPayloadTest.php new file mode 100644 index 0000000000..0dc92d383d --- /dev/null +++ b/tests/Feature/Livewire/Fleet/MetricsChartPayloadTest.php @@ -0,0 +1,73 @@ + 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); + }); +});