diff --git a/app/Filament/Admin/Widgets/ServerChartsWidget.php b/app/Filament/Admin/Widgets/ServerChartsWidget.php index dd2f150..2e6f7cd 100644 --- a/app/Filament/Admin/Widgets/ServerChartsWidget.php +++ b/app/Filament/Admin/Widgets/ServerChartsWidget.php @@ -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], diff --git a/app/Services/SysstatMetrics.php b/app/Services/SysstatMetrics.php index 918f6e0..cafa564 100644 --- a/app/Services/SysstatMetrics.php +++ b/app/Services/SysstatMetrics.php @@ -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 */ - 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> */ - 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 + */ + 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 $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 [ diff --git a/install.sh b/install.sh index 4ce11a1..512af1b 100755 --- a/install.sh +++ b/install.sh @@ -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 diff --git a/install_from_gitea.sh b/install_from_gitea.sh index 2a335e3..7b85b44 100755 --- a/install_from_gitea.sh +++ b/install_from_gitea.sh @@ -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 diff --git a/resources/views/filament/admin/widgets/server-charts.blade.php b/resources/views/filament/admin/widgets/server-charts.blade.php index 4b4e62d..43ae02f 100644 --- a/resources/views/filament/admin/widgets/server-charts.blade.php +++ b/resources/views/filament/admin/widgets/server-charts.blade.php @@ -1,11 +1,28 @@ @php $data = $this->getData(); @@ -45,7 +61,7 @@ }; $historyLabelFormat = match ($range) { '5m' => 'H:i:s', - '30m' => 'H:i', + '30m' => 'H:i:s', 'day' => 'H:00', 'week' => 'M d H:00', 'month' => 'M d', @@ -113,66 +129,35 @@