Fix sysstat timer start and chart layout

This commit is contained in:
root
2026-01-30 16:44:03 +02:00
parent 845b12f786
commit 2399e96970
5 changed files with 564 additions and 520 deletions

View File

@@ -13,8 +13,6 @@ class ServerChartsWidget extends Widget
protected int|string|array $columnSpan = 'full';
protected ?string $pollingInterval = '60s';
public int $refreshKey = 0;
public string $range = '5m';
@@ -201,7 +199,7 @@ class ServerChartsWidget extends Widget
{
return match ($range) {
'5m' => ['minutes' => 5, 'points' => 30, 'resolution' => '10s', 'label_format' => 'H:i:s', 'interval_seconds' => 10],
'30m' => ['minutes' => 30, 'points' => 180, 'resolution' => '10s', 'label_format' => 'H:i', 'interval_seconds' => 10],
'30m' => ['minutes' => 30, 'points' => 180, 'resolution' => '10s', 'label_format' => 'H:i:s', 'interval_seconds' => 10],
'day' => ['minutes' => 1440, 'points' => 24, 'resolution' => '1h', 'label_format' => 'H:00', 'interval_seconds' => 3600],
'week' => ['minutes' => 10080, 'points' => 28, 'resolution' => '6h', 'label_format' => 'M d H:00', 'interval_seconds' => 21600],
'month' => ['minutes' => 43200, 'points' => 30, 'resolution' => '1d', 'label_format' => 'M d', 'interval_seconds' => 86400],

View File

@@ -6,6 +6,7 @@ namespace App\Services;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\Process\Process;
class SysstatMetrics
@@ -34,13 +35,18 @@ class SysstatMetrics
}
}
$start = $end->subSeconds(($points - 1) * $intervalSeconds);
$samples = $this->readSamples($start, $end);
$endBucket = intdiv($end->getTimestamp(), $intervalSeconds);
$cacheKey = sprintf('sysstat.history.%d.%d.%s.%d', $points, $intervalSeconds, $labelFormat, $endBucket);
$ttl = $this->cacheTtl($intervalSeconds);
if (empty($samples)) {
return [];
}
return Cache::remember($cacheKey, $ttl, function () use ($start, $end, $points, $intervalSeconds, $labelFormat): array {
$samples = $this->readSamples($start, $end, $this->coreOptions());
if (empty($samples)) {
return [];
}
return $this->resample($samples, $start, $points, $intervalSeconds, $labelFormat);
return $this->resample($samples, $start, $points, $intervalSeconds, $labelFormat);
});
}
/**
@@ -51,25 +57,29 @@ class SysstatMetrics
$timezone = $this->systemTimezone();
$end = CarbonImmutable::now($timezone);
$start = $end->subMinutes(15);
$samples = $this->readSamples($start, $end);
$bucket = intdiv($end->getTimestamp(), 10);
$cacheKey = sprintf('sysstat.latest.%d', $bucket);
if (empty($samples)) {
return null;
}
return Cache::remember($cacheKey, now()->addSeconds(10), function () use ($start, $end): ?array {
$samples = $this->readSamples($start, $end, $this->coreOptions());
if (empty($samples)) {
return null;
}
$last = end($samples);
if (! is_array($last)) {
return null;
}
$last = end($samples);
if (! is_array($last)) {
return null;
}
return [
'load1' => (float) ($last['load1'] ?? 0),
'load5' => (float) ($last['load5'] ?? 0),
'load15' => (float) ($last['load15'] ?? 0),
'iowait' => (float) ($last['iowait'] ?? 0),
'memory' => (float) ($last['memory'] ?? 0),
'swap' => (float) ($last['swap'] ?? 0),
];
return [
'load1' => (float) ($last['load1'] ?? 0),
'load5' => (float) ($last['load5'] ?? 0),
'load15' => (float) ($last['load15'] ?? 0),
'iowait' => (float) ($last['iowait'] ?? 0),
'memory' => (float) ($last['memory'] ?? 0),
'swap' => (float) ($last['swap'] ?? 0),
];
});
}
public function timezoneName(): string
@@ -80,14 +90,16 @@ class SysstatMetrics
/**
* @return array<int, array{timestamp: int, load1: float, load5: float, load15: float, iowait: float, memory: float, swap: float}>
*/
private function readSamples(CarbonImmutable $start, CarbonImmutable $end): array
private function readSamples(CarbonImmutable $start, CarbonImmutable $end, array $options): array
{
$samples = [];
$current = $start->startOfDay();
$lastDay = $end->startOfDay();
while ($current <= $lastDay) {
$file = sprintf('/var/log/sysstat/sa%s', $current->format('d'));
$fileLong = sprintf('/var/log/sysstat/sa%s', $current->format('Ymd'));
$fileShort = sprintf('/var/log/sysstat/sa%s', $current->format('d'));
$file = is_readable($fileLong) ? $fileLong : $fileShort;
if (! is_readable($file)) {
$current = $current->addDay();
@@ -96,7 +108,7 @@ class SysstatMetrics
$dayStart = $current->isSameDay($start) ? $start : $current->startOfDay();
$dayEnd = $current->isSameDay($end) ? $end : $current->endOfDay();
$statistics = $this->readSadf($file, $dayStart, $dayEnd);
$statistics = $this->readSadf($file, $dayStart, $dayEnd, $options);
foreach ($statistics as $stat) {
$parsed = $this->parseSample($stat);
@@ -117,20 +129,25 @@ class SysstatMetrics
/**
* @return array<int, array<string, mixed>>
*/
private function readSadf(string $file, CarbonImmutable $start, CarbonImmutable $end): array
private function readSadf(string $file, CarbonImmutable $start, CarbonImmutable $end, array $options): array
{
$process = new Process([
'sadf',
'-j',
'-T',
$file,
'--',
'-A',
'-s',
$start->format('H:i:s'),
'-e',
$end->format('H:i:s'),
]);
$args = array_merge(
[
'sadf',
'-j',
'-T',
$file,
'--',
],
$options,
[
'-s',
$start->format('H:i:s'),
'-e',
$end->format('H:i:s'),
],
);
$process = new Process($args);
$process->run();
if (! $process->isSuccessful()) {
@@ -150,6 +167,23 @@ class SysstatMetrics
return $stats;
}
/**
* @return array<int, string>
*/
private function coreOptions(): array
{
return ['-q', '-u', '-r', '-S'];
}
private function cacheTtl(int $intervalSeconds): \DateInterval|\DateTimeInterface|int
{
if ($intervalSeconds <= 10) {
return 10;
}
return max(30, min(300, $intervalSeconds));
}
/**
* @param array<string, mixed> $stat
* @return array{timestamp: int, load1: float, load5: float, load15: float, iowait: float, memory: float, swap: float}|null
@@ -205,6 +239,7 @@ class SysstatMetrics
$index = 0;
$current = null;
$first = $samples[0] ?? null;
$count = count($samples);
for ($i = 0; $i < $points; $i++) {
@@ -214,11 +249,12 @@ class SysstatMetrics
$index++;
}
$sample = $current ?? $first;
$labels[] = $bucketTime->format($labelFormat);
$loadSeries[] = $current ? round((float) $current['load1'], 3) : 0.0;
$ioWaitSeries[] = $current ? round((float) $current['iowait'], 2) : 0.0;
$memorySeries[] = $current ? round((float) $current['memory'], 1) : 0.0;
$swapSeries[] = $current ? round((float) $current['swap'], 1) : 0.0;
$loadSeries[] = $sample ? round((float) $sample['load1'], 3) : 0.0;
$ioWaitSeries[] = $sample ? round((float) $sample['iowait'], 2) : 0.0;
$memorySeries[] = $sample ? round((float) $sample['memory'], 1) : 0.0;
$swapSeries[] = $sample ? round((float) $sample['swap'], 1) : 0.0;
}
return [

View File

@@ -1027,9 +1027,9 @@ configure_sysstat() {
fi
if grep -q '^INTERVAL=' /etc/default/sysstat; then
sed -i 's/^INTERVAL=.*/INTERVAL=1/' /etc/default/sysstat
sed -i 's/^INTERVAL=.*/INTERVAL=10/' /etc/default/sysstat
else
echo 'INTERVAL=1' >> /etc/default/sysstat
echo 'INTERVAL=10' >> /etc/default/sysstat
fi
fi
@@ -1059,9 +1059,9 @@ configure_sysstat() {
fi
if grep -q '^INTERVAL=' /etc/sysstat/sysstat; then
sed -i 's/^INTERVAL=.*/INTERVAL=1/' /etc/sysstat/sysstat
sed -i 's/^INTERVAL=.*/INTERVAL=10/' /etc/sysstat/sysstat
else
echo 'INTERVAL=1' >> /etc/sysstat/sysstat
echo 'INTERVAL=10' >> /etc/sysstat/sysstat
fi
fi
@@ -1070,6 +1070,7 @@ configure_sysstat() {
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnActiveSec=10s
OnUnitActiveSec=10s
AccuracySec=1s
Persistent=true

View File

@@ -995,9 +995,9 @@ configure_sysstat() {
fi
if grep -q '^INTERVAL=' /etc/default/sysstat; then
sed -i 's/^INTERVAL=.*/INTERVAL=1/' /etc/default/sysstat
sed -i 's/^INTERVAL=.*/INTERVAL=10/' /etc/default/sysstat
else
echo 'INTERVAL=1' >> /etc/default/sysstat
echo 'INTERVAL=10' >> /etc/default/sysstat
fi
fi
@@ -1027,9 +1027,9 @@ configure_sysstat() {
fi
if grep -q '^INTERVAL=' /etc/sysstat/sysstat; then
sed -i 's/^INTERVAL=.*/INTERVAL=1/' /etc/sysstat/sysstat
sed -i 's/^INTERVAL=.*/INTERVAL=10/' /etc/sysstat/sysstat
else
echo 'INTERVAL=1' >> /etc/sysstat/sysstat
echo 'INTERVAL=10' >> /etc/sysstat/sysstat
fi
fi
@@ -1038,6 +1038,7 @@ configure_sysstat() {
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnActiveSec=10s
OnUnitActiveSec=10s
AccuracySec=1s
Persistent=true