diff --git a/app/Services/SentinelMetricsClient.php b/app/Services/SentinelMetricsClient.php index ab3ca4082a..615adc5357 100644 --- a/app/Services/SentinelMetricsClient.php +++ b/app/Services/SentinelMetricsClient.php @@ -81,6 +81,10 @@ class SentinelMetricsClient return []; } + // Sentinel keeps the last samples of a removed container until retention deletes them, + // but only a container that Docker still lists has a status. + $running = array_filter($rows, fn ($c) => ($c['status']['state'] ?? null) === 'running'); + return array_values(array_map(function ($c) { $disk = ($c['disk']['writableLayer'] ?? 0) + ($c['disk']['volumesTotal'] ?? 0); $net = ($c['network']['rxBytesPerSec'] ?? 0) + ($c['network']['txBytesPerSec'] ?? 0); @@ -93,7 +97,7 @@ class SentinelMetricsClient 'diskBytes' => isset($c['disk']) ? (int) $disk : null, 'net' => isset($c['network']) ? (float) $net : null, ]; - }, $rows)); + }, $running)); } /** diff --git a/tests/Unit/Services/SentinelMetricsClientTest.php b/tests/Unit/Services/SentinelMetricsClientTest.php index 967ee7b52e..6f705c0da0 100644 --- a/tests/Unit/Services/SentinelMetricsClientTest.php +++ b/tests/Unit/Services/SentinelMetricsClientTest.php @@ -74,7 +74,8 @@ it('normalizes containers, parsing the stringified cpu percent', function () { ['id' => 'app-a', 'cpu' => ['time' => 't', 'percent' => '12.5'], 'memory' => ['used' => 500, 'usedPercent' => 5.0], 'disk' => ['writableLayer' => 3, 'volumesTotal' => 7], - 'network' => ['rxBytesPerSec' => 4.0, 'txBytesPerSec' => 1.0]], + 'network' => ['rxBytesPerSec' => 4.0, 'txBytesPerSec' => 1.0], + 'status' => ['state' => 'running', 'health' => 'healthy', 'restartCount' => 0]], ]), ]; @@ -87,6 +88,20 @@ it('normalizes containers, parsing the stringified cpu percent', function () { expect($rows[0]['net'])->toBe(5.0); // rx + tx }); +it('lists only running containers, not removed or stopped ones', function () { + $client = fakeMetricsClient(); + $client->bodies = [ + '/containers/current' => json_encode([ + ['id' => 'app-running', 'cpu' => ['percent' => '1.0'], 'status' => ['state' => 'running']], + // Sentinel keeps the last samples of a removed container until retention, without a status. + ['id' => 'volume-backup-removed', 'cpu' => ['percent' => '79.0'], 'status' => null], + ['id' => 'exciting_mclaren', 'cpu' => ['percent' => '1.0'], 'status' => ['state' => 'exited']], + ]), + ]; + + expect(array_column($client->containersCurrent(), 'id'))->toBe(['app-running']); +}); + it('returns an empty container list when the endpoint 404s', function () { $client = fakeMetricsClient(); $client->failing = ['/containers/current'];