Fix sysstat timer start and chart layout
This commit is contained in:
@@ -13,8 +13,6 @@ class ServerChartsWidget extends Widget
|
|||||||
|
|
||||||
protected int|string|array $columnSpan = 'full';
|
protected int|string|array $columnSpan = 'full';
|
||||||
|
|
||||||
protected ?string $pollingInterval = '60s';
|
|
||||||
|
|
||||||
public int $refreshKey = 0;
|
public int $refreshKey = 0;
|
||||||
|
|
||||||
public string $range = '5m';
|
public string $range = '5m';
|
||||||
@@ -201,7 +199,7 @@ class ServerChartsWidget extends Widget
|
|||||||
{
|
{
|
||||||
return match ($range) {
|
return match ($range) {
|
||||||
'5m' => ['minutes' => 5, 'points' => 30, 'resolution' => '10s', 'label_format' => 'H:i:s', 'interval_seconds' => 10],
|
'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],
|
'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],
|
'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],
|
'month' => ['minutes' => 43200, 'points' => 30, 'resolution' => '1d', 'label_format' => 'M d', 'interval_seconds' => 86400],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Services;
|
|||||||
|
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
class SysstatMetrics
|
class SysstatMetrics
|
||||||
@@ -34,13 +35,18 @@ class SysstatMetrics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$start = $end->subSeconds(($points - 1) * $intervalSeconds);
|
$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 Cache::remember($cacheKey, $ttl, function () use ($start, $end, $points, $intervalSeconds, $labelFormat): array {
|
||||||
return [];
|
$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();
|
$timezone = $this->systemTimezone();
|
||||||
$end = CarbonImmutable::now($timezone);
|
$end = CarbonImmutable::now($timezone);
|
||||||
$start = $end->subMinutes(15);
|
$start = $end->subMinutes(15);
|
||||||
$samples = $this->readSamples($start, $end);
|
$bucket = intdiv($end->getTimestamp(), 10);
|
||||||
|
$cacheKey = sprintf('sysstat.latest.%d', $bucket);
|
||||||
|
|
||||||
if (empty($samples)) {
|
return Cache::remember($cacheKey, now()->addSeconds(10), function () use ($start, $end): ?array {
|
||||||
return null;
|
$samples = $this->readSamples($start, $end, $this->coreOptions());
|
||||||
}
|
if (empty($samples)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$last = end($samples);
|
$last = end($samples);
|
||||||
if (! is_array($last)) {
|
if (! is_array($last)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'load1' => (float) ($last['load1'] ?? 0),
|
'load1' => (float) ($last['load1'] ?? 0),
|
||||||
'load5' => (float) ($last['load5'] ?? 0),
|
'load5' => (float) ($last['load5'] ?? 0),
|
||||||
'load15' => (float) ($last['load15'] ?? 0),
|
'load15' => (float) ($last['load15'] ?? 0),
|
||||||
'iowait' => (float) ($last['iowait'] ?? 0),
|
'iowait' => (float) ($last['iowait'] ?? 0),
|
||||||
'memory' => (float) ($last['memory'] ?? 0),
|
'memory' => (float) ($last['memory'] ?? 0),
|
||||||
'swap' => (float) ($last['swap'] ?? 0),
|
'swap' => (float) ($last['swap'] ?? 0),
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function timezoneName(): string
|
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}>
|
* @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 = [];
|
$samples = [];
|
||||||
$current = $start->startOfDay();
|
$current = $start->startOfDay();
|
||||||
$lastDay = $end->startOfDay();
|
$lastDay = $end->startOfDay();
|
||||||
|
|
||||||
while ($current <= $lastDay) {
|
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)) {
|
if (! is_readable($file)) {
|
||||||
$current = $current->addDay();
|
$current = $current->addDay();
|
||||||
|
|
||||||
@@ -96,7 +108,7 @@ class SysstatMetrics
|
|||||||
|
|
||||||
$dayStart = $current->isSameDay($start) ? $start : $current->startOfDay();
|
$dayStart = $current->isSameDay($start) ? $start : $current->startOfDay();
|
||||||
$dayEnd = $current->isSameDay($end) ? $end : $current->endOfDay();
|
$dayEnd = $current->isSameDay($end) ? $end : $current->endOfDay();
|
||||||
$statistics = $this->readSadf($file, $dayStart, $dayEnd);
|
$statistics = $this->readSadf($file, $dayStart, $dayEnd, $options);
|
||||||
|
|
||||||
foreach ($statistics as $stat) {
|
foreach ($statistics as $stat) {
|
||||||
$parsed = $this->parseSample($stat);
|
$parsed = $this->parseSample($stat);
|
||||||
@@ -117,20 +129,25 @@ class SysstatMetrics
|
|||||||
/**
|
/**
|
||||||
* @return array<int, array<string, mixed>>
|
* @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([
|
$args = array_merge(
|
||||||
'sadf',
|
[
|
||||||
'-j',
|
'sadf',
|
||||||
'-T',
|
'-j',
|
||||||
$file,
|
'-T',
|
||||||
'--',
|
$file,
|
||||||
'-A',
|
'--',
|
||||||
'-s',
|
],
|
||||||
$start->format('H:i:s'),
|
$options,
|
||||||
'-e',
|
[
|
||||||
$end->format('H:i:s'),
|
'-s',
|
||||||
]);
|
$start->format('H:i:s'),
|
||||||
|
'-e',
|
||||||
|
$end->format('H:i:s'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$process = new Process($args);
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
if (! $process->isSuccessful()) {
|
if (! $process->isSuccessful()) {
|
||||||
@@ -150,6 +167,23 @@ class SysstatMetrics
|
|||||||
return $stats;
|
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
|
* @param array<string, mixed> $stat
|
||||||
* @return array{timestamp: int, load1: float, load5: float, load15: float, iowait: float, memory: float, swap: float}|null
|
* @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;
|
$index = 0;
|
||||||
$current = null;
|
$current = null;
|
||||||
|
$first = $samples[0] ?? null;
|
||||||
$count = count($samples);
|
$count = count($samples);
|
||||||
|
|
||||||
for ($i = 0; $i < $points; $i++) {
|
for ($i = 0; $i < $points; $i++) {
|
||||||
@@ -214,11 +249,12 @@ class SysstatMetrics
|
|||||||
$index++;
|
$index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sample = $current ?? $first;
|
||||||
$labels[] = $bucketTime->format($labelFormat);
|
$labels[] = $bucketTime->format($labelFormat);
|
||||||
$loadSeries[] = $current ? round((float) $current['load1'], 3) : 0.0;
|
$loadSeries[] = $sample ? round((float) $sample['load1'], 3) : 0.0;
|
||||||
$ioWaitSeries[] = $current ? round((float) $current['iowait'], 2) : 0.0;
|
$ioWaitSeries[] = $sample ? round((float) $sample['iowait'], 2) : 0.0;
|
||||||
$memorySeries[] = $current ? round((float) $current['memory'], 1) : 0.0;
|
$memorySeries[] = $sample ? round((float) $sample['memory'], 1) : 0.0;
|
||||||
$swapSeries[] = $current ? round((float) $current['swap'], 1) : 0.0;
|
$swapSeries[] = $sample ? round((float) $sample['swap'], 1) : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1027,9 +1027,9 @@ configure_sysstat() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q '^INTERVAL=' /etc/default/sysstat; then
|
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
|
else
|
||||||
echo 'INTERVAL=1' >> /etc/default/sysstat
|
echo 'INTERVAL=10' >> /etc/default/sysstat
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1059,9 +1059,9 @@ configure_sysstat() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q '^INTERVAL=' /etc/sysstat/sysstat; then
|
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
|
else
|
||||||
echo 'INTERVAL=1' >> /etc/sysstat/sysstat
|
echo 'INTERVAL=10' >> /etc/sysstat/sysstat
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1070,6 +1070,7 @@ configure_sysstat() {
|
|||||||
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
|
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=
|
OnCalendar=
|
||||||
|
OnActiveSec=10s
|
||||||
OnUnitActiveSec=10s
|
OnUnitActiveSec=10s
|
||||||
AccuracySec=1s
|
AccuracySec=1s
|
||||||
Persistent=true
|
Persistent=true
|
||||||
|
|||||||
@@ -995,9 +995,9 @@ configure_sysstat() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q '^INTERVAL=' /etc/default/sysstat; then
|
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
|
else
|
||||||
echo 'INTERVAL=1' >> /etc/default/sysstat
|
echo 'INTERVAL=10' >> /etc/default/sysstat
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1027,9 +1027,9 @@ configure_sysstat() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q '^INTERVAL=' /etc/sysstat/sysstat; then
|
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
|
else
|
||||||
echo 'INTERVAL=1' >> /etc/sysstat/sysstat
|
echo 'INTERVAL=10' >> /etc/sysstat/sysstat
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1038,6 +1038,7 @@ configure_sysstat() {
|
|||||||
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
|
cat > /etc/systemd/system/sysstat-collect.timer.d/override.conf <<'EOF'
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=
|
OnCalendar=
|
||||||
|
OnActiveSec=10s
|
||||||
OnUnitActiveSec=10s
|
OnUnitActiveSec=10s
|
||||||
AccuracySec=1s
|
AccuracySec=1s
|
||||||
Persistent=true
|
Persistent=true
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user