mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(metrics): align fleet series by time bucket, harden fleet page inputs
Address review findings on the fleet metrics page. Data correctness: - bucket raw Sentinel samples into epoch-aligned windows (5m/30m/2h per range) and average them, replacing per-server LTTB. Every server now reports the same bucket timestamps, so the fleet sums add up point by point instead of zigzagging between servers (and RX+TX really combine) - keep only the root mount in disk history, matching HasMetrics::getDiskMetrics - leave sparkline gaps as null instead of 0, so absent samples read as gaps Robustness: - normalize containerMetric in mount()/updatedContainerMetric(), so /metrics?cmetric=foo no longer throws an undefined array key - include the server uuid in the container row wire:key; system containers share one Sentinel id across servers - round the range start to the minute so the 60s history cache can hit - cap the Sentinel curl with --max-time 10, so one hung request cannot stall the serial per-server loop UI: - pass the spark time axis on first paint, so tooltips no longer show 1970 - show the active series name (RX/TX) in the server network chart tooltip
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Services\SentinelMetricsClient;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class FakeMetricsClient extends SentinelMetricsClient
|
||||
{
|
||||
@@ -93,26 +94,61 @@ it('returns an empty container list when the endpoint 404s', function () {
|
||||
expect($client->containersCurrent())->toBe([]);
|
||||
});
|
||||
|
||||
it('maps cpu history to [time, value] pairs', function () {
|
||||
it('averages cpu history into range-aligned buckets', function () {
|
||||
$client = fakeMetricsClient();
|
||||
$client->bodies = [
|
||||
'/cpu/history' => json_encode([
|
||||
['time' => 1000, 'percent' => '10'],
|
||||
['time' => 2000, 'percent' => '20'],
|
||||
['time' => '1700000000000', 'percent' => '10'],
|
||||
['time' => '1700000060000', 'percent' => '20'],
|
||||
['time' => '1700000300000', 'percent' => '40'],
|
||||
]),
|
||||
];
|
||||
|
||||
expect($client->history('cpu', 'from'))->toBe([[1000, 10.0], [2000, 20.0]]);
|
||||
// 24h range → 5-minute buckets floored to absolute epoch boundaries.
|
||||
expect($client->history('cpu', '24h'))->toBe([[1699999800000, 15.0], [1700000100000, 40.0]]);
|
||||
});
|
||||
|
||||
it('downsamples long history series so the chart payload stays small', function () {
|
||||
it('aligns buckets across servers whose samples have different timestamps', function () {
|
||||
$a = fakeMetricsClient();
|
||||
$a->bodies = ['/cpu/history' => json_encode([['time' => 1700000001234, 'percent' => '10']])];
|
||||
$b = fakeMetricsClient();
|
||||
$b->bodies = ['/cpu/history' => json_encode([['time' => 1700000047890, 'percent' => '30']])];
|
||||
|
||||
expect(array_column($a->history('cpu', '24h'), 0))->toBe(array_column($b->history('cpu', '24h'), 0));
|
||||
});
|
||||
|
||||
it('keeps long history series small', function () {
|
||||
$rows = [];
|
||||
for ($i = 0; $i < 5000; $i++) {
|
||||
$rows[] = ['time' => $i, 'percent' => (string) ($i % 100)];
|
||||
// 24h of raw 20-second samples.
|
||||
for ($i = 0; $i < 4320; $i++) {
|
||||
$rows[] = ['time' => 1700000000000 + $i * 20_000, 'percent' => (string) ($i % 100)];
|
||||
}
|
||||
|
||||
$client = fakeMetricsClient();
|
||||
$client->bodies = ['/cpu/history' => json_encode($rows)];
|
||||
|
||||
expect(count($client->history('cpu', 'from')))->toBe(300);
|
||||
expect(count($client->history('cpu', '24h')))->toBeLessThanOrEqual(300);
|
||||
});
|
||||
|
||||
it('keeps only the root mount in disk history', function () {
|
||||
$client = fakeMetricsClient();
|
||||
$client->bodies = [
|
||||
'/disk/history' => json_encode([
|
||||
['time' => 1700000000000, 'mount' => '/', 'usedPercent' => 85],
|
||||
['time' => 1700000000000, 'mount' => '/boot', 'usedPercent' => 20],
|
||||
]),
|
||||
];
|
||||
|
||||
expect($client->history('disk', '24h'))->toBe([[1699999800000, 85.0]]);
|
||||
});
|
||||
|
||||
it('rounds the range start to the minute so history responses are cacheable', function () {
|
||||
Carbon::setTestNow('2026-01-01 12:30:05');
|
||||
$first = SentinelMetricsClient::rangeFrom('24h');
|
||||
|
||||
Carbon::setTestNow('2026-01-01 12:30:45');
|
||||
|
||||
expect(SentinelMetricsClient::rangeFrom('24h'))->toBe($first)->toBe('2025-12-31T12:30:00Z');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user