Monitor de I/O Wait Vcenter WA

Setup completo para capturar automáticamente qué está causando %wa alto en un servidor Linux, con evidencia forense de cada pico.

Requisitos previos

bash

apt install -y sysstat iotop iptables-persistent

sysstat trae iostat, vmstat. iotop para I/O por proceso.


Paso 1: Script del monitor principal

Crea /usr/local/bin/wa_monitor.sh:

bash

#!/bin/bash
# Monitor de %iowait — captura evidencia cuando supera el umbral
# Uso: wa_monitor.sh [umbral_wa] [intervalo_seg]

THRESHOLD=${1:-15}
INTERVAL=${2:-5}
LOG_DIR="/var/log/wa_monitor"
COOLDOWN=60

mkdir -p "$LOG_DIR"
LAST_CAPTURE=0

echo "[$(date '+%F %T')] Monitor iniciado. Umbral=%wa>${THRESHOLD}, intervalo=${INTERVAL}s"

while true; do
    WA=$(top -bn2 -d1 | grep -E '^%Cpu' | tail -1 | awk -F'wa' '{print $1}' | awk '{print $NF}' | tr ',' '.')
    WA_INT=$(printf "%.0f" "$WA" 2>/dev/null || echo 0)
    NOW=$(date +%s)

    if [ "$WA_INT" -ge "$THRESHOLD" ]; then
        if [ $((NOW - LAST_CAPTURE)) -lt $COOLDOWN ]; then
            sleep "$INTERVAL"
            continue
        fi

        TS=$(date '+%Y%m%d_%H%M%S')
        OUT="$LOG_DIR/pico_${TS}_wa${WA_INT}.log"
        LAST_CAPTURE=$NOW

        {
            echo "===== PICO DE IOWAIT ====="
            echo "Timestamp: $(date '+%F %T')"
            echo "%wa detectado: ${WA}%"
            echo "Uptime: $(uptime)"
            echo
            echo "===== TOP -bn1 (procesos activos) ====="
            top -bn1 | head -30
            echo
            echo "===== PROCESOS EN ESTADO D (esperando I/O) ====="
            ps -eo pid,stat,user,command | awk '$2 ~ /D/'
            echo
            echo "===== IOSTAT -xz 2 3 (uso de disco) ====="
            iostat -xz 2 3
            echo
            echo "===== IOTOP -boPn3 (top de I/O) ====="
            iotop -boPqqqn3 2>/dev/null | head -40
            echo
            echo "===== VMSTAT 2 5 ====="
            vmstat 2 5
            echo
            echo "===== WAZUH / NESSUS STATUS ====="
            ps -ef | grep -E 'wazuh-syscheck|nessus' | grep -v grep
            echo
            echo "===== TOP 10 PROCESOS POR MEMORIA ====="
            ps -eo pid,user,%cpu,%mem,rss,command --sort=-%mem | head -11
        } > "$OUT" 2>&1

        # Dispara el snapshot de contexto en paralelo (opcional)
        [ -x /usr/local/bin/ws_snapshot.sh ] && /usr/local/bin/ws_snapshot.sh "ws_snapshot" > /dev/null 2>&1 &

        echo "[$(date '+%F %T')] ⚠ Pico capturado (wa=${WA}%) → $OUT"
    fi

    sleep "$INTERVAL"
done

bash

chmod +x /usr/local/bin/wa_monitor.sh

Qué captura: en cada pico, un archivo pico_YYYYMMDD_HHMMSS_waN.log con top, procesos bloqueados en I/O, iostat, iotop, vmstat, estado de agentes de seguridad y top procesos por RAM.


Paso 2: Script de snapshot complementario (contexto de aplicación)

Crea /usr/local/bin/ws_snapshot.sh. Este es un ejemplo para VS Code Remote — adaptalo al software que estés investigando (procesos Java, contenedores Docker, workers de una app, etc.):

bash

#!/bin/bash
# Snapshot de contexto de aplicación al momento del pico
# Uso: ws_snapshot.sh [prefijo_archivo]

PREFIX="${1:-workspaces}"
OUT="/var/log/wa_monitor/${PREFIX}_$(date +%Y%m%d_%H%M%S).log"

{
    echo "===== Snapshot $(date '+%F %T') ====="
    echo

    echo "--- JDT Language Servers activos (adaptar patrón según tu caso) ---"
    ps -eo pid,rss,user,command | grep 'jdt_ws' | grep -v grep | while read pid rss user rest; do
        ws=$(echo "$rest" | grep -oP 'workspaceStorage/\K[a-f0-9-]+' | head -1)
        gb=$(awk "BEGIN {printf \"%.2f\", $rss/1024/1024}")
        printf "PID %-7s | %5s GB | user=%s | ws=%s\n" "$pid" "$gb" "$user" "$ws"
    done
    echo

    echo "--- Total RAM por procesos de interés ---"
    ps -eo rss,command | grep 'jdt_ws' | grep -v grep | \
        awk '{sum+=$1} END {printf "  %.2f GB\n", sum/1024/1024}'
    echo

    echo "--- Estado de RAM y swap ---"
    free -h
    echo

    echo "--- Top 15 procesos por RAM residente ---"
    ps -eo pid,user,%mem,rss,command --sort=-rss | head -16
} > "$OUT" 2>&1

echo "$OUT"

bash

chmod +x /usr/local/bin/ws_snapshot.sh

Personalización: cambiá el patrón grep 'jdt_ws' por lo que quieras trackear. Ejemplos:

  • Contenedores Docker: grep 'docker' | grep -v 'grep' + agregar docker stats --no-stream
  • Java apps: grep 'java' | grep -v grep
  • Procesos de un usuario: ps -u <usuario>
  • PostgreSQL workers: grep 'postgres'

Paso 3: Servicio systemd (persistencia entre reboots)

Crea /etc/systemd/system/wa-monitor.service:

ini

[Unit]
Description=I/O Wait Monitor
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/bin/wa_monitor.sh 15 5
Restart=on-failure
StandardOutput=append:/var/log/wa_monitor/monitor.log
StandardError=append:/var/log/wa_monitor/monitor.log

[Install]
WantedBy=multi-user.target

Los dos números en ExecStart son umbral %wa y intervalo de muestreo en segundos. Ajustá según sensibilidad deseada (típico: 15 5 para detección temprana, 25 5 si hay ruido de fondo de scans de seguridad).

bash

mkdir -p /var/log/wa_monitor
systemctl daemon-reload
systemctl enable --now wa-monitor
systemctl status wa-monitor

Paso 4: Cron de limpieza automática

bash

crontab -e

Agregar:

0 3 * * * find /var/log/wa_monitor -name 'pico_*.log' -mtime +7 -delete
5 3 * * * find /var/log/wa_monitor -name 'ws_snapshot_*.log' -mtime +7 -delete

Corre a las 3 AM, borra archivos de más de 7 días. Ajustá +7 según cuánto historial quieras conservar.


Comandos útiles para analizar

bash

# Listar picos capturados, más recientes primero
ls -lth /var/log/wa_monitor/pico_*.log | head

# Ver el pico más reciente
sudo cat "$(ls -t /var/log/wa_monitor/pico_*.log | head -1)"

# Ver un pico específico y su snapshot correspondiente (por timestamp)
TS=20260921_1019
sudo cat /var/log/wa_monitor/pico_${TS}*.log
sudo cat /var/log/wa_monitor/ws_snapshot_${TS}*.log

# Ordenar picos por valor de wa (los peores primero)
ls /var/log/wa_monitor/pico_*.log | sort -t'wa' -k2 -n -r | head

# Ver heartbeat del monitor
tail -f /var/log/wa_monitor/monitor.log

# Espacio consumido
du -sh /var/log/wa_monitor/

Glosario de programas que consumen recursos

Sospechosos frecuentes cuando ves %wa alto o carga inexplicable en un servidor Linux:

Kernel / sistema

  • kswapd0 — daemon de swap del kernel. Si aparece al 100% de CPU, es swap thrashing: RAM llena, kernel paginando entre disco y memoria. La causa raíz es siempre falta de RAM.
  • kworker/* — threads de trabajo del kernel. Actividad alta suele indicar drivers de disco/red trabajando duro.
  • jbd2/dm-0-8 — journal de ext4. Si genera mucho I/O, hay muchas escrituras en el filesystem.

Agentes de seguridad (muy comunes en empresas)

  • pmd — Palo Alto Cortex XDR (endpoint protection). Usa fanotify para monitorear cada archivo tocado. Puede consumir 100%+ CPU cuando otro proceso está haciendo I/O intensivo.
  • wazuh-syscheckd — Wazuh File Integrity Monitoring. Recorre el filesystem periódicamente calculando hashes.
  • wazuh-modulesd:syscollector — inventario de paquetes, procesos, puertos, hardware. Corre por defecto cada 1h.
  • wazuh-agentd, wazuh-logcollector, wazuh-modulesd — otros componentes de Wazuh.
  • nessus-service, nessus-agent-module, nessusd — agente de Tenable Nessus para escaneo de vulnerabilidades.
  • sandboxd (cortexus) — análisis de archivos con YARA de Cortex XDR.
  • fail2ban-server — puede generar I/O leyendo logs cuando hay muchos intentos.

Desarrollo (VS Code Remote / Java)

  • java ... jdt_ws — JDT Language Server de Red Hat (extensión Java para VS Code). Indexa proyectos. Configurado con -Xmx5G por defecto — un solo workspace puede pedir 5 GB.
  • node ... vscode-server — servidor de VS Code Remote. Usa bootstrap-fork para extensionHost, fileWatcher, ptyHost.
  • java ... oracle.dbtools — extensión Oracle SQL Developer, arranca un JDK propio por conexión.
  • node .../.vscode-server/ con --type=extensionHost — cada extensión que corre en su propio process host.

Contenedores / servicios

  • dockerd, containerd — daemons de Docker. docker ps, docker events generan lecturas.
  • snapd — daemon de snap packages. Refresca automáticamente snaps de vez en cuando (I/O importante).

Sistema operativo / escritorio

  • systemd-journald — recolector de logs de systemd. Si está en estado D, hay presión sobre /var/log/journal/.
  • tracker-miner-fs-3 — indexador de archivos de GNOME. En servidores no tiene sentido, se puede desactivar.
  • gnome-shell, gsd-* — shell y daemons de GNOME. No deberían correr en un server sin GUI.
  • vmtoolsd — VMware Tools. Se comunica con el hypervisor, actividad ligera pero constante.

Backups / replicación

  • rsync, borg, restic — herramientas de backup, cuando corren generan I/O masivo.
  • vgauth, procesos de snapshot — cuando el hypervisor toma un snapshot de la VM, el I/O del guest sufre.

Interpretación rápida del pico_*.log

Cuando abrás un pico, mirá en este orden:

  1. %Cpu(s) line en top: ¿qué proporción es us, sy, wa?
    • wa alto + sy alto = kernel bloqueado en I/O
    • wa alto + us bajo = procesos esperando disco, no CPU
  2. kswapd0 al 100% en top → falta RAM, revisá free -h en el snapshot
  3. Procesos en estado D: son los que están esperando I/O. El culpable directo.
  4. iostat: mirá %util del dispositivo sda (o el que corresponda) y w_await / r_await. Latencia > 20ms = disco saturado.
  5. iotop: quién está leyendo/escribiendo. Ojo con los ?dif que a veces significan waiting for I/O.
  6. Swap usage en free -h: si Inter usado está cerca del 100% → swap thrashing.

Combinación clásica del thrashing por RAM: kswapd0 al 100% + swap casi lleno + %wa > 50% + iotop mostrando lecturas de swap.

Combinación clásica del scan de seguridad: pmd al 100% + procesos wazuh/nessus activos + %wa moderado (15-25%) + duración corta (< 2 min).

Combinación clásica del backup del hypervisor: %wa alto, pero ningún proceso interno explica el I/O — hay que mirar métricas del datastore en vCenter.