Add Server stats (CPU, RAM, Disk, System) to admin Infra page

First row shows host-level stats from /proc: CPU model, cores, load
average with charge %, RAM total/used/available with usage %, disk
total/used/free with usage %, hostname, OS and uptime. All color-coded
green <70%, yellow <90%, red >=90%.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-26 10:41:41 +01:00
parent d7cecfe9ef
commit 08433321d0
2 changed files with 135 additions and 0 deletions

View File

@@ -664,8 +664,10 @@ class AdminController extends AbstractController
$pgbouncerInfo = $this->getPgBouncerInfo($databaseUrl);
$phpContainers = $this->getPhpContainerInfo();
$server = $this->getServerInfo();
return $this->render('admin/infra.html.twig', [
'server' => $server,
'redis_global' => $redisGlobal,
'redis_dbs' => [$redisMessenger, $redisSession, $redisCache],
'postgres' => $pgInfo,
@@ -674,6 +676,84 @@ class AdminController extends AbstractController
]);
}
/**
* @return array<string, mixed>
*/
private function getServerInfo(): array
{
$info = [];
// CPU
$info['cpu_model'] = '?';
$info['cpu_cores'] = 1;
if (is_readable('/proc/cpuinfo')) {
$cpuinfo = file_get_contents('/proc/cpuinfo');
$info['cpu_cores'] = substr_count($cpuinfo, 'processor') ?: 1;
if (preg_match('/model name\s*:\s*(.+)/i', $cpuinfo, $m)) {
$info['cpu_model'] = trim($m[1]);
}
}
$load = sys_getloadavg();
$info['load_1m'] = $load ? round($load[0], 2) : '?';
$info['load_5m'] = $load ? round($load[1], 2) : '?';
$info['load_15m'] = $load ? round($load[2], 2) : '?';
$info['load_percent'] = $load ? round($load[0] / $info['cpu_cores'] * 100, 1) : '?';
// RAM from /proc/meminfo (host-level)
$info['ram_total'] = '?';
$info['ram_used'] = '?';
$info['ram_free'] = '?';
$info['ram_usage_percent'] = '?';
$info['ram_available'] = '?';
if (is_readable('/proc/meminfo')) {
$meminfo = file_get_contents('/proc/meminfo');
$values = [];
foreach (['MemTotal', 'MemFree', 'MemAvailable', 'Buffers', 'Cached'] as $key) {
if (preg_match("/{$key}:\s+(\d+)\s+kB/", $meminfo, $m)) {
$values[$key] = (int) $m[1] * 1024;
}
}
if (isset($values['MemTotal'])) {
$total = $values['MemTotal'];
$available = $values['MemAvailable'] ?? ($values['MemFree'] + ($values['Buffers'] ?? 0) + ($values['Cached'] ?? 0));
$used = $total - $available;
$info['ram_total'] = $this->formatBytes($total);
$info['ram_used'] = $this->formatBytes($used);
$info['ram_free'] = $this->formatBytes($available);
$info['ram_usage_percent'] = round($used / $total * 100, 1);
}
}
// Disk
$path = '/app';
$diskTotal = @disk_total_space($path);
$diskFree = @disk_free_space($path);
if (false !== $diskTotal && false !== $diskFree) {
$diskUsed = $diskTotal - $diskFree;
$info['disk_total'] = $this->formatBytes((int) $diskTotal);
$info['disk_used'] = $this->formatBytes((int) $diskUsed);
$info['disk_free'] = $this->formatBytes((int) $diskFree);
$info['disk_usage_percent'] = round($diskUsed / $diskTotal * 100, 1);
} else {
$info['disk_total'] = '?';
$info['disk_used'] = '?';
$info['disk_free'] = '?';
$info['disk_usage_percent'] = '?';
}
// Uptime & hostname
if (is_readable('/proc/uptime')) {
$info['uptime'] = $this->formatSeconds((int) explode(' ', file_get_contents('/proc/uptime'))[0]);
} else {
$info['uptime'] = '?';
}
$info['hostname'] = gethostname() ?: '?';
$info['os'] = php_uname('s').' '.php_uname('r');
return $info;
}
/**
* @return list<array<string, mixed>>
*/

View File

@@ -5,6 +5,61 @@
{% block body %}
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page mb-8">Infrastructure</h1>
{# Server #}
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">Serveur</h2>
<div class="flex flex-wrap gap-6 mb-8">
{# CPU #}
<div class="flex-1 min-w-[280px]">
<div class="admin-card">
<h2 class="text-sm font-black uppercase tracking-widest mb-4">CPU</h2>
<div class="flex flex-col gap-0">
{{ _self.row('Modele', server.cpu_model) }}
{{ _self.row('Coeurs', server.cpu_cores) }}
{{ _self.row('Load 1m / 5m / 15m', server.load_1m ~ ' / ' ~ server.load_5m ~ ' / ' ~ server.load_15m) }}
{{ _self.row_color('Charge CPU', server.load_percent != '?' ? server.load_percent ~ '%' : '?', server.load_percent != '?' and server.load_percent < 70 ? 'green' : (server.load_percent == '?' ? 'gray' : (server.load_percent < 90 ? 'yellow' : 'red'))) }}
</div>
</div>
</div>
{# RAM #}
<div class="flex-1 min-w-[280px]">
<div class="admin-card">
<h2 class="text-sm font-black uppercase tracking-widest mb-4">RAM</h2>
<div class="flex flex-col gap-0">
{{ _self.row('Total', server.ram_total) }}
{{ _self.row('Utilisee', server.ram_used) }}
{{ _self.row('Disponible', server.ram_free) }}
{{ _self.row_color('Utilisation', server.ram_usage_percent != '?' ? server.ram_usage_percent ~ '%' : '?', server.ram_usage_percent != '?' and server.ram_usage_percent < 70 ? 'green' : (server.ram_usage_percent == '?' ? 'gray' : (server.ram_usage_percent < 90 ? 'yellow' : 'red'))) }}
</div>
</div>
</div>
{# Disk #}
<div class="flex-1 min-w-[280px]">
<div class="admin-card">
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Disque</h2>
<div class="flex flex-col gap-0">
{{ _self.row('Total', server.disk_total) }}
{{ _self.row('Utilise', server.disk_used) }}
{{ _self.row('Libre', server.disk_free) }}
{{ _self.row_color('Utilisation', server.disk_usage_percent != '?' ? server.disk_usage_percent ~ '%' : '?', server.disk_usage_percent != '?' and server.disk_usage_percent < 70 ? 'green' : (server.disk_usage_percent == '?' ? 'gray' : (server.disk_usage_percent < 90 ? 'yellow' : 'red'))) }}
</div>
</div>
</div>
{# System #}
<div class="flex-1 min-w-[280px]">
<div class="admin-card">
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Systeme</h2>
<div class="flex flex-col gap-0">
{{ _self.row('Hostname', server.hostname) }}
{{ _self.row('OS', server.os) }}
{{ _self.row('Uptime', server.uptime) }}
</div>
</div>
</div>
</div>
{# PHP Containers #}
<h2 class="text-xl font-black uppercase tracking-tighter italic heading-page mb-4">PHP</h2>
<div class="flex flex-wrap gap-6 mb-8">