IT-Admin.tech

Diagnóstico de fugas de memoria en servidores con perf, pmap y Valgrind

Diagramm einer Prozess-Heap- und mmap-Landkarte zur Speicherleck-Diagnose
Diagramm einer Heap-/mmap-Verteilung: Wie pmap, smaps und Heap-Profiler Speicherquellen sichtbar machen.

El diagnóstico de fugas de memoria comienza con la observación: un consumo de RAM que aumenta lentamente, registros recurrentes del OOM killer o un uso de swap inexplicable son señales claras de advertencia. En esta entrada muestro un enfoque práctico para la diagnóstico de fugas de memoria en servidores Linux. Los métodos combinan inspecciones sencillas (pmap, /proc/*/smaps), análisis por muestreo y Uprobe con perf, además de inspecciones profundas del heap con Valgrind. El objetivo es proporcionar a administradores y ingenieros de sistemas un camino de verificación fiable: requisitos, errores típicos, comandos concretos y estrategias de recuperación seguras.

¿Cuándo es realmente una fuga de memoria?

Antes de lanzar herramientas: no todo aumento del uso de RAM es una fuga. Fuentes típicas que pueden parecer fugas son caches crecientes, archivos mapeados en memoria, fragmentación o picos de carga transitorios. Una fuga de memoria, en sentido estricto, es una ocupación de memoria no deseada y no liberada en un proceso o en el kernel que crece de forma persistente a lo largo del tiempo.

Indicadores de comprobación:

  • Incremento monotónico a largo plazo del RSS (Resident Set Size) de un proceso durante horas/días.
  • Entradas de OOM en el registro del kernel relacionadas con el mismo proceso.
  • Valores altos de Private_Dirty o Private_Clean en /proc/<pid>/smaps que no se pueden explicar por estrategias legítimas de caché.

Observación inicial: métricas y monitorización

Antes de cualquier ejecución de depuración, el problema debe ser medible. Use herramientas de monitorización (p. ej., Prometheus, Zabbix, Datadog) o comandos sencillos para documentar tendencias. Registre medidas básicas: RSS, VSZ, uso de swap, page-ins/outs y número de procesos/hilos.

Shell
# Einmalig RSS und VSZ eines Prozesses prüfen (PID bekannt vorausgesetzt)
Shell
ps -o pid,user,vsz,rss,cmd -p 1234

Interpretation: VSZ ist der virtuelle Adressraum (inkl. mmaps), RSS ist tatsächlich im RAM resident. Ein steigendes RSS ist ein stärkerer Hinweis auf ein echtes Leak.

Shell
# Systemweite Tendenzen: freier Speicher, Swap, Pageins/outs
Shell
vmstat 5 12

Si los page-outs y el uso de swap crecen en paralelo al aumento del RSS, tiene repercusiones operativas inmediatas (degradación del rendimiento). Documente los valores como referencia antes de intervenir.

Enfoque en el proceso: pmap, smaps y PSS

pmap muestra el mapa de memoria de un proceso. Es útil para localizar mmaps grandes (p. ej., archivos grandes, shared memory). Complementariamente, /proc/<pid>/smaps ofrece métricas detalladas como Pss (Proportional Set Size), Private_Dirty y Shared_Clean. El PSS reparte el tamaño de las páginas compartidas de forma equitativa entre los procesos implicados y por ello es más útil para la contabilización.

Shell
# Detaillierte Zuordnung mit pmap
Shell
pmap -x 1234
Shell
# Smaps liefert pro-mapping Angaben (PSS, Private_Dirty, Referenced ...)
Shell
grep -A5 "^Rss: |^Pss: |^Private_Dirty:" /proc/1234/smaps | sed -n '1,200p'

Práctica: busque mmaps con Private_Dirty inusualmente grande o valores de PSS que crezcan de forma sostenida. Herramientas como smem consolidan el PSS por proceso y son útiles para generar informes.

Muestreo con perf: verificar hipótesis rápidamente

perf es una herramienta de rendimiento basada en el kernel. En diagnóstico ayuda a localizar hotpaths y cadenas de llamadas. Para fugas de memoria utilizamos dos patrones:

  • Muestreo de pilas de CPU para identificar rutas de código que causan asignaciones de memoria o crecimiento de caché.
  • Uprobes (sondas en espacio de usuario) en asignadores como malloc, realloc o mmap, para ver realmente las llamadas de asignación.
  • Importante: Para perf necesita permisos adecuados (root o ajustar /proc/sys/kernel/perf_event_paranoid) y, idealmente, símbolos de depuración para que los stacktraces sean útiles. Las uprobessólo funcionan si la biblioteca/ejecutable a instrumentar es estable y las rutas son conocidas.

    Shell
    # Registrar uprobe en malloc (glibc como ejemplo) - solo como root
    Shell
    perf probe -x /lib/x86_64-Linux-gnu/libc.so.6 malloc
    Shell
    # Muestreo de llamadas a malloc para PID 1234 durante 30 segundos
    Shell
    perf record -e probe:libc:malloc -p 1234 -g -- sleep 30
    Shell
    # Análisis
    Shell
    perf report -i perf.data --stdio

    Por qué funciona: las Uprobes permiten capturar llamadas a funciones en espacio de usuario; combinadas con callchains (stacktraces) verá qué código de la aplicación provoca con frecuencia asignaciones. Cuándo falla: binarios enlazados estáticamente, procesos con compilación JIT o binarios stripped proporcionan pocos símbolos interpretables.

    Trazas alternativas de perf

    También puede usar tracepoints para sys_enter_mmap o eventos del kernel como kmem:kmalloc (para fugas en el kernel):

    Shell
    # Observar syscalls mmap
    Shell
    perf record -e syscalls:sys_enter_mmap -p 1234 -g -- sleep 30

    Estos datos muestran si el proceso genera muchos mmaps —típico en cachés memory-mapped en crecimiento o por comportamiento de bibliotecas.

    Análisis profundo del heap con Valgrind

    Valgrind (Memcheck) es una herramienta de análisis dinámico que detecta fugas de memoria e lecturas/escrituras inválidas. No reemplaza el monitorizado en producción: Valgrind aumenta mucho tiempo de ejecución y uso de memoria y debe usarse en un entorno de pruebas o staging que reproduzca el comportamiento de producción.

    Opciones importantes:

    Shell
    # Análisis clásico de fugas
    Shell
    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --log-file=valgrind.log ./your_binary --args
    Shell
    # Massif para profiling del heap (instantáneas del heap en el tiempo)
    Shell
    valgrind --tool=massif --pages-as-heap=yes --massif-out-file=massif.out ./your_binary --args
    # Visualización en el escritorio del administrador
    ms_print massif.out

    Por qué Valgrind: Memcheck encuentra fugas reales de retención (alloc sin free) y muestra callstacks precisas si el binario y las bibliotecas se compilan con símbolos de depuración. Limitaciones: los lenguajes basados en GC (p. ej. runtimes gestionados) o servidores asíncronos complejos se comportan de forma distinta bajo Valgrind; navegadores o servicios grandes suelen ser imprácticos por su tamaño.

    Errores típicos y cómo evitarlos

    • Ejecuciones de Valgrind en producción: evítelas. Use un entorno de staging o capturas mediante core dumps y análisis offline.
    • Fragmentación vs. fuga: a veces la memoria virtual y residente crece por fragmentación. Compruébelo con /proc/<pid>/smaps y con herramientas como malloc-stats de su implementación de malloc.
    • Shared Memory y mmaps: grandes mmaps pueden aparecer como RSS, pero no son necesariamente fugas—compruebe los Backing-Files.
    • Hilos y TLS: cachés específicos por hilo (Thread-Local Storage) aumentan el uso de memoria persistente, especialmente con un gran número de hilos.

    Si Valgrind no es práctico: alternativas

    Para análisis de heap en entornos cercanos a producción sin Valgrind son apropiadas las siguientes opciones:

    • jemalloc con perfilado integrado (jeprof)
    • gperftools (tcmalloc) — profilador de heap
    • heaptrack (análisis en espacio de usuario basado en muestreo)
    • ASan/LSan (Address/LeakSanitizer) durante el proceso de build/test — mejor para CI que para producción

    Estas herramientas suelen ser menos invasivas que Valgrind y pueden ejecutarse en un entorno de staging o canary (ajustado).

    Proceso de comprobación pragmático — lista de verificación

    1. Observar: exportar datos de tendencia desde la monitorización, documentar el comportamiento de RSS/RAM/Swap.
    2. Enfoque por proceso: ps, top, pmap -x y /proc/<pid>/smaps analizar. Objetivo: determinar el ID de proceso y los mapeos afectados.
    3. Muestreo con perf: colocar uprobes en malloc/realloc/free o syscalls como mmap; evaluar cadenas de llamadas.
    4. Reproducir en staging: ejecutar Valgrind (Memcheck) y Massif, generar volcados de heap y analizarlos.
    5. Desarrollar la corrección: liberación de objetos, límites de caché, pooling o sustitución de allocador/bibliotecas.
    6. Despliegue con canary: definir plan de versiones y rollback, configurar alertas de monitorización.
    7. Postmortem: documentar causa, indicadores, la corrección y el alertado automático de monitorización.

    Estrategias seguras de operación y reversión

    Los diagnósticos pueden ser arriesgados: perf con uprobes es mínimamente invasivo, Valgrind disruptivo. Nunca ejecute pruebas invasivas directamente en producción. Si se despliega una corrección, tenga preparado un rollback rápido (repositorio de paquetes, script del gestor de servicios, revisión de Kubernetes). Defina niveles de alerta: p. ej. advertencia ante un aumento del 20 % en 1 h, alarma crítica ante entradas OOM.

    Lado del kernel: fugas en slab y en el kernel

    A veces las fugas son del kernel (Slab-Allocator, buffers de red). Compruebe:

    Shell
    # Slab-Statistiken
    Shell
    slabtop -s c
    Shell
    # Kernel dmesg auf OOM oder allocation failures prüfen
    Shell
    dmesg --ctime | tail -n 200

    Las fugas en el kernel son claramente más difíciles y suelen requerir perfiles del kernel o live-patching; como administrador documente los hallazgos y escale al equipo de desarrollo del kernel o al soporte del proveedor.

    Ejemplos prácticos — flujos de trabajo breves

    1) Prueba rápida para detectar mmaps crecientes (producción, poco invasivo):

    Shell
    # Beobachtung: welche Prozesse verursachen viele mmaps in kurzer Zeit
    perf record -e syscalls:sys_enter_mmap -a -g -- sleep 30
    perf script | head -n 200

    2) Uprobe en malloc en staging (asignar qué cadenas de llamadas provocan muchas asignaciones):

    Shell
    perf probe -x /lib/x86_64-Linux-gnu/libc.so.6 malloc
    perf record -e probe:libc:malloc -p 1234 -g -- sleep 60
    perf report -i perf.data --stdio

    3) Valgrind-Memcheck en entorno de pruebas:

    Shell
    valgrind --leak-check=full --show-leak-kinds=all --log-file=valgrind.log ./service_binary --config /etc/service/conf

    Diagnóstico de fugas de memoria: estrategias avanzadas para entornos complejos

    En entornos de hosting reales los servicios rara vez se ejecutan de forma aislada. Contenedores, unidades systemd, sidecars o bibliotecas compartidas cambian la situación y requieren prácticas de trabajo adaptadas.

    Contenedores y cgroups (consejos prácticos)

    En entornos con contenedores, las cgroups limitan el consumo de memoria. El proceso puede tener una fuga, pero será terminado antes por la influencia del límite de la cgroup. Compruebe las estadísticas de cgroup:

    Shell
    # cgroup v1 Beispiel (Pfad anpassen)
    cat /sys/fs/cgroup/memory/docker//memory.usage_in_bytes
    cat /sys/fs/cgroup/memory/docker//memory.max_usage_in_bytes
    # cgroup v2 Beispiel
    cat /sys/fs/cgroup//memory.current
    cat /sys/fs/cgroup//memory.max

    Consejos: En Kubernetes compruebe los eventos (kubectl describe pod) y los registros del contenedor; establezca límites conservadores para que una fuga siga siendo reproducible, pero sin destruir inmediatamente el entorno.

    Runtimes gestionadas: Java, Go, Node.js

    Valgrind suele ser inadecuado para runtimes gestionadas. Utilice los perfiles nativos del runtime:

    • Java: jmap/jcmd/jstack, volcados de heap analizados con Eclipse MAT (Memory Analyzer Tool).
    • Go: runtime/pprof, interfaz pprof, perfiles de heap a través de net/http/pprof.
    • Node.js: heapdump, –inspect, perfilado de heap de V8.
    Shell
    # Beispiel: Go-Profil aktivieren (Servercode muss pprof importieren)
    # Zugriff lokal: go tool pprof http://localhost:6060/debug/pprof/heap
    # Beispiel: Java Heap-Dump erzeugen
    jmap -dump:live,format=b,file=heap.hprof 
    # Analyse lokal mit Eclipse MAT
    mat heap.hprof

    Estas herramientas proporcionan instantáneas del heap, grafos de referencias y árboles dominadores que muestran qué objetos retienen memoria.

    Builds de depuración, Sanitizers y flags de compilación

    Para análisis reproducibles, los desarrolladores deberían proporcionar builds de depuración y sanitizers. Flags de compilador importantes:

    Shell
    # Beispiel GCC/Clang Debug/Sanitizer-Flags
    CFLAGS="-g -O0 -fsanitize=address,leak -fno-omit-frame-pointer"
    # Für ASan/LSan in Tests verwenden; nicht für Produktion
    

    Por qué: los símbolos de depuración hacen que los stacktraces sean informativos. ASan/LSan detectan rápidamente use-after-free y fugas en ejecuciones de prueba; las limitaciones son la sobrecarga de rendimiento y otros cambios en el comportamiento en tiempo de ejecución.

    Automatización, CI y comprobaciones anuales

    Las fugas de memoria a menudo son latentes y se manifiestan bajo carga a largo plazo. Planifique comprobaciones automatizadas:

    • Regresión nocturna en CI con sanitizers y cargas de trabajo reproducibles.
    • Ejecuciones periódicas de Massif en staging con carga conocida, informes basados en artefactos en el repositorio de artefactos.
    • Playbooks de alertas que, ante un aumento del RSS, generan automáticamente un snapshot (pmap/smaps) y lanzan un muestreo corto con perf.
    Shell
    # Beispiel-Skript: bei Alarm automatisch pmap+smaps anlegen
    #!/bin/bash
    PID=$1
    OUTDIR=/var/tmp/heap-dumps/$(date +%F_%T)
    mkdir -p "$OUTDIR"
    pmap -x "$PID" > "$OUTDIR/pmap.txt"
    cp /proc/$PID/smaps "$OUTDIR/smaps"
    # optional: perf kurz laufen lassen
    perf record -e probe:libc:malloc -p $PID -g -- sleep 10
    mv perf.data "$OUTDIR/"
    

    Los artefactos automatizados ayudan a los desarrolladores a reconstruir más rápidamente el escenario reproducible.

    Métricas medibles y ejemplos de Prometheus

    Defina métricas claras en Prometheus para reglas de alerta:

    Shell
    # PromQL-Beispiel: plötzlicher RSS-Anstieg über 1 Stunde
    increase(process_resident_memory_bytes{job="myservice"}[1h]) > 200000000
    # oder: relative Änderung
    (rate(process_resident_memory_bytes{job="myservice"}[15m]) > 1048576)
    

    Complementarlo con instrumentación de la aplicación: métricas del tamaño del heap de la runtime, número de caches abiertos, tasas de aciertos/fallos de cache — así es más fácil distinguir fugas de un crecimiento legítimo de caches.

    Plan de rollback y runbook de emergencia

    Un rollback limpio es obligatorio. Pasos de ejemplo para servicios systemd:

    Shell
    # Ejemplo de rollback (unidad systemd)
    # 1. Detener nuevos Pods / servicios
    systemctl stop myservice
    # 2. Desplegar versión antigua (desde repo o paquete)
    apt-get install --allow-downgrades myservice=1.2.3-1
    # 3. Iniciar y monitorizar
    systemctl start myservice
    journalctl -u myservice -f --lines=200
    

    En Kubernetes utilice los mecanismos de rollback (kubectl rollout undo) y asegúrese de que las alertas se desactiven o se sitúen en una ventana de mantenimiento del sistema de monitorización durante la reversión.

    Cuándo escalar — soporte del proveedor o del kernel

    Escale cuando:

    • Observe indicadores claros del kernel (crecimiento de slab, fallos de asignación).
    • Los backtraces de procesos apuntan a código de librerías que usted no mantiene (p. ej. glibc, librería de terceros).
    • Detecta fugas de memoria reproducibles en servicios gestionados y no existe una solución alternativa razonable.

    Prepare para los casos de soporte estos artefactos: extracto de dmesg, snapshot de slabtop, pmap/smaps, capturas de perf, logs de Valgrind o heap-dumps y pasos exactos de reproducción.

    Resumen y conclusión

    El diagnóstico de fugas de memoria puede realizarse de forma sistemática y por pasos: detectar mediante monitorización, usar pmap/smaps para acotar, emplear perf (incl. Uprobes) para pruebas rápidas de hipótesis y Valgrind para análisis profundos del heap en un entorno adecuado. Medios adicionales son los profileadores nativos del runtime (jemalloc, heaptrack), sanitizadores en CI y la generación automatizada de artefactos ante alertas. Requisitos importantes son permisos administrativos para perf, símbolos de depuración para una mejor interpretación de las cadenas de llamadas y entornos de staging aislados para herramientas de análisis invasivas. Fuentes comunes de error son las caches, mmaps o la fragmentación; éstas deben comprobarse siempre como posibles causas antes de aplicar medidas de depuración invasivas. En conclusión: planifique cada paso con una estrategia de rollback, documente artefactos y alertas y coordine estrechamente con los equipos de desarrollo para que las correcciones lleguen a producción de forma reproducible y segura.

    Indicaciones adicionales: Considere el uso de profileadores específicos del allocator (jemalloc, tcmalloc) en problemas recurrentes; a menudo son más adecuados para producción que Valgrind. Mantenga builds de depuración y scripts de reproducción disponibles para que los desarrolladores puedan reproducir con rapidez lo observado en el análisis.