IT-Admin.tech

Speicherleck-Diagnose auf Servern mit perf, pmap und 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.

Speicherleck-Diagnose beginnt mit Beobachtung: Langsam steigender RAM-Verbrauch, wiederkehrende OOM-Killer-Logs oder unerklärliche Swap-Nutzung sind klare Warnsignale. In diesem Beitrag zeige ich eine praxisorientierte Vorgehensweise zur Speicherleck-Diagnose auf Linux-Servern. Die Methoden kombinieren einfache Inspektionen (pmap, /proc/*/smaps), Sampling- und Uprobe-Analysen mit perf sowie tiefe Heap-Inspektionen mit Valgrind. Ziel ist, für Administratoren und System-Engineers einen verlässlichen Prüfpfad zu liefern: Voraussetzungen, typische Fallstricke, konkrete Befehle und sichere Rückfallstrategien.

Wann ist es wirklich ein Speicherleck?

Bevor Sie Werkzeuge anwerfen: Nicht jedes ansteigende RAM-Bild ist ein Leak. Typische Quellen, die wie Lecks wirken können, sind wachsende Caches, Memory-Mapped-Files, Fragmentierung oder kurzzeitige Lastspitzen. Ein Speicherleck ist im engeren Sinn unerwünschte, nicht freigegebene Speicherbelegung in einem Prozess oder im Kernel, die über die Zeit persistent wächst.

Prüfindikatoren:

  • Längerfristig monotone Erhöhung der RSS (Resident Set Size) eines Prozesses über Stunden/Tage.
  • OOM-Einträge im Kernel-Log verbunden mit demselben Prozess.
  • Hohe Private-Dirty- oder Private-Clean-Werte in /proc/<pid>/smaps, die nicht durch legitime Caching-Strategien erklärbar sind.

Erste Beobachtung: Metriken und Monitoring

Vor jedem Debuglauf muss das Problem messbar sein. Nutzen Sie Monitoring-Tools (z. B. Prometheus, Zabbix, Datadog) oder einfache Befehle, um Trends zu belegen. Erfassen Sie Basisgrößen: RSS, VSZ, Swap-Usage, Page-ins/outs und Anzahl der Prozesse/Threads.

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

Wenn Page-outs und Swap-Nutzung parallel zum RSS-Anstieg wachsen, hat das unmittelbare Betriebsrelevanz (Leistungseinbruch). Dokumentieren Sie Werte als Referenz vor Eingriffen.

Prozess-Fokus: pmap, smaps und PSS

pmap zeigt die Speicher-Map eines Prozesses an. Es ist nützlich, um große mmaps zu finden (z.B. große Dateien, shared memory). Ergänzend liefert /proc/<pid>/smaps detailreiche Metriken wie Pss (Proportional Set Size), Private_Dirty und Shared_Clean. PSS verteilt die Größe geteilter Pages fair auf beteiligte Prozesse und ist daher für Accounting brauchbarer.

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'

Praxis: Suchen Sie mmaps mit ungewöhnlich großem Private_Dirty oder stetig wachsende PSS-Werte. Tools wie smem konsolidieren PSS pro Prozess und sind hilfreich für Reports.

Sampling mit perf: schnell Hypothesen prüfen

perf ist ein Kernel-basiertes Performance-Tool. In der Diagnose hilft es, Hotpaths und Aufruferketten zu finden. Für Speicherlecks nutzen wir zwei Muster:

  • Sampling von CPU-Stacks, um code-Pfade zu identifizieren, die Speicherallokationen oder Cache-Wachstum verursachen.
  • Uprobes (User-space probes) auf Allokatoren wie malloc, realloc oder mmap, um tatsächlich Allocations-Aufrufe zu sehen.

Wichtig: Für perf benötigen Sie passende Rechte (root oder Anpassung von /proc/sys/kernel/perf_event_paranoid) und idealerweise Debug-Symbole, damit Stacktraces sinnvoll sind. Uprobes funktionieren nur, wenn die zu instrumentierende Bibliothek/Executable stabil ist und die Pfade bekannt sind.

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

Warum das funktioniert: Uprobes erlauben, Funktionsaufrufe in Userland zu erfassen; kombiniert mit Callchains (Stacktraces) sehen Sie, welcher Anwendungscode häufig Allokationen triggert. Wann es scheitert: statisch gelinkte Binaries, JIT-kompilierte Prozesse oder stripped binaries liefern kaum aussagekräftige Symbole.

Alternative perf-Traces

Sie können auch Tracepoints für sys_enter_mmap oder Kernel-Events wie kmem:kmalloc (für Kernel-Leaks) nutzen:

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

Diese Daten zeigen, ob der Prozess viele mmaps erzeugt – typisch bei wachsenden memory-mapped caches oder bibliotheksseitigem Verhalten.

Tiefe Heap-Analyse mit Valgrind

Valgrind (Memcheck) ist ein dynamisches Analysewerkzeug, das Speicherlecks und Invalid-Reads/Writes erkennt. Es ersetzt nicht das Monitoring in Produktion: Valgrind erhöht Laufzeit und Speicherbedarf stark und gehört in eine Test- oder Staging-Umgebung, die das Produktionsverhalten nachbildet.

Wichtige Optionen:

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

Warum Valgrind: Memcheck findet echte Festhalte-Lecks (alloc ohne free) und zeigt präzise Callstacks, wenn Binary und Libraries mit Debug-Symbolen gebaut sind. Einschränkungen: GC-basierte Sprachen (z. B. Managed-Runtimes) oder komplexe asynchrone Server verhalten sich unter Valgrind anders; Browser- oder große Services sind oft unpraktisch groß.

Typische Stolperfallen und wie man sie vermeidet

  • Produktive Valgrind-Läufe: vermeiden. Nutzen Sie Staging-Umgebung oder Capturing via core dumps und offline-Analyse.
  • Fragmentierung vs. Leak: Manchmal wächst der virtuelle und resident Speicher wegen Fragmentierung. Prüfen Sie mit /proc/<pid>/smaps und Tools wie malloc-stats Ihrer malloc-Implementierung.
  • Shared Memory und mmaps: Große mmaps können als RSS erscheinen, sind aber nicht notwendigerweise leaks—kontrollieren Sie Backing-Files.
  • Threads und TLS: Thread-spezifische Caches (Thread-Local Storage) erhöhen persistenten Speicher, besonders bei hoher Threadanzahl.

Wenn Valgrind nicht praktikabel ist: Alternativen

Für produktionsnahe Heap-Analysen ohne Valgrind bieten sich an:

  • jemalloc mit Built-in Profiling (jeprof)
  • gperftools (tcmalloc) Heap-Profiler
  • heaptrack (sampling-basierte userland-Analyse)
  • ASan/LSan (Address/LeakSanitizer) während des Build-/Test-Prozesses — besser für CI als für Produktion

Diese Tools sind in der Regel weniger invasiv als Valgrind und können in (angepasster) Staging- oder Canary-Umgebung laufen.

Pragmatischer Prüfablauf — Checkliste

  1. Beobachten: Trenddaten aus Monitoring exportieren, RSS/RAM/Swap-Verhalten dokumentieren.
  2. Per-Prozess-Fokus: ps, top, pmap -x und /proc/<pid>/smaps analysieren. Ziel: Prozess-ID und betroffene Mappings bestimmen.
  3. Sampling mit perf: Uprobes auf malloc/realloc/free oder syscalls wie mmap setzen; Callchains auswerten.
  4. Reproduzieren in Staging: Valgrind (Memcheck) und Massif laufen lassen, Heap-Dumps erzeugen und auswerten.
  5. Fix entwickeln: Objektfreigabe, Cache-Grenzen, Pooling oder Austausch von Allocator/Libs.
  6. Deploy mit Canary: Versions- und Rollback-Plan, Monitoring-Alerts definieren.
  7. Postmortem: Ursache, Indikatoren, Fix und automatisches Monitoring-Alerting dokumentieren.

Sichere Betriebs- und Rückfallstrategien

Diagnosen können riskant sein: perf mit uprobes ist minimal-invasiv, Valgrind disruptiv. Führen Sie invasive Tests nie direkt in Production durch. Wenn ein Fix ausgerollt wird, halten Sie einen schnellen Rollback bereit (Package-Repository, Service-Manager-Skript, Kubernetes-Revision). Definieren Sie Alert-Level: z. B. Warnung bei Anstieg um 20 % in 1 Std., kritischer Alarm bei OOM-Einträgen.

Kernel-Seite: Slab- und Kernel-Leaks

Manchmal sind Leaks kernelseitig (Slab-Allocator, Netzwerkbuffers). Prüfen Sie:

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

Kernel-Leaks sind deutlich schwieriger und erfordern meist Kernel-Profile oder Live-Patching; als Admin dokumentieren Sie Fundstücke und eskalieren an Kernel-Entwickler oder Vendor-Support.

Praktische Beispiele — kurze Workflows

1) Schnelltest auf steigende mmaps (Produktiv, gering invasiv):

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 auf malloc in Staging (zuordnen, welche Callchains viele Allokationen auslösen):

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 Testumgebung:

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

Speicherleck-Diagnose: weiterführende Strategien für komplexe Umgebungen

In realen Hosting-Umgebungen laufen Services selten isoliert. Container, systemd-Units, Sidecars oder Shared-Libraries verändern das Bild und fordern angepasste Arbeitsweisen.

Container und cgroups (Praxishinweise)

In Container-Umgebungen begrenzen cgroups den Speicherverbrauch. Der Prozess kann ein Leak haben, wird aber durch den cgroup-Limit-Einfluss früher getötet. Prüfen Sie cgroup-Statistiken:

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

Tipps: In Kubernetes prüfen Sie Events (kubectl describe pod) und Container-Logs; setzten Sie vorsichtige Limits, damit ein Leak noch reproduzierbar bleibt, aber die Umgebung nicht sofort zerstört wird.

Managed-Runtimes: Java, Go, Node.js

Valgrind ist für Managed-Runtimes oft ungeeignet. Use runtime-native profilers:

  • Java: jmap/jcmd/jstack, Heap Dumps analysiert mit Eclipse MAT (Memory Analyzer Tool).
  • Go: runtime/pprof, pprof-UI, heap-profiles über net/http/pprof.
  • Node.js: heapdump, –inspect, v8 heap-profiling.
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

Diese Werkzeuge liefern Heap-Snapshots, References-Graphs und dominator-Bäume, die zeigen, welche Objekte Speicher halten.

Debug-Builds, Sanitizer und Build-Flags

Für wiederholbare Analysen sollten Entwickler Debug-Builds und Sanitizer bereitstellen. Wichtige Compiler-Flags:

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

Warum: Debug-Symbole machen Stacktraces aussagekräftig. ASan/LSan finden Use-After-Free und Leaks im Testlauf schnell; Limitationen sind Performance-Overhead und andere Laufzeit-Änderungen.

Automatisierung, CI und jährliche Prüfungen

Speicherlecks sind oft latent und zeigen sich unter langfristiger Last. Planen Sie automatisierte Prüfungen:

  • Nightly-Regression in CI mit Sanitizern und reproduzierbaren Workloads.
  • Periodische Massif-Läufe in Staging mit bekannter Last, artefakt-basierte Reports im Artefakt-Repo.
  • Alert-Playbooks, die bei steigendem RSS automatisch einen Snapshot erzeugen (pmap/smaps) und ein kurzes perf-Sampling starten.
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/"

Automatisierte Artefakte helfen Entwicklern, das reproduzierbare Bild schneller nachzustellen.

Messbare Metriken und Prometheus-Beispiele

Definieren Sie klare Metriken in Prometheus für Alert-Regeln:

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)

Ergänzen Sie mit application-instrumentation: Heap-Size-Metriken der Runtime, Anzahl offener Caches, cache hit/miss-Rates — so lassen sich Leaks leichter von legitimen Cache-Wachstums unterscheiden.

Rollback-Plan und Notfall-Runbook

Ein sauberes Rollback ist Pflicht. Beispiel-Schritte für Systemd-Services:

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 nutzen Sie Rollback-Mechanismen (kubectl rollout undo) und stellen sicher, dass Alerts während des Rollbacks abgeschaltet oder in ein Monitoring-Maintenance-Fenster gesetzt werden.

Wann eskalieren — Vendor- oder Kernel-Support

Eskalieren Sie, wenn:

  • Sie klare Kernel-Indikatoren sehen (Slab-Wachstum, allocation failures).
  • Prozess-Backtraces zeigen auf Library-Code, den Sie nicht selbst pflegen (z. B. glibc, Drittanbieter-Bibliothek).
  • Sie reproduzierbare Speicherlecks in Managed-Services sehen und keine sinnvolle Workaround-Möglichkeit besteht.

Bereiten Sie für Support-Fälle diese Artefakte vor: dmesg-Auszug, slabtop-Snapshot, pmap/smaps, perf captures, Valgrind-Logs oder heap-dumps und exakte Reproduktionsschritte.

Zusammenfassung und Fazit

Die Speicherleck-Diagnose lässt sich systematisch und schrittweise durchführen: Monitoring erkennen lassen, pmap/smaps zur Einengung nutzen, perf (inkl. Uprobes) für schnelle Hypothesentests einsetzen und Valgrind für tiefe Heap-Analysen in einer geeigneten Umgebung. Zusätzliche Mittel sind runtime-native Profiler (jemalloc, heaptrack), Sanitizer in CI und automatisierte Artefakt-Erzeugung bei Alerts. Wichtige Voraussetzungen sind administrative Rechte für perf, Debug-Symbole zur besseren Interpretation von Callchains und isolierte Staging-Umgebungen für invasive Analysewerkzeuge. Häufige Fehlerquellen sind Caches, mmaps oder Fragmentierung—diese sollten immer als mögliche Ursachen geprüft werden, bevor invasive Debugging-Maßnahmen greifen. Abschließend: Planen Sie jeden Schritt mit Rollback-Strategie, dokumentieren Sie Artefakte und Alerts und koordinieren Sie eng mit Entwicklerteams, damit Fixes reproduzierbar und sicher in Produktion gelangen.

Weiterführende Hinweise: Erwägen Sie den Einsatz von allocatorspezifischen Profilern (jemalloc, tcmalloc) bei wiederkehrenden Problemen; diese sind oft produktionsfreundlicher als Valgrind. Halten Sie Debug-Builds und Reproduktions-Skripte bereit, damit Entwickler zeitnah nachvollziehen können, was Sie in der Analyse gesehen haben.

Weiterfuehrend

Passende weitere Inhalte