Monitoring système#
Métriques système fondamentales#
Superviser un système Linux revient à mesurer en continu quatre grandes familles de ressources : le processeur, la mémoire, les entrées/sorties et le réseau. Chaque famille expose des métriques distinctes que le noyau agrège dans le pseudo-système de fichiers /proc.
CPU — les états de temps processeur#
Le noyau comptabilise le temps passé par chaque cœur dans plusieurs états, exprimés en jiffies (unité de temps interne) puis convertis en pourcentage :
État |
Signification |
|---|---|
user |
Temps passé à exécuter du code applicatif en espace utilisateur |
nice |
Temps passé pour des processus à priorité réduite (nice positif) |
system |
Temps passé dans le noyau (appels système, interruptions logicielles) |
iowait |
Temps d’attente d’opérations I/O disque (la CPU est inactive mais le système est bloqué) |
irq |
Gestion des interruptions matérielles |
softirq |
Interruptions logicielles (traitements réseau, timer) |
steal |
Temps « volé » par l’hyperviseur sur une machine virtuelle |
idle |
Temps réellement inactif |
Un iowait élevé (> 20 %) indique une saturation du sous-système disque. Un steal non nul signale une contention CPU sur l’hôte de virtualisation — un problème invisible depuis l’intérieur de la VM mais mesurable depuis /proc/stat.
Mémoire — RSS, VSZ, cache et buffers#
La comptabilité mémoire Linux distingue plusieurs notions souvent confondues :
VSZ (Virtual Set Size) : espace d’adressage virtuel réservé par le processus, inclut les bibliothèques partagées, les zones anonymes non encore allouées physiquement (lazy allocation). Toujours supérieur à RSS.
RSS (Resident Set Size) : pages réellement présentes en mémoire physique pour ce processus, hors swap.
Cache de page : mémoire utilisée par le noyau pour mettre en cache les blocs disque lus récemment. Libérée immédiatement si une application en a besoin — ne jamais interpréter ce chiffre comme « mémoire perdue ».
Buffers : mémoire tampon pour les métadonnées du système de fichiers (inodes, répertoires). Distincte du cache de page mais souvent agrégée avec lui dans les outils de monitoring.
La formule mémoire disponible = free + buffers + cache est celle qui importe pour évaluer la pression mémoire réelle. Le champ MemAvailable de /proc/meminfo l’estime directement.
I/O — débit, latence et queue depth#
Les métriques I/O clés sont :
r/s, w/s : opérations de lecture/écriture par seconde
rkB/s, wkB/s : débit en kilo-octets par seconde
await : temps moyen d’attente d’une opération I/O (queue + service), en millisecondes
%util : pourcentage de temps où le périphérique avait au moins une requête en cours (saturation si proche de 100 %)
avgqu-sz : taille moyenne de la file d’attente du périphérique
Réseau — paquets, débit et erreurs#
Les compteurs réseau exposés par /proc/net/dev comprennent pour chaque interface : octets reçus/envoyés, paquets, erreurs, collisions et paquets abandonnés. Une augmentation régulière des erreurs ou des drops indique une saturation de buffers ou un problème de pilote.
Outils temps réel#
top — l’outil universel#
top affiche la liste des processus actualisée toutes les 3 secondes par défaut. Les colonnes importantes :
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 www-data 20 0 512m 48m 12m S 4.7 0.6 0:23.11 nginx
PR : priorité noyau réelle (20 + NI pour les processus normaux)
NI : valeur nice (-20 à 19)
VIRT : VSZ, RES : RSS, SHR : pages partagées
S : état (R=running, S=sleeping, D=uninterruptible sleep, Z=zombie, T=stopped)
Raccourcis interactifs utiles : M (tri par mémoire), P (tri par CPU), k (envoyer un signal), r (renice), 1 (afficher chaque cœur), H (afficher les threads), W (sauvegarder la configuration).
htop et btop — alternatives enrichies#
htop ajoute une interface ncurses avec barres de progression, navigation à la souris, arborescence des processus (F5), filtrage (F4) et tri multi-colonnes (F6). Il permet aussi de tuer ou renommer un processus directement.
btop (anciennement bpytop) propose une visualisation graphique ASCII en temps réel de la CPU, mémoire, swap, réseau et disque dans un seul écran. Particulièrement lisible sur les terminaux haute résolution.
uptime et w#
uptime
14:32:07 up 42 days, 3:17, 2 users, load average: 0.85, 1.12, 1.04
La charge moyenne (load average) mesure le nombre moyen de processus en état R (running) ou D (uninterruptible sleep) sur 1, 5 et 15 minutes. Une valeur inférieure au nombre de cœurs logiques indique un système non saturé.
w
14:32:07 up 42 days, 3:17, 2 users, load average: 0.85, 1.12, 1.04
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
alice pts/0 192.168.1.10 14:10 0.00s 0.12s 0.02s w
bob pts/1 192.168.1.25 13:55 10:22 0.05s 0.05s vim /etc/nginx/nginx.conf
w combine la sortie d”uptime avec la liste des utilisateurs connectés et leur activité en cours.
vmstat et iostat#
vmstat — vue globale du système#
vmstat 2 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1240320 45612 2341200 0 0 14 28 312 645 8 2 89 1 0
0 0 0 1238912 45620 2342100 0 0 0 48 289 612 5 2 93 0 0
2 0 0 1237400 45628 2342500 0 0 0 16 445 890 18 4 78 0 0
Colonnes critiques :
Colonne |
Description |
|---|---|
r |
Processus en file d’attente d’exécution (runqueue) |
b |
Processus bloqués en uninterruptible sleep (I/O) |
swpd |
Mémoire swap utilisée (Ko) |
bi / bo |
Blocs lus/écrits depuis/vers le swap (blocs/s) |
si / so |
Pages swappées depuis/vers le disque (swap in/out) |
wa |
% CPU en iowait |
in |
Interruptions par seconde |
cs |
Context switches par seconde |
Un r > nb_cœurs persistant indique une saturation CPU. Un b > 0 persistant couplé à wa > 10 % signale un sous-système I/O saturé. Des valeurs si/so non nulles révèlent du swapping actif — situation à éviter en production.
iostat — métriques I/O par périphérique#
iostat -xz 2 3
Device r/s w/s rkB/s wkB/s await r_await w_await %util
sda 12.4 38.7 496.0 1548.0 2.14 1.82 2.28 18.4
nvme0n1 45.2 120.3 1808.0 4812.0 0.38 0.31 0.41 8.7
L’option -x affiche les statistiques étendues, -z masque les périphériques sans activité. Un await > 20 ms pour un SSD signale un problème sérieux (normalement < 1 ms). Pour un disque rotatif, des valeurs jusqu’à 10 ms sont acceptables en charge mixte.
sar et sysstat#
sysstat est une suite d’outils de collecte et d’analyse historique des performances. Elle comprend sar, sadc (collecteur), sadf (formateur), pidstat, cifsiostat et nfsiostat.
Activation de la collecte#
# Debian/Ubuntu
apt install sysstat
# Activer la collecte
sed -i 's/ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
systemctl enable --now sysstat
Le daemon sadc collecte les données toutes les 10 minutes (configurable dans /etc/cron.d/sysstat) et les archive dans /var/log/sysstat/sa<JJ>.
Commandes sar essentielles#
# Utilisation CPU sur les dernières 24h
sar -u
# Utilisation mémoire
sar -r
# Statistiques I/O par périphérique
sar -d -p
# Trafic réseau
sar -n DEV
# Données d'hier
sar -u -f /var/log/sysstat/sa$(date -d yesterday +%d)
# Plage horaire spécifique
sar -u --start 08:00:00 --end 18:00:00
# sar -u 1 5
Linux 6.1.0 (serveur) 2026-03-24 _x86_64_ (8 CPU)
14:35:01 CPU %user %nice %system %iowait %steal %idle
14:35:02 all 8.37 0.00 2.14 0.50 0.00 89.00
14:35:03 all 12.45 0.00 3.27 0.25 0.00 84.03
Archives sysstat
Les archives binaires /var/log/sysstat/saXX sont lisibles avec sadf -d saXX -- -u pour obtenir un format CSV, ou sadf -j saXX -- -u pour du JSON. Cela facilite l’intégration dans des pipelines d’analyse.
Lecture de /proc#
Le répertoire /proc est un système de fichiers virtuel exposant l’état interne du noyau sous forme de fichiers texte. C’est la source primaire de toutes les métriques système — tous les outils (top, vmstat, sar) ne font que le lire.
/proc/meminfo#
cat /proc/meminfo | head -20
MemTotal: 16384000 kB
MemFree: 1240320 kB
MemAvailable: 5120000 kB
Buffers: 45612 kB
Cached: 2341200 kB
SwapCached: 0 kB
Active: 4512000 kB
Inactive: 2100000 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
Dirty: 1024 kB
Writeback: 0 kB
AnonPages: 3200000 kB
Mapped: 512000 kB
Shmem: 200000 kB
/proc/stat — calcul manuel du % CPU#
import time
def lire_cpu_stat():
with open("/proc/stat") as f:
ligne = f.readline()
vals = list(map(int, ligne.split()[1:]))
idle = vals[3] + vals[4] # idle + iowait
total = sum(vals)
return total, idle
t1, i1 = lire_cpu_stat()
time.sleep(0.5)
t2, i2 = lire_cpu_stat()
delta_total = t2 - t1
delta_idle = i2 - i1
cpu_pct = 100.0 * (1 - delta_idle / delta_total) if delta_total > 0 else 0.0
print(f"Utilisation CPU (calcul /proc/stat) : {cpu_pct:.1f} %")
print(f" delta_total={delta_total} jiffies, delta_idle={delta_idle} jiffies")
Utilisation CPU (calcul /proc/stat) : 4.7 %
delta_total=401 jiffies, delta_idle=382 jiffies
/proc/diskstats et /proc/net/dev#
cat /proc/diskstats | grep -v " 0 0 0 0 0 0 0 0 0 0 0"
259 0 nvme0n1 45231 1204 2305482 12340 120345 48203 6712044 234567 0 180234 247890
Les 11 champs par périphérique correspondent aux compteurs définis dans la documentation du noyau (Documentation/admin-guide/iostats.txt) : lectures complétées, lectures fusionnées, secteurs lus, temps de lecture (ms), et les équivalents pour les écritures.
psutil Python#
psutil (Process and System UTILities) est la bibliothèque Python de référence pour accéder aux métriques système de façon portable. Elle lit /proc sous Linux mais fonctionne également sur macOS et Windows.
Utilisation CPU par cœur#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Mesure sur 0.5s pour avoir des valeurs représentatives
cpu_par_coeur = psutil.cpu_percent(percpu=True, interval=0.5)
cpu_times = psutil.cpu_times()
n = len(cpu_par_coeur)
labels = [f"CPU{i}" for i in range(n)]
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Barplot utilisation par cœur
axes[0].bar(labels, cpu_par_coeur, color=sns.color_palette("muted")[0])
axes[0].set_ylim(0, 100)
axes[0].set_xlabel("Cœur logique")
axes[0].set_ylabel("Utilisation (%)")
axes[0].set_title("Utilisation CPU par cœur")
for i, v in enumerate(cpu_par_coeur):
axes[0].text(i, v + 1, f"{v:.0f}%", ha="center", va="bottom", fontsize=9)
# Répartition des états CPU (cpu_times)
etats = ["user", "system", "iowait", "irq", "softirq", "idle"]
valeurs = []
for e in etats:
valeurs.append(getattr(cpu_times, e, 0.0))
total_t = sum(valeurs)
pcts = [100 * v / total_t for v in valeurs] if total_t > 0 else valeurs
axes[1].pie(
pcts,
labels=etats,
autopct="%1.1f%%",
colors=sns.color_palette("muted", len(etats)),
startangle=90,
)
axes[1].set_title("Répartition des états CPU (cumulé)")
plt.suptitle("Métriques CPU — psutil", fontsize=13, fontweight="bold")
plt.show()
Mémoire — donut chart#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
mem = psutil.virtual_memory()
swap = psutil.swap_memory()
def octets_vers_gio(n):
return n / (1024 ** 3)
# Segments du donut : used, cached, buffers, free
used_pure = mem.used - getattr(mem, "cached", 0) - getattr(mem, "buffers", 0)
cached = getattr(mem, "cached", 0)
buffers = getattr(mem, "buffers", 0)
free = mem.free
# S'assurer que rien n'est négatif
used_pure = max(used_pure, 0)
labels = ["Utilisée", "Cache page", "Buffers", "Libre"]
sizes = [octets_vers_gio(v) for v in [used_pure, cached, buffers, free]]
couleurs = sns.color_palette("muted", 4)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Donut mémoire principale
wedges, texts, autotexts = axes[0].pie(
sizes,
labels=labels,
autopct="%1.1f%%",
colors=couleurs,
startangle=90,
wedgeprops=dict(width=0.5),
)
axes[0].set_title(
f"Mémoire RAM — {octets_vers_gio(mem.total):.1f} GiB total\n"
f"Disponible : {octets_vers_gio(mem.available):.1f} GiB",
fontsize=11,
)
# Barres swap
categories = ["Swap utilisé", "Swap libre"]
valeurs_swap = [octets_vers_gio(swap.used), octets_vers_gio(swap.free)]
bars = axes[1].bar(categories, valeurs_swap,
color=[sns.color_palette("muted")[3], sns.color_palette("muted")[2]])
axes[1].set_ylabel("GiB")
axes[1].set_title(f"Swap — {octets_vers_gio(swap.total):.1f} GiB total")
for bar, val in zip(bars, valeurs_swap):
axes[1].text(bar.get_x() + bar.get_width() / 2,
bar.get_height() + 0.02, f"{val:.2f} GiB",
ha="center", va="bottom")
plt.suptitle("Métriques mémoire — psutil", fontsize=13, fontweight="bold")
plt.show()
I/O disque#
io = psutil.disk_io_counters(perdisk=True)
lignes = []
for nom, c in io.items():
lignes.append({
"Périphérique" : nom,
"Lectures" : c.read_count,
"Écritures" : c.write_count,
"Lu (MiB)" : round(c.read_bytes / 1024**2, 1),
"Écrit (MiB)" : round(c.write_bytes / 1024**2, 1),
"Tps lecture (s)" : round(c.read_time / 1000, 2),
"Tps écriture (s)": round(c.write_time / 1000, 2),
})
df_io = pd.DataFrame(lignes)
print("=== Métriques I/O disque ===")
print(df_io.to_string(index=False))
=== Métriques I/O disque ===
Périphérique Lectures Écritures Lu (MiB) Écrit (MiB) Tps lecture (s) Tps écriture (s)
sda 365419 320510 6951.9 10111.9 351.87 667.39
sda1 596 2 8.5 0.0 0.27 0.00
sda2 106978 15626 623.6 2665.8 77.62 227.68
sda3 94314 57879 3107.8 359.5 197.63 69.36
sda4 162620 246989 3200.1 7086.6 75.76 370.34
sda5 732 13 9.1 0.0 0.51 0.01
sdb 2181 28 19.4 0.0 2.15 0.03
sdb1 232 0 4.8 0.0 0.32 0.00
sdb2 1410 28 3.5 0.0 0.80 0.03
sdb3 448 0 9.8 0.0 0.88 0.00
Trafic réseau par interface#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
net = psutil.net_io_counters(pernic=True)
interfaces = []
envois = []
receptions = []
for nic, stats in net.items():
# Exclure loopback si trop dominant, garder si seul
interfaces.append(nic)
envois.append(stats.bytes_sent / 1024**2)
receptions.append(stats.bytes_recv / 1024**2)
x = np.arange(len(interfaces))
largeur = 0.35
fig, ax = plt.subplots(figsize=(10, 5))
barres1 = ax.bar(x - largeur/2, envois, largeur, label="Envoyé (MiB)", color=sns.color_palette("muted")[0])
barres2 = ax.bar(x + largeur/2, receptions, largeur, label="Reçu (MiB)", color=sns.color_palette("muted")[1])
ax.set_xticks(x)
ax.set_xticklabels(interfaces, rotation=20, ha="right")
ax.set_ylabel("MiB (cumulé depuis le démarrage)")
ax.set_title("Trafic réseau par interface — psutil")
ax.legend()
plt.show()
Top 10 processus par mémoire RSS#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
processus = []
for proc in psutil.process_iter(["pid", "name", "memory_info", "username"]):
try:
rss = proc.info["memory_info"].rss if proc.info["memory_info"] else 0
processus.append({
"PID" : proc.info["pid"],
"Nom" : proc.info["name"] or "?",
"RSS MiB" : round(rss / 1024**2, 1),
"Utilisateur": proc.info["username"] or "?",
})
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
df_proc = pd.DataFrame(processus).sort_values("RSS MiB", ascending=False).head(10)
fig, ax = plt.subplots(figsize=(10, 5))
couleurs = sns.color_palette("muted", len(df_proc))
labels_proc = df_proc.apply(lambda r: f"{r['Nom']} ({r['PID']})", axis=1)
ax.barh(labels_proc[::-1], df_proc["RSS MiB"][::-1], color=couleurs)
ax.set_xlabel("Mémoire RSS (MiB)")
ax.set_title("Top 10 processus par consommation mémoire RSS")
for i, (val, label) in enumerate(zip(df_proc["RSS MiB"][::-1], labels_proc[::-1])):
ax.text(val + 0.5, i, f"{val} MiB", va="center", fontsize=9)
plt.show()
Alertes et seuils#
Script bash de surveillance#
Un script de monitoring minimaliste peut être planifié via cron ou exécuté en démon :
#!/bin/bash
# /usr/local/bin/check_resources.sh
SEUIL_CPU=80
SEUIL_MEM=90
SEUIL_DISK=85
DESTINATAIRE="admin@example.com"
# CPU utilisation (moyenne sur 1 mesure vmstat)
CPU_IDLE=$(vmstat 1 2 | tail -1 | awk '{print $15}')
CPU_USED=$((100 - CPU_IDLE))
# Mémoire
MEM_POURCENT=$(free | awk '/Mem:/ {printf "%.0f", ($3/$2)*100}')
# Disque racine
DISK_POURCENT=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
alerte() {
local SUJET="$1"
local CORPS="$2"
echo "$CORPS" | mail -s "[ALERTE] $SUJET" "$DESTINATAIRE"
logger -t monitoring "[ALERTE] $SUJET"
}
[ "$CPU_USED" -gt "$SEUIL_CPU" ] && alerte "CPU élevé" "CPU: ${CPU_USED}%"
[ "$MEM_POURCENT" -gt "$SEUIL_MEM" ] && alerte "Mémoire" "RAM: ${MEM_POURCENT}%"
[ "$DISK_POURCENT" -gt "$SEUIL_DISK" ] && alerte "Disque /" "Disque: ${DISK_POURCENT}%"
Intégration avec systemd OnFailure#
Pour qu’un service envoie une alerte en cas d’échec :
# /etc/systemd/system/mon-app.service
[Unit]
Description=Mon application
OnFailure=alerter-failure@%n.service
[Service]
ExecStart=/opt/mon-app/start.sh
Restart=on-failure
RestartSec=10s
# /etc/systemd/system/alerter-failure@.service
[Unit]
Description=Notification d'échec pour %i
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notify-failure.sh %i
Seuils et faux positifs
Des seuils trop bas génèrent des alertes intempestives qui finissent par être ignorées (fatigue d’alerte). Calibrer les seuils sur l’historique sar de la semaine précédente : utiliser le 95e percentile + 20 % comme point de départ, puis affiner selon le profil de charge de l’application.
Prometheus et Grafana#
Architecture#
Prometheus est un système de monitoring open source basé sur un modèle de collecte par pull : le serveur Prometheus interroge périodiquement des exporters qui exposent des métriques au format texte sur un endpoint HTTP (/metrics).
┌─────────────────┐ scrape ┌────────────────┐
│ Serveur cible │ ←────────────────── │ Prometheus │
│ node_exporter │ /metrics HTTP │ (serveur) │
│ :9100 │ └───────┬────────┘
└─────────────────┘ │ PromQL queries
┌──────▼──────┐
│ Grafana │
│ (dashboards)│
└─────────────┘
node_exporter#
node_exporter est l’exporter officiel Prometheus pour les métriques système Linux. Il expose plus de 700 métriques nativement :
# Installation
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/\
node_exporter-1.7.0.linux-amd64.tar.gz
tar xf node_exporter-1.7.0.linux-amd64.tar.gz
mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/
# Service systemd
systemctl enable --now node_exporter
# Vérification
curl -s http://localhost:9100/metrics | grep "^node_cpu_seconds"
node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67
node_cpu_seconds_total{cpu="0",mode="iowait"} 45.12
node_cpu_seconds_total{cpu="0",mode="system"} 234.89
node_cpu_seconds_total{cpu="0",mode="user"} 1023.45
Requêtes PromQL essentielles#
# % CPU utilisé (sur 5 minutes glissantes)
100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Mémoire disponible en GiB
node_memory_MemAvailable_bytes / 1024^3
# Trafic réseau entrant par interface
rate(node_network_receive_bytes_total[5m]) * 8
Rétention et haute disponibilité
Prometheus stocke ses données localement par défaut (15 jours). Pour une rétention longue durée, intégrer Thanos ou Cortex pour le stockage objet distribué. Grafana se connecte à Prometheus comme datasource et propose des dashboards communautaires prêts à l’emploi (ID 1860 : « Node Exporter Full »).
Résumé#
Le monitoring système repose sur la lecture cohérente de métriques issues de /proc, exposées et historisées par des outils spécialisés. La progression naturelle va des outils interactifs (top, htop) pour le diagnostic immédiat, aux outils d’agrégation temporelle (sar, vmstat) pour l’analyse de tendance, jusqu’aux plateformes de monitoring continues (Prometheus/Grafana) pour la supervision à l’échelle.
Points à retenir :
iowait et saturation disque (
%util,avgqu-sz) sont les indicateurs I/O les plus critiques.La mémoire disponible (
MemAvailable) est plus pertinente que la mémoire libre brute.Le steal CPU est invisible aux outils classiques mais mesurable depuis
/proc/stat— crucial en environnement virtualisé.psutiloffre un accès Python idiomatique à toutes ces métriques, adapté à l’automatisation et aux rapports.Les alertes doivent être calibrées sur l’historique réel pour éviter la fatigue d’alerte.
Prometheus + Grafana représentent la solution standard pour la supervision continue de parcs de serveurs.
Outil |
Usage principal |
Granularité |
|---|---|---|
|
Diagnostic interactif immédiat |
Temps réel |
|
Vue agrégée CPU/mémoire/I/O |
Intervalle configurable |
|
Historique sur 24h+ |
10 min (configurable) |
|
Intégration Python/automatisation |
À la demande |
Prometheus |
Supervision continue multi-hôtes |
15s (configurable) |