mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(metrics): keep reachable servers online when Sentinel has no summary
Closes #12289 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ade4bae265
commit
9a8b4eb53c
@@ -251,15 +251,15 @@ class Metrics extends Component
|
||||
}
|
||||
|
||||
if ($summary === null) {
|
||||
$rows[] = $this->offlineRow($server);
|
||||
$rows[] = $this->rowWithoutMetrics($server);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$current = $results[$server->uuid.':containers'] ?? [];
|
||||
$rows[] = array_merge(
|
||||
['uuid' => $server->uuid, 'name' => $server->name, 'online' => true, 'containers' => count($current)],
|
||||
$summary
|
||||
$summary,
|
||||
['uuid' => $server->uuid, 'name' => $server->name, 'online' => true, 'reporting' => true, 'containers' => count($current)]
|
||||
);
|
||||
|
||||
foreach ($current as $c) {
|
||||
@@ -308,12 +308,15 @@ class Metrics extends Component
|
||||
}
|
||||
|
||||
/**
|
||||
* A server whose Sentinel returned no current metrics. It is online when Coolify can reach it,
|
||||
* because a failed metrics request does not mean that the server is down.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function offlineRow(Server $server): array
|
||||
protected function rowWithoutMetrics(Server $server): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $server->uuid, 'name' => $server->name, 'online' => false,
|
||||
'uuid' => $server->uuid, 'name' => $server->name, 'online' => (bool) $server->isFunctional(), 'reporting' => false,
|
||||
'cpu' => null, 'memUsed' => null, 'memTotal' => null,
|
||||
'diskUsed' => null, 'diskTotal' => null, 'diskPercent' => null,
|
||||
'load1' => null, 'netRx' => null, 'netTx' => null, 'containers' => null,
|
||||
|
||||
@@ -13,25 +13,27 @@ class FleetMetricsAggregator
|
||||
public static function fleetKpis(array $rows): array
|
||||
{
|
||||
$online = array_values(array_filter($rows, fn ($r) => $r['online'] === true));
|
||||
// Only servers whose Sentinel returned current metrics give values.
|
||||
$reporting = array_values(array_filter($online, fn ($r) => $r['reporting'] === true));
|
||||
|
||||
$cpuValues = self::present($online, 'cpu');
|
||||
$cpuValues = self::present($reporting, 'cpu');
|
||||
$cpuAvg = $cpuValues === [] ? 0.0 : round(array_sum($cpuValues) / count($cpuValues), 1);
|
||||
|
||||
$busiest = null;
|
||||
foreach ($online as $r) {
|
||||
foreach ($reporting as $r) {
|
||||
if ($r['cpu'] !== null && ($busiest === null || $r['cpu'] > $busiest['percent'])) {
|
||||
$busiest = ['name' => $r['name'], 'percent' => (float) $r['cpu']];
|
||||
}
|
||||
}
|
||||
|
||||
$memUsed = (int) self::sum($online, 'memUsed');
|
||||
$memTotal = (int) self::sum($online, 'memTotal');
|
||||
$diskUsed = (int) self::sum($online, 'diskUsed');
|
||||
$diskTotal = (int) self::sum($online, 'diskTotal');
|
||||
$load1 = self::present($online, 'load1');
|
||||
$memUsed = (int) self::sum($reporting, 'memUsed');
|
||||
$memTotal = (int) self::sum($reporting, 'memTotal');
|
||||
$diskUsed = (int) self::sum($reporting, 'diskUsed');
|
||||
$diskTotal = (int) self::sum($reporting, 'diskTotal');
|
||||
$load1 = self::present($reporting, 'load1');
|
||||
|
||||
$needsAttention = 0;
|
||||
foreach ($online as $r) {
|
||||
foreach ($reporting as $r) {
|
||||
$hot = PressureLevel::for($r['cpu'], 'cpu') !== 'ok'
|
||||
|| PressureLevel::for(self::percent($r['memUsed'], $r['memTotal']), 'memory') !== 'ok'
|
||||
|| PressureLevel::for($r['diskPercent'], 'disk') !== 'ok';
|
||||
@@ -42,6 +44,7 @@ class FleetMetricsAggregator
|
||||
|
||||
return [
|
||||
'serversOnline' => count($online),
|
||||
'serversReporting' => count($reporting),
|
||||
'serversTotal' => count($rows),
|
||||
'needsAttention' => $needsAttention,
|
||||
'cpuAvg' => $cpuAvg,
|
||||
@@ -53,11 +56,11 @@ class FleetMetricsAggregator
|
||||
'diskUsed' => $diskUsed,
|
||||
'diskTotal' => $diskTotal,
|
||||
'diskPercent' => self::percent($diskUsed, $diskTotal),
|
||||
'netRx' => (float) self::sum($online, 'netRx'),
|
||||
'netTx' => (float) self::sum($online, 'netTx'),
|
||||
'netRx' => (float) self::sum($reporting, 'netRx'),
|
||||
'netTx' => (float) self::sum($reporting, 'netTx'),
|
||||
'loadBusiest' => $load1 === [] ? 0.0 : max($load1),
|
||||
'loadAvg' => $load1 === [] ? 0.0 : round(array_sum($load1) / count($load1), 2),
|
||||
'containers' => (int) self::sum($online, 'containers'),
|
||||
'containers' => (int) self::sum($reporting, 'containers'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,10 @@ $serverListboxOptions = array_merge(
|
||||
@php
|
||||
$sTotal = max(1, (int) ($kpis['serversTotal'] ?? 0));
|
||||
$sOnline = (int) ($kpis['serversOnline'] ?? 0);
|
||||
$sAttention = min($sOnline, (int) ($kpis['needsAttention'] ?? 0));
|
||||
$sHealthy = max(0, $sOnline - $sAttention);
|
||||
$sReporting = min($sOnline, (int) ($kpis['serversReporting'] ?? 0));
|
||||
$sAttention = min($sReporting, (int) ($kpis['needsAttention'] ?? 0));
|
||||
$sHealthy = max(0, $sReporting - $sAttention);
|
||||
$sNoMetrics = $sOnline - $sReporting;
|
||||
$sOffline = max(0, $sTotal - $sOnline);
|
||||
@endphp
|
||||
<div class="mt-auto pt-3">
|
||||
@@ -118,11 +120,13 @@ $serverListboxOptions = array_merge(
|
||||
<div class="flex h-2 overflow-hidden rounded-full bg-neutral-200 dark:bg-white/[0.08] [&>div]:transition-[width] [&>div]:duration-700 [&>div]:ease-out">
|
||||
@if ($sHealthy > 0)<div class="bg-emerald-500" style="width: {{ $sHealthy / $sTotal * 100 }}%"></div>@endif
|
||||
@if ($sAttention > 0)<div class="bg-orange-400 dark:bg-warning" style="width: {{ $sAttention / $sTotal * 100 }}%"></div>@endif
|
||||
@if ($sNoMetrics > 0)<div class="bg-neutral-400 dark:bg-white/30" style="width: {{ $sNoMetrics / $sTotal * 100 }}%"></div>@endif
|
||||
@if ($sOffline > 0)<div class="bg-red-500 dark:bg-red-400" style="width: {{ $sOffline / $sTotal * 100 }}%"></div>@endif
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-2 text-[10px] text-neutral-500 dark:text-fg-dim">
|
||||
<span>{{ $sHealthy }} healthy</span>
|
||||
@if ($sAttention > 0)<span class="text-orange-500 dark:text-warning">{{ $sAttention }} attention</span>@endif
|
||||
@if ($sNoMetrics > 0)<span title="Coolify can reach these servers, but Sentinel did not return current metrics.">{{ $sNoMetrics }} no metrics</span>@endif
|
||||
@if ($sOffline > 0)<span class="text-red-500 dark:text-red-400">{{ $sOffline }} offline</span>@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,16 +263,18 @@ $serverListboxOptions = array_merge(
|
||||
'containers' => $row['containers'],
|
||||
];
|
||||
@endphp
|
||||
{{-- Offline servers have no values, so they stay last in each sort. --}}
|
||||
<div wire:key="fleet-server-{{ $row['uuid'] }}" data-row="{{ json_encode($sortValues) }}" @unless ($row['online']) data-pinned @endunless
|
||||
{{-- Servers without metrics have no values, so they stay last in each sort. --}}
|
||||
<div wire:key="fleet-server-{{ $row['uuid'] }}" data-row="{{ json_encode($sortValues) }}" @unless ($row['reporting']) data-pinned @endunless
|
||||
x-show="shown($el)"
|
||||
class="data-table-row fleet-servers-table-grid border-t border-neutral-200 dark:border-white/[0.07]">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<a href="{{ route('server.metrics', ['server_uuid' => $row['uuid']]) }}"
|
||||
class="min-w-0 truncate text-[13px] font-medium text-black hover:underline dark:text-fg" {{ wireNavigate() }}>{{ $row['name'] }}</a>
|
||||
@unless ($row['online'])
|
||||
@if (! $row['online'])
|
||||
<span class="table-badge table-badge-warning shrink-0">Offline</span>
|
||||
@endunless
|
||||
@elseif (! $row['reporting'])
|
||||
<span class="table-badge shrink-0" title="Coolify can reach this server, but Sentinel did not return current metrics.">No metrics</span>
|
||||
@endif
|
||||
</div>
|
||||
<div>@include('livewire.fleet._usage-cell', ['percent' => $row['cpu'], 'kind' => 'cpu'])</div>
|
||||
<div>@include('livewire.fleet._usage-cell', ['percent' => $memPercent, 'kind' => 'memory'])</div>
|
||||
|
||||
@@ -131,6 +131,23 @@ it('keeps an unreachable server in the table but excludes it from KPIs', functio
|
||||
expect($component->get('kpis')['cpuAvg'])->toBe(30.0);
|
||||
});
|
||||
|
||||
it('counts a reachable server as online when Sentinel returns no summary', function () {
|
||||
$server = metricsServer($this->team, $this->privateKey);
|
||||
$server->update(['name' => 'web-1']);
|
||||
$server->settings->forceFill(['is_reachable' => true, 'is_usable' => true])->saveQuietly();
|
||||
StubMetricsClient::$summaries = [$server->uuid => null];
|
||||
|
||||
$component = Livewire::test(TestableMetrics::class)
|
||||
->assertSee('No metrics')
|
||||
->assertDontSee('Offline');
|
||||
|
||||
$row = collect($component->get('serverRows'))->firstWhere('uuid', $server->uuid);
|
||||
expect($row['online'])->toBeTrue();
|
||||
expect($row['reporting'])->toBeFalse();
|
||||
expect($component->get('kpis')['serversOnline'])->toBe(1);
|
||||
expect($component->get('kpis')['serversReporting'])->toBe(0);
|
||||
});
|
||||
|
||||
it('ranks containers across servers by the selected metric', function () {
|
||||
$s = metricsServer($this->team, $this->privateKey);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use App\Services\FleetMetricsAggregator;
|
||||
function row(array $overrides = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'uuid' => 'u', 'name' => 'srv', 'online' => true,
|
||||
'uuid' => 'u', 'name' => 'srv', 'online' => true, 'reporting' => true,
|
||||
'cpu' => 10.0, 'memUsed' => 1_000, 'memTotal' => 10_000,
|
||||
'diskUsed' => 1_000, 'diskTotal' => 10_000, 'diskPercent' => 10.0,
|
||||
'load1' => 0.5, 'netRx' => 100.0, 'netTx' => 50.0, 'containers' => 2,
|
||||
@@ -51,7 +51,7 @@ it('sums network rates and reports busiest and average load', function () {
|
||||
it('ignores offline rows and counts servers needing attention', function () {
|
||||
$kpis = FleetMetricsAggregator::fleetKpis([
|
||||
row(['cpu' => 95.0]), // over cpu error cutoff
|
||||
row(['online' => false, 'cpu' => 99.0]),
|
||||
row(['online' => false, 'reporting' => false, 'cpu' => 99.0]),
|
||||
]);
|
||||
|
||||
expect($kpis['serversOnline'])->toBe(1);
|
||||
@@ -60,6 +60,18 @@ it('ignores offline rows and counts servers needing attention', function () {
|
||||
expect($kpis['cpuAvg'])->toBe(95.0); // offline row excluded from the mean
|
||||
});
|
||||
|
||||
it('counts an online server without metrics as online but leaves it out of the values', function () {
|
||||
$kpis = FleetMetricsAggregator::fleetKpis([
|
||||
row(['cpu' => 20.0]),
|
||||
row(['reporting' => false, 'cpu' => 99.0]),
|
||||
]);
|
||||
|
||||
expect($kpis['serversOnline'])->toBe(2);
|
||||
expect($kpis['serversReporting'])->toBe(1);
|
||||
expect($kpis['needsAttention'])->toBe(0);
|
||||
expect($kpis['cpuAvg'])->toBe(20.0);
|
||||
});
|
||||
|
||||
it('returns zeroed kpis for no rows', function () {
|
||||
$kpis = FleetMetricsAggregator::fleetKpis([]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user