---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Monitoring système

```{code-cell} python
:tags: [hide-input]

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns
import pandas as pd
import psutil
import os
import time

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
```

## 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

```bash
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é.

```bash
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

```bash
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

```bash
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

```bash
# 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

```bash
# 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
```

```{admonition} Archives sysstat
:class: tip
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

```bash
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

```{code-cell} python
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")
```

### /proc/diskstats et /proc/net/dev

```bash
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

```{code-cell} python
:tags: [hide-input]

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

```{code-cell} python
:tags: [hide-input]

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

```{code-cell} python
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))
```

### Trafic réseau par interface

```{code-cell} python
:tags: [hide-input]

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

```{code-cell} python
:tags: [hide-input]

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 :

```bash
#!/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 :

```bash
# /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
```

```bash
# /etc/systemd/system/alerter-failure@.service
[Unit]
Description=Notification d'échec pour %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/notify-failure.sh %i
```

```{admonition} Seuils et faux positifs
:class: warning
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 :

```bash
# 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

```promql
# % 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
```

```{admonition} Rétention et haute disponibilité
:class: note
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é.
- `psutil` offre 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é |
|-------|----------------|-------------|
| `top` / `htop` | Diagnostic interactif immédiat | Temps réel |
| `vmstat` / `iostat` | Vue agrégée CPU/mémoire/I/O | Intervalle configurable |
| `sar` | Historique sur 24h+ | 10 min (configurable) |
| `psutil` | Intégration Python/automatisation | À la demande |
| Prometheus | Supervision continue multi-hôtes | 15s (configurable) |
