IT-Admin.tech

Diagnosi di perdite di memoria su server con perf, pmap e 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.

La diagnosi delle perdite di memoria inizia dall’osservazione: un consumo di RAM che aumenta lentamente, log ricorrenti dell’OOM-Killer o un utilizzo di swap inspiegabile sono segnali d’allarme chiari. In questo contributo mostro un approccio operativo alla diagnosi delle perdite di memoria su Linux-server. I metodi combinano ispezioni semplici (pmap, /proc/*/smaps), analisi di sampling e Uprobe con perf e ispezioni profonde dell’heap con Valgrind. L’obiettivo è fornire ad amministratori e ingegneri di sistema un percorso di verifica affidabile: prerequisiti, trappole tipiche, comandi concreti e strategie di fallback sicure.

Quando è realmente una perdita di memoria?

Prima di avviare gli strumenti: non ogni immagine di RAM in crescita è una leak. Fonti tipiche che possono sembrare leak sono cache in crescita, file memory-mapped, frammentazione o picchi di carico transitori. Una perdita di memoria, in senso stretto, è un consumo di memoria non rilasciato da un processo o dal kernel che cresce in modo persistente nel tempo.

Indicatori da verificare:

  • Aumento monotono a lungo termine della RSS (Resident Set Size) di un processo su ore/giorni.
  • voci OOM nei log del kernel correlate allo stesso processo.
  • Valori elevati di Private_Dirty o Private_Clean in /proc/<pid>/smaps che non sono spiegabili con strategie di caching legittime.

Prima osservazione: metriche e monitoraggio

Prima di ogni esecuzione di debug il problema deve essere misurabile. Usate tool di monitoring (ad es. Prometheus, Zabbix, Datadog) o comandi semplici per documentare le tendenze. Rilevate grandezze di base: RSS, VSZ, utilizzo di swap, page-ins/outs e numero di processi/thread.

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

Interpretazione: VSZ è lo spazio di indirizzamento virtuale (incl. mmaps), RSS è ciò che è effettivamente residente in RAM. Un aumento della RSS è un segnale più forte di una leak reale.

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

Se page-outs e utilizzo di swap crescono parallelamente all’aumento della RSS, c’è un impatto operativo immediato (degrado delle prestazioni). Documentate i valori come riferimento prima di intervenire.

Focus sul processo: pmap, smaps e PSS

pmap mostra la mappa di memoria di un processo. È utile per trovare grandi mmaps (es. file di grandi dimensioni, shared memory). In aggiunta /proc/<pid>/smaps fornisce metriche dettagliate come Pss (Proportional Set Size), Private_Dirty e Shared_Clean. La PSS distribuisce equamente le dimensioni delle pagine condivise tra i processi coinvolti ed è quindi più utile per l’accounting.

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'

Pratica: cercate mmaps con Private_Dirty insolitamente grande o valori PSS in crescita costante. Strumenti come smem consolidano la PSS per processo e sono utili per i report.

Sampling con perf: verificare rapidamente le ipotesi

perf è uno strumento di performance a livello kernel. Nella diagnosi aiuta a trovare hotpath e catene di chiamata. Per le perdite di memoria utilizziamo due pattern:

  • Campionamento degli stack CPU per identificare i percorsi di codice che causano allocazioni di memoria o crescita della cache.
  • Uprobes (user-space probes) sugli allocatori come malloc, realloc o mmap per osservare le chiamate di allocazione effettive.

Importante: per perf servono i permessi adeguati (root o la modifica di /proc/sys/kernel/perf_event_paranoid) e idealmente i simboli di debug, affinché gli stacktrace siano utili. Le uprobes funzionano solo se la libreria/eseguibile da instrumentare è stabile e i percorsi sono noti.

Shell
# Uprobe auf malloc registrieren (glibc als Beispiel) - nur als root
Shell
perf probe -x /lib/x86_64-Linux-gnu/libc.so.6 malloc
Shell
# Sampling von malloc-Aufrufen für PID 1234 über 30 Sekunden
Shell
perf record -e probe:libc:malloc -p 1234 -g -- sleep 30
Shell
# Auswertung
Shell
perf report -i perf.data --stdio

Perché funziona: le uprobes consentono di catturare le chiamate di funzione in userland; combinate con le callchain (stacktrace) permettono di identificare quale codice applicativo innesca frequentemente allocazioni. Quando fallisce: i binari linkati staticamente, i processi JIT o i binari stripped forniscono pochi simboli utili.

Tracce perf alternative

È possibile usare anche tracepoint come sys_enter_mmap o eventi kernel come kmem:kmalloc (per leak nel kernel):

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

Questi dati indicano se il processo genera molte mmap — tipico in presenza di cache memory-mapped in crescita o di comportamenti lato libreria.

Analisi approfondita dell’heap con Valgrind

Valgrind (Memcheck) è uno strumento di analisi dinamica che rileva leak di memoria e letture/scritture invalide. Non sostituisce il monitoraggio in produzione: Valgrind aumenta significativamente tempi di esecuzione e consumo di memoria e va utilizzato in un ambiente di test o staging che riproduca il comportamento di produzione.

Opzioni importanti:

Shell
# Klassische Leak-Analyse
Shell
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --log-file=valgrind.log ./your_binary --args
Shell
# Massif für Heap-Profiling (Heap-Snapshot über Zeit)
Shell
valgrind --tool=massif --pages-as-heap=yes --massif-out-file=massif.out ./your_binary --args
# Visualisierung auf dem Admin-Desktop
ms_print massif.out

Perché Valgrind: Memcheck individua veri leak per mancata liberazione (alloc senza free) e mostra callstack precisi se il binario e le librerie sono compilati con simboli di debug. Limitazioni: linguaggi basati su GC (es. runtime gestiti) o server asincroni complessi si comportano diversamente sotto Valgrind; browser o servizi di grande dimensione risultano spesso poco pratici da analizzare.

Trappole tipiche e come evitarle

  • Esecuzioni Valgrind in produzione: evitarle. Utilizzate un ambiente di staging o la cattura tramite core dumps e analisi offline.
  • Fragmentazione vs. leak: a volte la memoria virtuale e residente cresce a causa di frammentazione. Controllate con /proc/<pid>/smaps e strumenti come malloc-stats della vostra implementazione di malloc.
  • Shared Memory e mmaps: grandi mmaps possono apparire come RSS, ma non sono necessariamente perdite di memoria — controllare i file di backing.
  • Thread e TLS: cache specifiche per thread (Thread-Local Storage) aumentano il consumo di memoria persistente, soprattutto con un numero elevato di thread.
  • Se Valgrind non è praticabile: alternative

    Per analisi dell’heap in ambiente vicino alla produzione senza Valgrind si possono usare:

    • jemalloc con profiling integrato (jeprof)
    • gperftools (tcmalloc) profiler dell’heap
    • heaptrack (analisi userland basata su sampling)
    • ASan/LSan (Address/LeakSanitizer) durante il processo di build/test — più adatti per CI che per la produzione

    Questi strumenti sono generalmente meno invasivi di Valgrind e possono essere eseguiti in ambienti di staging o canary (adattati).

    Procedura di verifica pragmatica — Checklist

    1. Osservare: esportare i dati di trend dal monitoring, documentare il comportamento di RSS/RAM/Swap.
    2. Focus per processo: analizzare ps, top, pmap -x e /proc/<pid>/smaps. Obiettivo: determinare l’ID del processo e le mappature interessate.
    3. Sampling con perf: impostare uprobes su malloc/realloc/free o syscall come mmap; analizzare le catene di chiamata.
    4. Riprodurre in staging: eseguire Valgrind (Memcheck) e Massif, generare e analizzare dump dell’heap.
    5. Sviluppare la correzione: rilascio degli oggetti, limiti per le cache, pooling o sostituzione di allocator/librerie.
    6. Deploy con canary: definire piano di versioning e rollback, nonché gli alert di monitoring.
    7. Postmortem: documentare causa, indicatori, fix e alerting automatico del monitoring.

    Strategie operative e di rollback sicure

    Le diagnosi possono essere rischiose: perf con uprobes è minimamente invasivo, Valgrind è dirompente. Non eseguire mai test invasivi direttamente in Production. Quando si distribuisce un fix, predisporre un rollback rapido (repository dei pacchetti, script del service manager, revisioni Kubernetes). Definire livelli di alert: es. avviso per un aumento del 20% in 1 ora, allarme critico in caso di voci OOM.

    Lato kernel: slab e leak a livello kernel

    A volte le perdite sono a livello di kernel (slab allocator, buffer di rete). Controllare:

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

    I leak a livello kernel sono molto più difficili e richiedono generalmente profili del kernel o live-patching; come amministratore documentare i risultati e scalare verso gli sviluppatori del kernel o il supporto del vendor.

    Esempi pratici — brevi flussi di lavoro

    1) Test rapido per aumenti di mmaps (produzione, 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 su malloc in staging (per assegnare quali catene di chiamata generano molte allocazioni):

    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 in ambiente di test:

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

    Diagnosi delle perdite di memoria: strategie avanzate per ambienti complessi

    In ambienti di hosting reali i servizi raramente vengono eseguiti isolati. Container, unità systemd, sidecar o librerie condivise cambiano la situazione e richiedono modalità di lavoro adeguate.

    Container e cgroups (indicazioni pratiche)

    Negli ambienti con container le cgroup limitano il consumo di memoria. Un processo può avere una perdita di memoria, ma viene terminato prima a causa dei limiti impostati dalla cgroup. Verifichi le statistiche della 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

    Suggerimenti: in Kubernetes verifichi gli event (kubectl describe pod) e i log dei container; imposti limiti cauti in modo che una fuga di memoria resti ancora riproducibile, ma non distrugga immediatamente l’ambiente.

    Runtime gestite: Java, Go, Node.js

    Valgrind è spesso inadeguato per runtime gestite. Utilizzi profiler nativi della runtime:

    • Java: jmap/jcmd/jstack, heap dump analizzati con Eclipse MAT (Memory Analyzer Tool).
    • Go: runtime/pprof, interfaccia pprof, heap profile via net/http/pprof.
    • Node.js: heapdump, –inspect, profilazione heap di 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

    Questi strumenti producono snapshot dell’heap, grafi delle referenze e alberi dei dominatori che mostrano quali oggetti trattengono memoria.

    Build di debug, Sanitizer e flag di compilazione

    Per analisi ripetibili gli sviluppatori dovrebbero fornire build di debug e Sanitizer. Flag del compilatore importanti:

    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
    

    Perché: i simboli di debug rendono gli stacktrace significativi. ASan/LSan rilevano rapidamente Use-After-Free e leak durante i test; le limitazioni sono l’overhead prestazionale e altre modifiche al runtime.

    Automazione, CI e verifiche annuali

    I leak di memoria sono spesso latenti e si manifestano sotto carico a lungo termine. Pianifichi verifiche automatizzate:

    • Regression notturna nella CI con Sanitizer e workload riproducibili.
    • Esecuzioni periodiche di Massif in staging con carico noto, report basati su artefatti nel repository degli artefatti.
    • Playbook di alert che, al crescere del RSS, generano automaticamente uno snapshot (pmap/smaps) e avviano un breve campionamento 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/"
    

    Gli artefatti automatizzati aiutano gli sviluppatori a ricostruire più rapidamente lo scenario riproducibile.

    Metriche misurabili ed esempi per Prometheus

    Definisca metriche chiare in Prometheus per le regole di alert:

    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)
    

    Integrate con application-instrumentation: metriche della dimensione dell’heap della runtime, numero di cache aperte, tassi di cache hit/miss — in questo modo i leak si distinguono più facilmente dalla crescita legittima delle cache.

    Piano di rollback e runbook di emergenza

    Un rollback pulito è obbligatorio. Passi di esempio per i servizi systemd:

    Shell
    # Beispiel Rollback (systemd unit)
    # 1. Stoppen neuer Pods / Dienste
    systemctl stop myservice
    # 2. Deploy alte Version (aus Repo oder Paket)
    apt-get install --allow-downgrades myservice=1.2.3-1
    # 3. Starten und Monitoren
    systemctl start myservice
    journalctl -u myservice -f --lines=200
    

    In Kubernetes utilizzate i meccanismi di rollback (kubectl rollout undo) e assicuratevi che gli alert siano disattivati durante il rollback o inseriti in una finestra di manutenzione del monitoring.

    Quando scalare — supporto vendor o kernel

    Scalate quando:

    • Osservate indicatori chiari a livello di kernel (crescita delle slab, errori di allocazione).
    • I backtrace dei processi puntano a codice di libreria che non mantenete voi stessi (es. glibc, libreria di terze parti).
    • Avete leak di memoria riproducibili in servizi gestiti e non esiste una soluzione alternativa sensata.

    Preparate per i casi di supporto questi artefatti: estratto di dmesg, snapshot di slabtop, pmap/smaps, perf captures, log di Valgrind o heap-dump e passi di riproduzione esatti.

    Riepilogo e conclusione

    La diagnosi dei leak di memoria può essere eseguita in modo sistematico e graduale: far emergere i problemi tramite monitoring, usare pmap/smaps per restringere il campo, impiegare perf (incl. Uprobes) per rapidi test di ipotesi e Valgrind per analisi profonde dell’heap in un ambiente appropriato. Mezzi aggiuntivi sono profiler nativi della runtime (jemalloc, heaptrack), sanitizer in CI e generazione automatica di artefatti al verificarsi di alert. Requisiti importanti sono privilegi amministrativi per perf, simboli di debug per una migliore interpretazione delle catene di chiamate e ambienti di staging isolati per strumenti di analisi invasivi. Cause comuni sono cache, mmaps o frammentazione — queste devono essere sempre verificate come possibili cause prima di procedere con misure di debugging invasive. In conclusione: pianificate ogni passo con una strategia di rollback, documentate artefatti e alert e coordinatevi strettamente con i team di sviluppo affinché le correzioni siano riproducibili e possano essere distribuite in produzione in sicurezza.

    Ulteriori indicazioni: Prendete in considerazione l’uso di profiler specifici per l’allocator (jemalloc, tcmalloc) in caso di problemi ricorrenti; questi sono spesso più adatti alla produzione rispetto a Valgrind. Tenete pronti build di debug e script di riproduzione, in modo che gli sviluppatori possano ricostruire rapidamente ciò che avete osservato nell’analisi.