Problemerkennung mit Dtrace
Artikel erschienen in Swiss IT Magazine 2008/03
Plötzlich wird ein Server unerträglich langsam. Die Netzlast hält sich laut Kollegen der Netzabteilung aber im grünen Bereich. Die Zahl der Plattenzugriffe ist zwar erhöht, aber auch nicht alarmierend. Welcher Prozess bremst das System aus? Oder warum stürzt ein gestern noch laufendes Programm bei bestimmten Operationen reproduzierbar nach 3 Minuten Laufzeit ab? Bei welcher Bibliothek wird ein Core File generiert?
Solche Fragen beschäftigen einen Systemadministrator regelmässig. Werkzeuge wie truss (Solaris), strace (Linux) oder ktrace (Mac OS, BSD) dienen dazu, sogenannte System Calls (Aufrufe von Systemprozeduren) zu verfolgen und einigermassen zu sehen, was ein Prozess macht. Leider ist es schwer, der erzeugten Datenflut Herr zu werden und das herauszufiltern, was wirklich interessant ist. Ausserdem verlangsamen diese Werkzeuge die Ausführung des Prozesses, welcher überwacht wird. Wer ganz tief ins System einsteigen möchte, kann auch auf Werkzeuge zum Kernel Debugging zurückgreifen. Dies führt aber meistens viel zu weit und erfordert ein wirklich tiefes Verständnis des jeweiligen Betriebssystems.
Einen vollkommen neuen Ansatz zur Problemerkennung hat Sun mit Solaris 10 eingeführt. Unter dem Namen Dtrace wird ein Werkzeugkasten geliefert, welcher Systemadministratoren und Entwicklern gleichermassen die Arbeit erleichtern soll.
Im Wesentlichen besteht Dtrace aus sogenannten Providern und einer interpretierten Programmiersprache namens D, die aber nichts mit einer anderen, älteren, Programmiersprache namens D zu tun hat. Ein Provider ist eine Art Messpunkt im Kern, in den Bibliotheken oder in eigenen Programmen. Ein aktuelles Solaris-10-System hat rund 47’000 Provider, ein Mac OS X 10.5 knapp 23’000. Die Zahl der Provider variiert je nach installierter Software, Patches und Betriebssystemversion und erhöht sich stetig.
Die Funktionsweise eines D-Scripts lässt sich am besten anhand eines Beispiels illustrieren (siehe Kasten «Dtrace-Beispiel»). Wie man sieht, besteht ein D-Skript aus einem oder mehreren Blöcken. Am Anfang jedes Blocks wird der Provider definiert, welcher durch zusätzliche Bedingungen eingeschränkt werden kann. Der erste Block im Beispiel verwendet den bereits erwähnten Provider syscall:::entry. Dieser würde nun alle Einsprung-Ereignisse in Systemaufrufen protokollieren. Da dies aber weder gewünscht noch praktikabel ist, wird die Protokollierung durch die Angabe einer Prozess-ID eingeschränkt. Die gewünschte Prozess-ID kann entweder im Script hart codiert oder per Umgebungsvariable $target referenziert werden. Die Umgebungsvariable steht aber nur zur Verfügung, wenn das zu überwachende Programm über Dtrace und das Kommandozeilenargument -c gestartet wird.
Im nächsten Teil des Blocks wird angegeben, was mit den vom Provider erhaltenen Daten gemacht werden soll. Im Beispiel werden diese auf den Bildschirm ausgegeben. Dazu verwenden wir die von C bekannte Syntax von printf. Ausgegeben werden der Name des Systemaufrufs und die Argumente.
Wie das Beispiel zeigt, ist die Erstellung eines einfachen Scripts keine Hexerei. Auch sonst ist die zu Grunde liegende Sprache D nicht schwer zu erlernen. Kenntnisse in C sind aber hilfreich. Auch sollte man zumindest das fundemantalste Basiswissen über Prozess- und Kern-Architektur haben, um selber D- beziehungsweise Dtrace-Skripte zu schreiben. Hilfe erhält man auch im Web. Brendan Gregg stellt beispielsweise auf seiner Webseite (www.brendangregg.com/Dtrace.html) eine Auswahl von Dtrace-Einzeilern sowie ein nützliches Dtrace Toolkit zum Download bereit. Mit Hilfe dieses Toolkits kann man viele in der Praxis auftretenden Probleme analysieren, ohne gleich selbst Hand anlegen zu müssen. Mit folgendem Beispiel kann man die Anzahl der Systemaufrufe der laufenden Prozesse ausgeben:
dtrace -n 'syscall:::entry{ @[execname] = count(); }'
Dtrace ist aber nicht nur ein Ersatz für truss und Konsorten oder dazu geeignet, Statistiken auszugeben. Es vereinfacht tatsächlich die Analyse beziehungsweise die Suche von Problemen in einem System.
Ein Beispiel: Ein Server leidet unter einer relativ hohen Load. Zwar kann mit top oder prstat ermittelt werden, welcher Prozess die hohe Last verursacht, nicht aber, was deren Ursache ist. Mit Dtrace ist dies nun aber möglich.
Dazu benötigt man den Provider profile. Dieser kann als eine Art Taktgeber verwendet werden, der soundsooft pro Sekunde (in folgendem Beispiel 1000 Mal) eine Dtrace-Abfrage abfeuert. Diese ermittelt, welcher Prozess einen der im System vorhandenen Prozessor beansprucht und gibt aus, wie oft dies der Fall war:
# dtrace -n 'profile-1000 { @[execname] = count(); }'
dtrace: description 'profile-1000' matched 1 probe
samu 2
esd 4
dtrace 23
samfsrestore 40
fsflush 205
scp 395
sam-arcopy 577
sshd 1566
sched 7032
Wie man sieht, verbraucht der SSH-Daemon gleich nach dem System Scheduler am meisten Rechenzeit. Jetzt kann man anhand der Prozess-ID des SSH-Daemon weitere Analysen vornehmen. Im nächsten Beispiel wird untersucht, welche Systemaufrufe von diesem Prozess am meisten verwendet werden:
# dtrace -p 21853 -n 'pid$target:::entry{ @[probefunc] = count(); }'
dtrace: description 'pid$target:::entry' matched 10234 probes
debug2 30
do_log 30
[...]
memcpy 51998
memset 52247
AES_decrypt 146030
Damit weiss man nun, dass der Prozess die meiste Zeit beim Decodieren der mit AES verschlüsselten SSH-Verbindung verbraucht.
Gregor Longariva (longariva@softbaer.de) ist Solaris-Administrator am Rechenzentrum der Universität Erlangen-Nürnberg.