65 lines
2.4 KiB
PHP
65 lines
2.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\MetricSample;
|
|
use App\Models\Server;
|
|
|
|
/**
|
|
* Turns persisted per-minute metric samples into a bucketed cpu/mem/disk (% average) + load series
|
|
* for the Server-Details history chart. Each bucket is the AVERAGE of the samples that fall in it
|
|
* (gauges, not counters); a bucket with no samples is null so the chart shows a gap instead of
|
|
* fabricating a value. Mirrors the WireGuard traffic-series shape.
|
|
*/
|
|
class MetricHistory
|
|
{
|
|
/**
|
|
* @return array{window:int, points:array<int,array{t:int,cpu:?int,mem:?int,disk:?int,load:?float}>}
|
|
*/
|
|
public function series(Server $server, int $windowSeconds, int $buckets = 80): array
|
|
{
|
|
$windowSeconds = max(60, $windowSeconds);
|
|
$buckets = max(1, $buckets);
|
|
$now = time();
|
|
$from = $now - $windowSeconds;
|
|
$width = $windowSeconds / $buckets;
|
|
|
|
$sum = array_fill(0, $buckets, ['cpu' => 0, 'mem' => 0, 'disk' => 0, 'load' => 0.0, 'n' => 0]);
|
|
|
|
$rows = MetricSample::query()
|
|
->where('server_id', $server->id)
|
|
->where('sampled_at', '>=', now()->subSeconds($windowSeconds))
|
|
->orderBy('sampled_at')
|
|
->get(['cpu', 'mem', 'disk', 'load', 'sampled_at']);
|
|
|
|
foreach ($rows as $r) {
|
|
$idx = (int) floor(($r->sampled_at->getTimestamp() - $from) / $width);
|
|
if ($idx < 0) {
|
|
continue;
|
|
}
|
|
if ($idx >= $buckets) {
|
|
$idx = $buckets - 1; // a sample exactly on the right edge belongs to the last bucket
|
|
}
|
|
$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']++;
|
|
}
|
|
|
|
$points = [];
|
|
for ($i = 0; $i < $buckets; $i++) {
|
|
$n = $sum[$i]['n'];
|
|
$points[] = [
|
|
't' => (int) round($from + ($i + 0.5) * $width),
|
|
'cpu' => $n ? (int) round($sum[$i]['cpu'] / $n) : null,
|
|
'mem' => $n ? (int) round($sum[$i]['mem'] / $n) : null,
|
|
'disk' => $n ? (int) round($sum[$i]['disk'] / $n) : null,
|
|
'load' => $n ? round($sum[$i]['load'] / $n, 2) : null,
|
|
];
|
|
}
|
|
|
|
return ['window' => $windowSeconds, 'points' => $points];
|
|
}
|
|
}
|