70 lines
2.7 KiB
PHP
70 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\MetricSample;
|
|
use App\Models\Server;
|
|
|
|
/**
|
|
* Turns persisted per-minute metric samples into a downsampled cpu/mem/disk (% average) + load
|
|
* series for the Server-Details history chart. Bucketing is done in PHP (DB-agnostic, like the
|
|
* WireGuard traffic series) — each returned point is the AVERAGE of the samples in a bucket, and
|
|
* ONLY non-empty buckets are returned (the chart connects consecutive points and the client breaks
|
|
* the line only on a real time gap, so normal sampling renders a continuous curve, not dropouts).
|
|
*/
|
|
class MetricHistory
|
|
{
|
|
/**
|
|
* @return array{window:int, from:int, now:int, bucket:int,
|
|
* points:array<int,array{t:int,cpu:int,mem:int,disk:int,load:float}>}
|
|
*/
|
|
public function series(Server $server, int $windowSeconds): array
|
|
{
|
|
$windowSeconds = max(300, $windowSeconds);
|
|
// Bucket width is at least the 1/min sample interval, so a bucket holds ~one sample and the
|
|
// line stays continuous instead of zig-zagging between half-empty buckets.
|
|
$width = max(60, (int) floor($windowSeconds / 120));
|
|
$buckets = (int) ceil($windowSeconds / $width);
|
|
$now = time();
|
|
$from = $now - $windowSeconds;
|
|
|
|
$rows = MetricSample::query()
|
|
->where('server_id', $server->id)
|
|
->where('sampled_at', '>=', now()->subSeconds($windowSeconds))
|
|
->orderBy('sampled_at')
|
|
->get(['cpu', 'mem', 'disk', 'load', 'sampled_at']);
|
|
|
|
$sum = [];
|
|
foreach ($rows as $r) {
|
|
$idx = (int) floor(($r->sampled_at->getTimestamp() - $from) / $width);
|
|
if ($idx < 0) {
|
|
continue;
|
|
}
|
|
if ($idx >= $buckets) {
|
|
$idx = $buckets - 1;
|
|
}
|
|
$sum[$idx] ??= ['cpu' => 0, 'mem' => 0, 'disk' => 0, 'load' => 0.0, 'n' => 0];
|
|
$sum[$idx]['cpu'] += (int) $r->cpu;
|
|
$sum[$idx]['mem'] += (int) $r->mem;
|
|
$sum[$idx]['disk'] += (int) $r->disk;
|
|
$sum[$idx]['load'] += (float) $r->load;
|
|
$sum[$idx]['n']++;
|
|
}
|
|
ksort($sum);
|
|
|
|
$points = [];
|
|
foreach ($sum as $i => $b) {
|
|
$n = $b['n'];
|
|
$points[] = [
|
|
't' => (int) round($from + ($i + 0.5) * $width),
|
|
'cpu' => (int) round($b['cpu'] / $n),
|
|
'mem' => (int) round($b['mem'] / $n),
|
|
'disk' => (int) round($b['disk'] / $n),
|
|
'load' => round($b['load'] / $n, 2),
|
|
];
|
|
}
|
|
|
|
return ['window' => $windowSeconds, 'from' => $from, 'now' => $now, 'bucket' => $width, 'points' => $points];
|
|
}
|
|
}
|