Cuando un proceso se cuelga, el disco aparece lleno sin razón aparente o el sistema responde lento, tienes dos opciones: reiniciar y rezar, o entender qué está pasando realmente. Las herramientas que vamos a ver te dan acceso directo a lo que el kernel ve: qué syscalls ejecuta un proceso, qué file descriptors tiene abiertos, qué está ocupando el espacio, qué evento de hardware disparó el problema.
strace es un interceptor de syscalls. Se engancha al proceso objetivo mediante ptrace(2) —el mismo mecanismo que usa gdb— y registra cada llamada al kernel con sus argumentos y valor de retorno. Cuando un proceso “no hace nada”, strace te dice exactamente en qué syscall está bloqueado: si está en read() esperando un socket, en futex() esperando un mutex, o en epoll_wait() esperando eventos. Sin este nivel de detalle, estás adivinando.
lsof (“list open files”) consulta /proc/<pid>/fd/, /proc/<pid>/maps y otras entradas del sistema de archivos virtual del kernel para construir un mapa de todos los recursos que un proceso tiene abiertos: archivos regulares, sockets, pipes, device nodes. El caso más traicionero es el del archivo borrado pero todavía abierto: rm elimina la entrada del directorio (el dentry), pero el kernel mantiene el inodo vivo mientras algún proceso tenga un file descriptor apuntando a él. df reporta el espacio como ocupado porque el inodo sigue existiendo; du no lo ve porque recorre el árbol de directorios y el archivo ya no está en ningún sitio. El resultado es un disco que aparece lleno y no encuentras nada con du. lsof | grep deleted te saca de ese agujero.
Úsalas en incidencias activas, no solo en postmortem. strace -p sobre un proceso que ya está corriendo es especialmente valioso cuando no puedes reproducir el problema desde cero. Ten en cuenta que strace introduce overhead real —decenas de microsegundos por syscall— por lo que en producción úsalo con filtros y el tiempo mínimo necesario. En un sistema bajo carga alta de I/O, puede agravar momentáneamente el problema; saberlo es parte del diagnóstico.
Si te equivocas con estas herramientas el riesgo directo es bajo —son de observación, no de modificación— pero hay trampas: strace sin filtros sobre un proceso muy activo puede generar megabytes de output por segundo y llenar /tmp o el terminal buffer. lsof sin argumentos sobre un sistema con miles de procesos es lento y puede generar contención en /proc. Úsalos con criterio.
# ── ESCENARIO: servidor web nginx responde lento, disco "lleno" ──────────────
# df reporta /var al 100%, pero du -sh /var no cuadra con el total
# 1. Confirmar el problema con df
df -h /var
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 20G 20G 0 100% /var
# 2. Drill-down rápido para localizar el subdirectorio culpable
du -sh /var/* 2>/dev/null | sort -rh | head -10
# Si los números no cuadran con df, hay archivos deleted-but-open
# 3. ncdu para exploración interactiva (apt install ncdu si no está)
# ncdu /var
# Útil cuando du muestra ~15G pero df dice 20G usados
# 4. Buscar archivos borrados pero abiertos que explican la diferencia
lsof | grep '(deleted)'
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# nginx 1823 www-data 3w REG 8,1 5368709120 131073 /var/log/nginx/access.log (deleted)
# Ese archivo de 5GB ya fue borrado con rm pero nginx lo tiene abierto en escritura
# 5. Ver todos los file descriptors del proceso nginx problemático
lsof -p 1823
# Muestra sockets, archivos de config, libs — visión completa del proceso
# 6. Recuperar espacio SIN reiniciar nginx: truncar el fd directamente
# El fd 3 de ese proceso apunta al archivo deleted
ls -la /proc/1823/fd/3
# lrwx------ 1 www-data www-data 64 /proc/1823/fd/3 -> /var/log/nginx/access.log (deleted)
# Truncar a cero a través del descriptor — el kernel libera los bloques
> /proc/1823/fd/3
# Verificar que df refleja el espacio libre inmediatamente
df -h /var
# 7. Entender por qué nginx está lento — ver sus syscalls en tiempo real
# -p adjunta al proceso corriendo; -e filtra solo I/O relevante; -T muestra tiempo por syscall
strace -p 1823 -e trace=read,write,open,openat,close,stat -T 2>&1 | head -50
# Si ves writev() tardando >100ms, el problema es I/O de disco, no CPU
# 8. Ver syscalls de un comando nuevo con filtro más amplio
# Útil para entender qué archivos de configuración lee un binario al arrancar
strace -e trace=openat,read -o /tmp/nginx-trace.txt nginx -t
# -o escribe a archivo para no perder output; nginx -t hace config check y sale
grep 'ENOENT\|EACCES' /tmp/nginx-trace.txt | head -20
# ENOENT = archivo que busca y no existe; EACCES = problema de permisos
# 9. Diagnóstico de CPU: qué procesos consumen más
ps aux --sort=-%cpu | head -10
# Si el culpable no es obvio, ver el estado del proceso
cat /proc/1823/status | grep -E 'VmRSS|VmSwap|Threads|voluntary'
# VmRSS: RAM física usada; VmSwap: cuánto está en swap; voluntary_ctxt_switches
# 10. Memoria del sistema
free -h
# Si used/total está al límite, comprobar si el OOM killer actuó
# journalctl -k busca en el ring buffer del kernel (equivalente a dmesg pero con timestamps)
journalctl -k --since "1 hour ago" | grep -iE 'oom|killed|out of memory|hardware error|error'
# Mensaje típico de OOM: "Out of memory: Killed process 2341 (java) score 892"
# 11. Errores de hardware y filesystem — siempre revisar en una incidencia de disco
journalctl -k | grep -iE 'i/o error|ext4 error|ata[0-9]|blk_update_request'
# "ata1.00: status: { DRDY ERR }" o "EXT4-fs error" indican disco muriendo
# En ese caso: smartctl -a /dev/sda (apt install smartmontools)
# 12. Si el disco lleno persiste tras el truncado, buscar en el directorio completo
# lsof +D es lento porque recorre /proc por cada proceso — usarlo en último recurso
lsof +D /var/log
# Muestra qué procesos tienen abierto cualquier archivo bajo /var/log
# Imprescindible antes de intentar umount: si algo aparece aquí, el umount falla
# 13. Memoria detallada: smem muestra uso real por proceso (descuenta shared memory)
# apt install smem
smem -r -k | head -15
# USS (Unique Set Size): RAM que SOLO usa ese proceso — la métrica más honesta
# PSS (Proportional): USS + su fracción proporcional de shared libs
Lo que está pasando debajo
El > /proc/1823/fd/3 del paso 6 es la técnica más importante del ejemplo. Cuando haces rm de un archivo, el kernel decrementa el contador de referencias del inodo. Si ese contador llega a cero, libera los bloques. Pero si un proceso tiene el archivo abierto, el contador no llega a cero —el fd del proceso es otra referencia. El truco es que /proc/<pid>/fd/<n> es un enlace simbólico que resuelve al inodo directamente, saltándose el sistema de nombres. Truncar ese fd a cero (> /proc/1823/fd/3) llama a truncate(2) sobre el inodo, el kernel libera los bloques de datos inmediatamente, y el proceso puede seguir escribiendo en el mismo fd —simplemente empieza desde el offset cero. Sin reiniciar nginx, sin perder conexiones.
El filtro -e trace=openat,read en el paso 8 merece explicación. Sin filtro, strace registra absolutamente todas las syscalls, incluyendo mmap, mprotect, brk, clock_gettime… que en un proceso activo son decenas por milisegundo. El output es ilegible y el overhead es máximo. Con el filtro, capturas exactamente lo que te interesa: en un problema de archivos de configuración, openat te dice qué busca el proceso y si lo encuentra (= 3) o no (= -1 ENOENT). El grep posterior sobre ENOENT y EACCES convierte cien líneas de trace en dos o tres que importan.
La diferencia entre dmesg y journalctl -k en el paso 10 es relevante: dmesg lee directamente el ring buffer del kernel, que tiene tamaño fijo (típicamente 512KB en Bookworm) y pierde mensajes antiguos. journalctl -k lee los mensajes del kernel que journald ya persistió en /var/log/journal/, por lo que puedes ir semanas atrás y usar --since. En una incidencia donde el servidor reinició hace dos días por un OOM, dmesg no te dice nada; journalctl -k --since "3 days ago" sí.
La distinción entre USS y PSS que muestra smem en el paso 13 resuelve una confusión habitual: ps aux muestra RSS (Resident Set Size), que incluye páginas de memoria compartidas —librerías del sistema, por ejemplo— contadas tantas veces como procesos las usan. Si tienes 50 workers de nginx, su RSS suma 50 veces libc. smem con USS te da el consumo que realmente desaparecería si mataras ese proceso. Es la métrica correcta para decidir si tienes un memory leak real o simplemente muchos procesos compartiendo las mismas librerías.
N° 101