Troubleshooting Linux und IP-Netzwerke

8. Werkzeuge zur lokalen Fehlersuche

Linux stellt mir eine Unmenge an Werkzeugen für die Fehlersuche zur Verfügung. Etliche davon kommen mir sowohl bei Total- als auch bei Partialausfällen zu gute. Andere bei Performanceproblemen. Einige sind so nützlich, dass ich sie immer wieder bei den unterschiedlichsten Problemen einsetze.

Da es mir schwerfällt, die einzelnen Werkzeuge bestimmten Kategorien zuzuordnen, stelle ich sie nachfolgend in alphabetischer Reihenfolge vor. Das erleichtert zumindest das Wiederfinden, wenn ich nur etwas nachschlagen möchte.

acct

Abrechnungsprogramme spielen eine wichtige Rolle bei der Performanceoptimierung. Sie liefern einen einfachen Weg um herauszufinden, was ein Rechner macht, welche Anwendungen laufen, wieviel Systemzeit die Prozesse verbrauchen, wie stark sie das System belasten. Wenn ich die Programme und ihr Verhalten im Großen und Ganzen kenne, kann ich mir eine Strategie überlegen, um die Performance zu optimieren. Das Programmpaket acct liefert diese Abrechnungsdaten.

Natürlich belastet das Führen der Statistiken das System zusätzlich. Und Extra-Plattenplatz benötigen die Statistiken auch. Andererseits: wenn das System deutlich auf das Einschalten der Statistikerfassung reagiert, arbeitet es bereits in einem Bereich nahe der Belastungsgrenze und eine Analyse der Systemperformance und entsprechende Maßnahmen sind längst fällig.

Ist das Paket acct installiert, so wird das Accounting meist automatisch beim Systemstart via accton eingeschaltet. Will ich es deaktivieren, reicht es nicht, es über das Startscript auszuschalten, da cron in regelmäßigen Abständen die Komprimierung der Protokolle anstößt und dabei das Accounting ab- und wieder angeschaltet wird. Um das Accounting zu deaktivieren ändere ich bei Debian-Systemen in /etc/default/acct die Variable ACCT_ENABLE auf 0.

Die Abrechnungsdaten werte ich mit dem Programm sa aus. sa gibt eine Tabelle aus mit einer Zeile pro Programm, der Anzahl der Aufrufe des Programms in der ersten Spalte und den folgenden Bezeichnungen in den anderen Spalten:

cpu
die Summe von System- und Userzeit in CPU-Minuten
re
die ``wirkliche’’ Laufzeit des Programms
k
der durchschnittliche Speicherverbrauch in KByte. Der Durchschnitt basiert auf der CPU-Zeit des Programms.
avio
die durchschnittliche Anzahl von I/O-Operationen pro Programmaufruf
tio
die Gesamtzahl der I/O-Operationen
ksec*
das Integral über den Speicher und die CPU-Zeit
s
die Systemzeit
u
die Benutzerzeit

Ganz rechts, ohne Bezeichnung steht der Programmname.
Ist dieser mit einem Asterisk (‘*’) gekennzeichnet, ist das Programm als Daemon gelaufen. Das heißt, es hat fork() aufgerufen, aber nicht exec(). Daemon-Prozesse sammeln durch ihre lange Laufzeit sehr viel CPU-Zeit an.

Die Tabelle ist nach der CPU-Zeit absteigend sortiert.

Programme, die nur einmalig gelaufen sind, werden in der Zeile ***other* zusammengefasst.

Mit den folgenden Optionen kann ich die Ausgabe von sa modifizieren, die Handbuchseite kennt noch mehr davon:

-a | –list-all-names
Zeigt alle Programme, fasst keine Programme unter
***other* zusammen.
-b | –sort-sys-user-div-calls
Sortiert die Aufrufe nach der CPU-Zeit geteilt durch die Anzahl der Aufrufe.
-d | –sort-avio
Sortiert nach der durchschnittlichen Anzahl der Eingabe-/Ausgabe-Operationen.
-D | –sort-tio
Sortiert nach der Gesamtzahl der I/O-Operationen.
-i | –dont-read-summary-file
Ignoriert die Auswertungsdatei. Mit dieser Option zeigt sa die Prozesse seit dem letzten Aufruf von sa -s.
-k | –sort-cpu-avmem
Sortiert nach dem durchschnittlichen Speicherverbrauch. Dieser Report identifiziert die größten Speichernutzer.
-n | –sort-num-calls
Sortiert nach der Anzahl der Aufrufe, identifiziert die am häufigsten aufgerufenen Programme.
-r | –reverse-sort
Dreht die Sortierreihenfolge um.
-s | –merge
Fasst die aktuellen Accountingdaten in der Auswertungsdatei zusammen.
-t | –print-ratio
Zeigt das Verhältnis von Laufzeit zu CPU-Zeit, identifiziert Programme mit sehr viel Leerlauf.

bonnie++

Bei jeglicher Art von Performance Tuning mache ich grundsätzlich je eine Bestandsaufnahme vor den Tuning-Maßnahmen und danach, um mich von der Wirkung des Tunings zu überzeugen. Bonnie++ ist ein Programm, mit dem ich die Performance der Lese- und Schreiboperationen im Dateisystem in Zahlen ausdrücken und damit vergleichen kann. Ich setze bonnie++ ein, wenn ich

Bonnie++ sollte niemals auf aktiven Produktionsmaschinen laufen, da die Performance durch die Tests sehr stark beeinträchtigt wird.

Das Programm gibt für jeden Test, den es durchführt, zwei Kennzahlen aus: die geschaffte Arbeit (je mehr, um so besser) und die dafür benötigte CPU-Zeit (je weniger, umso besser).

Die Tests teilen sich grob in zwei Abschnitte. In einem Abschnitt testet bonnie++ den I/O-Durchsatz mit großen Dateien, wie er ähnlich bei Datenbankanwendungen vorkommt. Im anderen Abschnitt geht es um das Erzeugen, Lesen und Löschen vieler kleiner Dateien, wie es auf Proxy-, Mail- und News-Servern vorkommt.

In den meisten Fällen bin ich daran interessiert, das I/O-Verhalten bei einzelnen Dateizugriffen zu beobachten. Bei bestimmten Problemen möchte ich jedoch den Einfluss von gleichzeitigen Dateizugriffen bewerten. Zu diesem Zweck kann ich mehrere bonnie++ Prozesse synchron starten.

Die Ausgabe von bonnie++ kommt, wie schon beim Vorgängerprogramm bonnie als Text mit 80 Spalten. Zusätzlich gibt bonnie++ die Daten als kommaseparierte Werte (CSV) aus, die einfacher weiterverarbeitet werden und mehr als 80 Zeichen pro Zeile einnehmen können. Für diese CSV-Daten gibt es zwei Programme (bon_csv2html, bon_csv2txt), die die Daten für die HTML-Ausgabe beziehungsweise das bekannte Textformat aufbereiten. Die Felder der CSV-Daten sind in den Handbuchseiten der beiden Programme beschrieben.

Optionen

-d dir
In diesem Verzeichnis, legt bonnie++ die Testdateien an. Ohne Angabe dieser Option werden die Testdateien im aktuellen Verzeichnis angelegt.
-s size
Die Größe der Dateien für die I/O-Performance-Tests. Mit einer Größe von 0 wird dieser Test übersprungen.
-n number
Die Anzahl der Dateien für den Dateierzeugungstest. Die Anzahl wird als Vielfaches von 1024 angegeben. Ist die Anzahl 0, überspringt bonnie++ diesen Test. Per Default legt bonnie++ leere Dateien an. Es ist möglich, die maximale und minimale Größe der Dateien und die Anzahl der Verzeichnisse durch Doppelpunkt getrennt gemeinsam mit der Anzahl anzugeben. Details stehen in den Handbuchseiten.
-x number
Die Anzahl der Testläufe. Damit ist es möglich, mehrere Tests ununterbrochen nacheinander zu machen, die Ergebnisse kommen kontinuierlich als CSV-Daten.
-u user
Der Benutzer, unter dem der bonnie++ Prozess laufen soll. Ich kann bonnie++ als normaler Nutzer starten. Starte ich es als root, gebe ich mit dieser Option einen anderen Benutzer vor, um Fehler im Dateisystem zu vermeiden.
-f | -f size
Fast Mode Control, überspringt den zeichenweisen I/O-Test, wenn kein Parameter. Ansonsten gibt es die Testgröße für zeichenweisen I/O-Test vor (default 20M).
-b
Keine Pufferung der Schreiboperationen, das heißt bonnie++ ruft fsync() nach jedem Schreiben auf.
-q
Quiet Mode. Ein Teil der Ausgabe wird unterdrückt, an STDOUT werden nur die CSV-Daten ausgegeben, alles andere an STDERR. Damit ist es einfacher, die Ausgabe weiter zu verarbeiten.
-p number
Die Anzahl der Prozesse, für die Semaphore reserviert werden sollen. Alle Prozesse, die Semaphore mit Option -ys verwenden, starten synchron.
-y s
Durch Semaphor synchronisiert starten
-y p
Mit Prompt synchronisieren. Bonnie++ startet erst, wenn <RETURN> eingegeben wurde.
-D
Direct-I/O (O_DIRECT) für Massen-I/O-Tests verwenden.
-z seed
Die Startzahl für den Zufallsgenerator angeben, um den gleichen Test zu wiederholen.
-Z file
Zufallsdaten aus der angegebenen Datei verwenden.

Synchrone Tests

Um mehrere Prozesse mit bonnie++ synchron zu starten, kann ich wie folgt vorgehen:

$ bonnie++ -p3
$ bonnie++ [weitere Optionen] -ys > out1 &
$ bonnie++ [weitere Optionen] -ys > out2 &
$ bonnie++ [weitere Optionen] -ys > out3 &

Signale

Bonnie++ kann mitunter recht lange laufen, vor allem wenn es mit Option -x seine Tests wiederholt.

Mit SIGINT kann ich die Ausführung abbrechen, bonnie++ räumt dann wieder auf, das heißt, es entfernt die temporären Dateien und Verzeichnisse. Das kann etwas dauern. Durch wiederholtes Senden von SIGINT bricht es das Aufräumen ab.

SIGHUP wird ignoriert, das heißt, wenn bonnie++ im Hintergrund läuft, bricht es nicht nach Abmelden vom Terminal ab.

busybox

Busybox kombiniert Mini-Versionen vieler gebräuchlicher UNIX-Befehle in einem Programm. Es ist mit Blick auf Größenoptimierung und geringen Ressourcenverbrauch geschrieben. Für ein arbeitsfähiges System benötige ich nur noch ein /dev Verzeichnis mit den Gerätedateien, ein /etc Verzeichnis mit den Konfigurationsdateien und einen Linux-Kernel. Busybox ist extrem anpassungsfähig. Beim Kompilieren kann ich festlegen, welche Befehle hineinkommen und so die Funktionalität gegen die Programmgröße abwägen. Das macht es ideal für eingebettete Systeme und kleine Systemumgebungen.

Damit bin ich beim Punkt, warum man Busybox kennen sollte. Es befindet sich auf dem InitRamFS, dem ersten Dateisystem, das zusammen mit dem Kernel vom Bootloader geladen wird. Auf Live-CDs, beim Netboot und bei Rescue-Disks kann ich mit Busybox rechnen. Auch in kleineren Einzelgeräten, wie Routern, wird es oft eingesetzt.

Busybox ist ein Multi-Call-Binary. Das heißt, es verhält sich komplett anders, je nachdem, wie es aufgerufen wird. Sämtliche enthaltenen UNIX-Befehle kann ich auf zwei Arten aufrufen. Entweder ich gebe den Befehl als erstes Argument und danach dessen Argumente an:

$ /bin/busybox cp file file.bak

Oder ich erzeuge einen Link auf Busybox mit dem Namen des Befehls und rufe es über diesen Link auf:

$ /bin/busybox ln -s /bin/busybox ./cp
$ ./cp file file.bak

Hieße der Link stattdessen mv, hätte busybox die Datei nicht kopiert sondern umbenannt.

Weil Busybox extrem konfigurierbar ist, möchte ich vielleicht wissen, welche Funktionen mein konkretes Binary kennt. Dazu rufe ich Busybox ohne Argumente auf:

$ busybox
BusyBox v1.18.5 (Ubuntu 1:1.18.5-1ubuntu4.1) \
multi-call binary.
Copyright (C) 1998-2009 Erik Andersen, Rob Landley, \
Denys Vlasenko
and others. Licensed under GPLv2.
See source distribution for full notice.

Usage: busybox [function] [arguments]...
   or: busybox --list[-full]
   or: function [arguments]...

    BusyBox is a multi-call binary that combines \
    many common Unix
    utilities into a single executable. Most people \
    will create a
    link to busybox for each function they wish to \
    use and BusyBox
    will act like whatever it was invoked as.

Currently defined functions:
    [, [[ , acpid, addgroup, adduser, adjtimex, ar, \
    arping, ash, awk,
    ...
    xargs, xz, xzcat, yes, zcat

Da die in Busybox implementierten Versionen der UNIX-Befehle von den täglich benutzten in Details abweichen, muss ich die genauen Optionen wissen. Neben der Handbuchseite, die ich in einer kleinen Umgebung oft nicht zur Verfügung habe, hilft mir Busybox selbst. Ich rufe den Befehl mit der Option --help auf, welche die meisten der implementierten Befehle verstehen:

$ busybox ln --help
BusyBox v1.18.5 (Ubuntu 1:1.18.5-1ubuntu4.1) \
multi-call binary.

Usage: ln [OPTIONS] TARGET... LINK|DIR

Create a link LINK or DIR/TARGET to the specified \
TARGET(s)

Options:
    -s  Make symlinks instead of hardlinks
    -f  Remove existing destinations
    -n  Don't dereference symlinks - treat like \
        normal file
    -b  Make a backup of the target (if exists) \
        before link operation
    -S suf  Use suffix instead of ~ when making \
        backup files

C (Programmiersprache)

Als Systemadministrator setze ich die Programmiersprache C selten direkt ein. Gut, zugegeben, ich nutze mein Verständnis dieser Programmiersprache, wenn ich die Ausgabe von strace auswerte. Aber das hätte ich auch ohne dieses Wissen hinbekommen. Wenn ich einen Fehler im Debugger suche, möchte ich die Sprache ebenfalls kennen, aber auch das mache ich als Systemadministrator sehr selten.

Ich nutze es, wenn ich einem Fehler in einem C-Programm nachspüre und keine Gelegenheit habe, mit dem Autor des Programms in Kontakt zu treten. Meist aber, wenn ich ein Werkzeug benötige, das Informationen direkt vom Kernel abgreift oder mit ihm interagiert. Dann ist C das Mittel der Wahl, weil es die Sprache ist, die im Kernel selbst verwendet wird, so dass ich die gleichen Datenstrukturen wie der Kernel in meinem Programm verwenden kann.

Natürlich kann ich mir damit gewaltig in den Fuß schießen, deshalb setze ich die Programmiersprache so defensiv wie möglich ein. Zum Beispiel, wenn ich in meiner bevorzugten Skriptsprache keine Entsprechung für die benötigte Systemfunktion habe. Oder, wenn ich ein Programm finde, das in etwa das macht, was ich will, und nur noch etwas modifiziert werden muss, wie das folgende Beispiel, das auf [ctDiedrich2012] zurückgeht.

Dieses Minimalprogramm überwacht Zugriffe auf die Dateien in einem Verzeichnis mit der Systemfunktion fanotify und gibt die PID des Prozesses aus, der auf die Datei zugreift, sowie den Dateideskriptor der Datei in diesem Prozess. Will ich mehr über den Prozess erfahren, kann ich mit lsof alle von ihm geöffneten Dateien herausbekommen, oder mit strace seine Systemaufrufe überwachen.

fnotify.c


 1 /* fnotify.c - notify about file access */
 2 #include <linux/fcntl.h>
 3 #include <stdio.h>
 4 #include <sys/fanotify.h>
 5 
 6 int main(int arg, char **argv) {
 7     struct fanotify_event_metadata ev;
 8     int fa = fanotify_init(FAN_CLASS_NOTIF,
 9 	O_RDONLY | O_LARGEFILE);
10     if (fa >= 0) {
11 	int m = fanotify_mark(fa, FAN_MARK_ADD,
12 	    FAN_OPEN | FAN_EVENT_ON_CHILD,
13 	    AT_FDCWD, argv[1]);
14 	if (m) {
15 	    perror("error with 'fanotify_mark()'");
16 	    return 2;
17 	}
18 	while (read(fa, &ev, sizeof(ev))
19 	      == sizeof(ev)) {
20 	    printf("PID: %d, FD: %d, M: 0x%02llX\n",
21 		ev.pid, ev.fd, ev.mask);
22 	}
23     }
24     else {
25 	perror("error with 'fanotify_init()'");
26 	return 1;
27     }
28     return 0;
29 }

Mit gcc -o fnotify fnotify.c übersetze ich das Programm.

Für die Systemaufrufe benötigt das Programm die POSIX Capability CAP_SYS_ADMIN, die ich ihm gebe, wie in Kapitel 6 erläutert. Anschließend kann ich das Programm aufrufen und zum Test in einer anderen Konsole eine Datei in diesem Verzeichnis öffnen.

$ sudo setcap cap_sys_admin=ep fnotify
$ ./fnotify .
PID: 5021, FD: 4, M: 0x20

Mit lsof schaue ich nach, welches Programm welche Datei geöffnet hat:

$ lsof -p5021
COMMAND...  FD   TYPE...NAME
less   ... txt    REG.../bin/less
...
less   ...   4r   REG.../home/.../code/fnotify.c

In diesem Beispiel habe ich in einer zweiten Konsole den Quelltext mit less betrachtet, was mir lsof in einer weiteren Konsole anzeigt.

Fanotify bietet noch weitergehende Möglichkeiten. So kann ich den Prozess blockieren und den Zugriff erlauben oder verbieten. Oder ich modifiziere die Datei vor dem Zugriff. Eine ausführliche Behandlung dieser Systemschnittstelle würde den Rahmen dieses Buches sprengen, aber es ist in manchen Fällen gut zu wissen, dass sich da noch etwas machen lässt.

dd

Ein eher unscheinbares, aber sehr nützliches Werkzeug ist dd. Bei der Fehlersuche verwende ich es um

Dateien von einem anderen Rechner sichern

Um Dateien zwischen zwei Rechnern zu kopieren, verwende ich am liebsten scp oder rsync.

In manchen Fällen, wenn beide Befehle nicht zur Verfügung stehen oder ich Daten von einer Gerätedatei kopieren möchte, greife ich auf dd zurück.

Bei kleinen Linux-Routern, die von einer read-only eingehängten Flash-Disk betrieben werden, sichere ich die Flash-Disk mit folgendem Befehl, von einem anderen Rechner:

$ ssh root@r-xyz dd if=/dev/sda | dd of=r-xyz.image

Mit SSH starte ich auf dem Router einen Prozess mit dd, der den Inhalt von /dev/sda, der ersten Festplatte, zur Standardausgabe schreibt. Die Standardausgabe dieses Prozesses leite ich via SSH und Pipe an einen lokalen Prozess mit dd, der von Standardeingabe liest und in die Datei r-xyz.image schreibt.

Partitionstabellen sichern und wiederherstellen

Partitionstabellen sichere ich, wenn ich mehrere Betriebssysteme auf einer Festplatte installieren will und damit rechnen muß, dass das Linux-System unbootbar wird, wenn ich ein anderes System installiere. Aber auch, wenn ich die Partitionen manipuliere, weil sich beispielsweise die Größe eines Festplattenimages geändert hat. Ebenso ist die Sicherung vor der Installation eines Bootloaders in der Partitionstabelle angezeigt.

# dd if=/dev/sda of=sda.image bs=512 count=1

Damit sichere ich die Partionstabelle.

# dd if=sda.image of=/dev/sda

Dieser Befehl stellt sie wieder her. Natürlich muss ich die Image-Datei an einem sicheren und erreichbaren Ort lagern.

Reparieren von Festplattensektoren

Der dritte Anwendungsfall ist das “Reparieren” von defekten Festplattensektoren.

Moderne Festplatten haben ungenutzten Plattenplatz, mit denen sie defekte Sektoren ersetzen können. Allerdings nur, wenn auf diesen Sektor geschrieben wird.

# dd if=/dev/null of=/dev/sda1 bs=$BLOCKSIZE \
     seek=$OFFSET count=1 oflag=direct,dsync

Mit diesem Befehl beschreibe ich gezielt einen Sektor der Festplatte. Dabei ist $BLOCKSIZE die Größe eines Sektors der Festplatte und $OFFSET die Differenz zwischen dem defekten Sektor und dem Anfang der Partition in Sektoren. Wenn die Plattenelektronik beim Schreiben bemerkt, dass der ursprüngliche Block defekt ist, ersetzt sie diesen durch einen Reserveblock und die Festplatte ist “repariert”.

Wie ich herausbekomme, welchen Sektor ich beschreiben muss, ist im Bad Block Howto in Englisch und in einem Artikel auf Pro-Linux.de in deutscher Sprache beschrieben.

fio - flexible I/O tester

Manche Fehler treten nur unter Last zutage. Genauso ist es beim Performance-Tuning. Ob meine Maßnahmen erfolgreich sind, sehe ich erst, wenn das System unter Last arbeitet. Damit ich nicht auf die Systembenutzer angewiesen bin, deren Last meist stochastisch und nicht vorhersagbar ist, benötige ich in diesem Fall ein Werkzeug, das mir eine geeignete Last erzeugen kann.

Früher habe ich dann schnell ein kleines Skript oder Programm gebastelt, das ungefähr die geforderte Last erzeugt, und dieses anschließend entsorgt.

Mit fio brauche ich kein Programm mehr zu schreiben, sondern nur noch eine Beschreibung für die gewünschte Last. Und das kann fast jede erdenkliche Last sein. Das Programm fio kann in einem Job synchron oder asynchron schreiben, per mmap() eingebundene Dateien bewegen, Netzlast simulieren oder einfach nur CPU-Zeit verbrennen. Und es kann mehrere Jobs gleichzeitig ausführen, so dass ich mir die gewünschte Last sehr genau zusammenstellen kann.

Die Jobs, die fio abarbeitet, werden in Dateien beschrieben, deren Format den Ini-Dateien entspricht, mit Sektionen, die durch ihren Namen in eckigen Klammern eingeleitet werden und Kommentaren, die mit ; oder # am Zeilenanfang beginnen.

Meist rufe ich fio nur mit dem Namen der Job-Datei als einzigem Argument auf. Habe ich nur einen einzigen Job, könnte ich dessen Parameter auch gleich auf der Kommandozeile angeben.

Details zum Einsatz von fio finde ich in der Handbuchseite, der Datei HOWTO und den Beispiel-Job-Dateien im Verzeichnis examples/ bei der Paketdokumentation.

fuser

Das Programm fuser setze ich ein, wenn ich Informationen darüber haben will, welche Prozesse bestimmte Dateien oder Netzwerksockets geöffnet haben, um sie dann mit anderen Programmen zu untersuchen. Zwar kann ich die ermittelten Prozesse gleich von fuser beenden lassen, aber in diesem Buch geht es vor allem um die Fehleranalyse und dafür wäre dieses Vorgehen doch zu voreilig.

Was mich vor allem interessiert, sind die Prozesse und die Art und Weise, wie diese die betreffenden Dateien verwenden. Die Prozesse zeigt fuser in einer Tabelle an. In der ersten Spalte steht der Dateiname, dahinter die PID. Mit der Option -v kann ich diese Ausgabe erweitern, so dass fuser für jeden Prozess den Benutzer (USER), die PID, den Zugriff (ACCESS) und den Namen des Prozesses (COMMAND) anzeigt.

Die Spalte ACCESS interessiert mich am meisten für die Analyse. Diese kann die folgenden Merkmale haben:

c
CWD, das Arbeitsverzeichnis des Prozesses.
e
Executable, die Datei wird als Programm ausgeführt.
f
File, die Datei ist als normale Datei geöffnet.
F
File, die Datei ist zum Schreiben geöffnet.
r
Root, die Datei ist Wurzelverzeichnis.
m
MMAP, die Datei ist in den Speicherbereich des Prozesses eingeblendet, zum Beispiel als Bibliothek.

Mit diesen Merkmalen bekomme ich heraus, wie ein Prozess eine Datei verwendet. Allerdings sehe ich das nur für Dateien, die im Dateisystem verlinkt sind. Dateien, die zwar geöffnet, aber nicht mehr im Dateisystem verlinkt sind, finde ich damit nicht, dafür benötige ich andere Programme, wie zum Beispiel lsof. Mit der Option --mount (-m) bekomme ich von fuser zumindest die PID dieser Prozesse, und kann diese dann mit lsof näher untersuchen.

Dazu muss ich folgende Besonderheit der Ausgabe von fuser beachten. Das Programm schreibt nur die PIDs an die Standardausgabe, alles andere kommt über die Fehlerausgabe. Damit kann ich die Ausgabe sehr bequem in Scripts weiterverarbeiten:

for p in $(fuser -m /); do
    lsof -p $p
done

Will ich die Ausgabe allerdings in einem Pager betrachten, oder dokumentieren, so schreibe ich:

fuser -m / 2>&1 | less

Außer für Dateien in Dateisystemen kann ich mit fuser auch die Prozesse ermitteln, die bestimmte Sockets geöffnet haben. Dazu wähle ich den entsprechenden Namensraum mit der Option --namespace SPACE (-n SPACE) aus. Das Programm kennt die folgenden Namensräume:

file
ist der Standard-Namensraum, der nicht extra angegeben werden muss.
tcp
steht für TCP-Sockets.
udp
steht für UDP-Sockets.

Sockets werden nach dem folgenden Schema angegeben:

[locport][,[remhost][,[remport]]][/namespace]

Den Namensraum kann ich angeben, wenn die Angabe eindeutig ist und ich diesen nicht explizit mit --namespace angeben will. Die Komma sind wichtig. So zeigt fuser ssh/tcp alle Prozesse, die mit dem lokalen Port 22 arbeiten, während fuser ,,ssh/tcp alle Prozesse mit abgehender Verbindung zu diesem Port anzeigt.

Mit der Option -4 beziehungsweise -6 grenze ich die Ausgabe auf die entsprechende Version des Internet-Protokolls ein.

Eher selten wende ich die Option --kill (-k) an, mit der fuser ein Signal (ohne weitere Angaben: SIGKILL) an die ermittelten Prozesse sendet. Das Signal kann ich mit einem vorangestellten Bindestrich (-) angeben, eine Liste der Signale bekomme ich mit --list-signals (-l).

Zum Beispiel könnte ich mit

fuser -k -HUP 22/tcp

alle SSH-Anmeldungen an diesem Rechner regulär beenden. War auch ich via SSH angemeldet, habe ich mich damit selbst hinausgeworfen.

Falls ich eine CD-ROM aushängen will, kann ich mit

fuser -k -m /media/cdrom

alle Prozesse, die auf die eingehängte CD-ROM zugreifen, beenden. Falls unter /media/cdrom kein Dateisystem eingehängt war, werden alle Prozesse, die das nächsthöhere Dateisystem (meist /) verwenden, beendet. Das kommt einem unvermittelten Ausschalten des Rechners schon sehr nahe. Darum gibt es, quasi als Sicherheitsgurt für solche Fälle, die Option --ismountpoint (-M), mit der alle Aktionen nur dann ausgeführt werden, wenn der angegebene Dateiname ein Mountpoint ist.

Außerdem kann ich mit der Option -w das Senden des Signals auf Prozesse einschränken, die eine Datei zum Schreiben geöffnet haben. Das ist interessant, wenn ich ein Dateisystem von read-write auf read-only umhängen will.

GDB der GNU Debugger

GDB ist ein mächtiges Werkzeug, das ich in extrem schwierigen Fällen einsetze. Vorzugsweise, wenn ich nachträglich die Ursache eines Programmabsturzes ermitteln will oder wenn ich einen Programmfehler vermute und finden will.

Um mit dem Debugger zu arbeiten benötige ich Zugriff auf die Quellen, aus denen das Programm übersetzt wurde3. Außerdem brauche ich die Symboltabellen des Programms damit der Debugger die Maschinenbefehle des Binärprogramms den Quellcodezeilen zuordnen kann. Diese gibt es, wenn beim Übersetzen des Programms mit dem Compiler gcc die Option -g angegeben wurde. Bei vielen Softwarepaketen kann ich die Symboltabellen über das entsprechende Paket mit der Endung -dbg, zum Beispiel avahi-dbg für avahi, installieren.

Um einen Prozess postmortal zu analysieren, muss ich das System anweisen, ein Corefile zu schreiben, das den Zustand des Prozesses beim Programmabsturz enthält. Dazu kann ich in der Bash mit dem Befehl ulimit -c $size die maximale Größe des Corefiles festlegen, die das Betriebssystem schreibt. Diese muss ich hoch genug wählen, damit das Corefile groß genug für den gewünschten Prozess werden kann.

Ich kann den GNU Debugger auf verschiedene Arten starten:

gdb $options $program [$core]
So starte ich, wenn ich einen Prozess postmortal analysieren will (mit $core) oder, wenn ich ein Programm nur beim Ablauf verfolgen will (ohne $core). Dabei ist $program der Name der Programmdatei und $core der Name des Corefiles.
gdb $options –args $program $arguments
Hier gebe ich dem Programm, dass ich beobachten will gleich die Kommandozeilenargumente beim Aufruf des GDB mit.
gdbtui $options
Das startet den GDB mit einer Textbenutzeroberfläche, bei der im oberen Teil der Quelltext angezeigt wird und im unteren Teil die Befehle und Ausgaben des GDB.

Von den Optionen, die GDB beim Aufruf mitgegeben werden können, sind die folgenden für die Fehlersuche relevant. Weitere bekomme ich aus der Handbuchseite, der GDB-Texinfo-Datei oder mit gdb -help.

-c $file | -core $file
Damit gebe ich das Corefile bei den Optionen an und brauche es nicht mehr nach dem Programmnamen anzugeben.
-e $file | -exec $file
Gibt das ausführbare Programm an.
-s $file | -symbols $file
Gibt die Datei mit den Symboltabellen an. Die Optionen -s und -e können auch zu -se zusammengefasst werden, wenn die Symboltabellen noch in der Binärprogrammdatei enthalten sind.
-help
Listet alle Optionen mit einer kurzen Erläuterung auf.

Nachdem ich GDB gestartet habe, steuere ich den Ablauf der Sitzung mit Textbefehlen. Alle diese Befehle kann ich soweit abkürzen, wie sie noch eindeutig sind. Die wichtigsten Befehle für die Fehlersuche sind:

break $function | break $file:$function
So ziemlich als erstes rufe ich in einer Debuggingsitzung break main auf, damit der Debugger an dieser Funktion anhält und ich anschließend das Programm in Ruhe analysieren kann. Vor einer Funktion kann ich, durch : getrennt die Datei angeben, falls der Debugger momentan eine andere geladen hat.
run | run $arglist
Damit starte ich das Programm im Debugger. Optional kann ich dem Prozess mit $arglist Kommandozeilenargumente mitgeben.
bt
Dieser Befehl zeigt den Programmstack an. Bei einer postmortalen Analyse eines Prozesses rufe ich diesen Befehl als erstes auf, um herauszubekommen, wo das Programm abgestürzt ist.
print $expr
Mit print lasse ich mir die Werte in den Variablen und verschiedene andere Daten ausgeben. Dabei kann $expr ein komplexer C-Ausdruck sein.
c
Mit c (continue) läuft das Programm weiter bis zum nächsten Haltepunkt oder bis zum Programmende.
next
Dieser Befehl arbeitet die nächste Zeile im Quelltext ab. Dabei werden Funktionsaufrufe ausgeführt und übersprungen.
step
Auch dieser Befehl arbeitet die nächste Zeile im Quelltext ab, der Debugger folgt hier allerdings Funktionsaufrufen in das Innere der Funktion.
list | list $function | list $file:$function
Zeigt die aktuelle Programmumgebung beziehungsweise die angegebene Funktion im Quelltext.
help $befehl
Gibt die Hilfe zu dem angegebenen Befehl aus.
quit
Beendet die Debuggersitzung.

GDB ist ein mächtiges Werkzeug für die Fehlersuche und sehr komplex in der Anwendung. Da die Bedienung nicht einfach ist und es auch einiger Vorkehrungen für den erfolgreichen Einsatz bedarf, setze ich es selten ein, quasi als Ultima Ratio. Trotzdem ist es sinnvoll, sich gelegentlich hinzusetzen und zum Test das eine oder andere Programm im Debugger zu beobachten und zu analysieren, damit es im Ernstfall einfacher von der Hand geht.

Installiere die Debug-Informationen (Paket mit Endung -dbg) für ein einfaches Programm wie ls oder kompiliere ein Programm selbst mit Debug-Informationen.

Verfolge anschließend den Ablauf des Programms mit gdb oder gdbtui.

hdparm

Mit hdparm kann ich die Schnittstelle zur Festplatte des Rechners beeinflussen. Ich verwende das Programm zum Feintuning der Festplattenzugriffe, aber auch zum Verifizieren und Beheben von Plattenfehlern. Es arbeitet zusammen mit der Kernelschnittstelle des SATA-, PATA- und des SAS-Subsystems. Auch bei manchen USB-Festplatten kann ich Parameter mit hdparm verändern. Für einige der Optionen benötige ich eine aktuelle Kernelversion.

Allgemein sieht der Aufruf von hdparm wie folgt aus:

hdparm [$optionen] [$geraet ...]

Mit manchen Optionen kann ich Parameter sowohl abfragen als auch setzen (get/set). Bei diesen Optionen gilt, dass sie ohne zusätzliches Argument den Parameter abfragen und mit Argument den Wert entsprechend setzen.

Rufe ich hdparm ganz ohne Parameter auf, verhält es sich, als hätte ich die Optionen -acdgkmur angegeben.

Nachfolgend beschreibe ich die, aus meiner Sicht, wichtigsten Optionen. Weitere Informationen gibt es, wie immer, in den Handbuchseiten.

-a
(get/set) Anzahl der Sektoren für das Vorauslesen (read-ahead) im Dateisystem. Damit verbessert sich die Performance beim sequentiellen Lesen großer Dateien.
-A
(get/set) Ein- oder Ausschalten der Vorauslesefunktion der Festplatte. Mit -A0 wird es ausgeschaltet, mit -A1 eingeschaltet.
-B
(get/set) Advanced Power Management (APM) Eigenschaften der Platte, soweit diese das unterstützt. Gültig sind Werte von 1, für die meiste Energieeinsparung, bis 254, für die höchste I/O-Performance, mit dem Wert 255 wird es ganz abgeschaltet. Werte von 1-127 erlauben einen Spin-Down der Festplatte, Werte von 128-254 erlauben das nicht.
-c
(get/set) 32-Bit-Support für (E)IDE ein- oder ausschalten. Mit 0 wird dieser ausgeschaltet, 1 schaltet ihn ein und 3 schaltet den 32-bit-Support mit speziellem Sync ein.

Es geht hierbei um den Transfer via PCI oder VLB zum Hostadapter. Das Kabel zur Festplatte hat immer 16 Bit.

–fibmap $dateiname
Liefert die Liste der Blockextents (Sektorbereiche), die die Datei auf der Platte belegt.

Damit kann ich mir die Fragmentierung einer Datei auf der Platte ansehen, oder die Blöcke für Fehlertests bestimmen.

Wenn ich diese Option verwende, muss sie die einzige sein.

-g
Zeigt die Laufwerksgeometrie (Zylinder, Köpfe, Sektoren), die Größe des Gerätes in Sektoren und den Startoffset von Beginn der Platte.
-i
Zeigt Identifizierungsinformationen, die die Kerneltreiber während des Systemstarts und der Konfiguration gesammelt haben.
-I
Zeigt die Identifizierungsinformationen, die das Laufwerk liefert.
-m
(get/set) Die Anzahl der Sektoren für multiple Sektor I/O. Mit dem Wert 0 wird das abgeschaltet. Die meisten IDE-Festplatten erlauben die Übertragung von mehreren Sektoren pro Interrupt. Damit läßt sich der System Overhead für Disk I/O um 30 bis 50 Prozent reduzieren. Es kann allerdings in einigen Fällen zu massiven Dateisystemfehlern führen.
–read-sector $sektornummer
Liest den angegebenen Sektor und schreibt den Inhalt in Hex-Darstellung zum Standardausgang. Die Sektornummer wird als Dezimalzahl angegeben. Hdparm führt einen Low-Level-Read für diesen Sektor aus. Damit dient diese Funktion als definitiver Test für schlechte Sektoren. Um den Sektor durch die Plattenelektronik ersetzen zu lassen, kann ich die Option --write-sector verwenden.
-t
Nimmt die Zeit von Lesezugriffen für Benchmarks und Vergleichsmessungen. Um brauchbare Werte zu erhalten, muss ich diese Funktion mindestens zweimal bei einem ansonsten inaktiven System, das heißt keine anderen aktiven Prozesse, und genügend freiem Hauptspeicher wiederholen. Diese Funktion zeigt, wie schnell Daten ohne den Overhead des Dateisystems gelesen werden können.
-T
Nimmt die Zeit von Cache-Read-Zugriffen. Auch diese Funktion sollte ich mindestens zweimal bei ansonsten inaktivem System wiederholen. Zeigt den Durchsatz von Prozessor, Cache und RAM.
–write-sector $sektornummer
Schreibt Nullen in den Sektor. Sehr gefährlich! Irre ich mich in der Sektornummer, werden möglicherweise vitale Informationen des Dateisystems überschrieben. Die Sektornummer wird als Dezimalzahl angegeben. Diese Funktion kann ich zum Anstoßen der automatischen Reparatur des Sektors durch die Festplatte verwenden. Vorher vergewissere ich mich mit der Option --read-sector, dass ich es wirklich mit einem defekten Sektor zu tun habe.
-W
(get/set) Zeigt und modifiziert das Write Caching des IDE/SATA Laufwerks. 1 bedeutet eingeschaltet.
-z
Zwingt den Kernel, die Partitionstabelle neu zu lesen.

In /etc/hdparm.conf kann ich die Default-Konfiguration für hdparm hinterlegen. Diese Datei ist gut kommentiert, so dass ich mir weitere Erläuterungen dazu spare.

lsof

Ein Werkzeug, dass in meinem Werkzeugkasten für die lokale Fehlersuche nicht fehlen darf, ist lsof. Das Programm zeigt Informationen zu Dateien, die von gerade laufenden Prozessen geöffnet wurden.

Ich habe dieses Programm erfolgreich beim Untersuchen von Mount-Problemen eingesetzt. Auch beim Aufspüren und Untersuchen von Sicherheitsproblemen leistet es wertvolle Dienste.

Außer für Linux gibt es das Programm auch für andere UNIX-Derivate, bei denen einige Optionen eine andere Bedeutung haben. Aus diesem Grund und weil ich hier nicht alle Optionen erläutern werde, ist ein Blick in die Handbuchseite unumgänglich.

Offene Dateien, die lsof auflistet, können

In einigen Aspekten überschneidet sich die Funktionialität von lsof mit der von netstat, welches ich an anderer Stelle beschreibe. Meist entscheide ich je nach vorliegendem Problem, zu welchem Programm ich greife.

Ich kann von lsof, statt einer einmaligen Ausgabe, automatisch in bestimmten Abständen neue Schnappschüsse der angeforderten Informationen erhalten und die Ausgabe so umformen, dass sie von einem Skript überwacht werden kann.

Rufe ich lsof ohne Optionen und Argumente auf, bekomme ich eine Liste aller Dateien, die alle laufenden Prozesse im Moment geöffnet haben. Bin ich nur an wenigen Dateien interessiert, gebe ich diese als Argumente auf der Kommandozeile an. Bin ich nur an bestimmten Aspekten oder an Dateien interessiert, die ich nicht genau kenne, so spezifiziere ich das mit Optionen.

Selektiere ich mit einer Option eine definierte Menge von Dateien, dann zeigt lsof nur die Dateien, die dieser Selektion genügen. Gebe ich mehrere Selektoren an, dann werden alle Dateien angezeigt, die irgendeinem dieser Selektoren entsprechen. Das heißt, die Menge der angezeigten Dateien entspricht der ODER-Verknüpfung der einzelnen Selektoren. Dazu gibt es folgende Ausnahme: wenn ich mit einer Option bestimmte Dateien deselektiere (zum Beispiel durch vorangestelltes ^ bei Auswahllisten), dann zeigt lsof diese Dateien auch nicht an, wenn sie durch einen anderen Selektor ausgewählt werden. Außerdem kann ich die Verknüpfung der Selektionskriterien mit der Option -a von ODER auf UND umstellen. Gebe ich mehrmals die gleiche Option mit verschiedenen Selektoren an, so werden diese vor der ODER- beziehungsweise UND-Verknüpfung zu einem Selektor zusammengefasst.

Wenn ich zum Beispiel nur an allen Internetsockets interessiert bin, die von Prozessen mit UID xyz geöffnet sind, dann schreibe ich:

lsof -a -i -u xyz

Optionen

Mit Option -c $name selektiere ich Prozesse, deren Name mit $name beginnt. Fängt $name selbst mit ^ an, dann werden genau diese Prozesse ignoriert. Beginnt und endet $name mit einem Schrägstrich (/), dann interpretiert lsof diesen als regulärer Ausdruck.

Mit der Option +d $s bekomme ich alle geöffneten Dateien direkt im Verzeichnis $s. Demgegenüber liefert +D $s auch die Dateien und Verzeichnisse in den Unterverzeichnissen von $s, die von Prozessen geöffnet sind. Beide Optionen kann ich mit -x kombinieren, damit lsof symbolischen Links folgt und Mountpoints überquert, was es ansonsten nicht machen würde.

Die Option -d $s erwartet eine Liste von Dateideskriptoren (diese stehen in der Ausgabe in Spalte FD), die ich einschließen, oder mit ^ ausschließen kann. Möchte ich das Arbeitsverzeichnis, aber nicht Standardeingabe, -ausgabe und -fehlerausgabe von Prozessen wissen, dann drücke ich das so aus:

lsof -d cwd,^0,^1,^2

Mit der Option -i [$m] bekomme ich Internetsockets und zwar speziell für TCP oder UDP angezeigt. Optional kann ich diese mit dem Muster $m genauer spezifizieren. Dazu gebe ich m in der folgenden Form

[46][$protocol][@$hostname|$hostaddr][:$service|$port]

an. Hierbei steht

4
für die Beschränkung auf IPv4
6
für die Beschränkung auf IPv6
$protocol
für den Protokollnamen TCP oder UDP
$hostname
für einen Internet-Hostnamen oder alternativ
$hostaddr
für eine numerische Adresse
$service
für einen Servicenamen aus /etc/services oder alternativ
$port
für die Portnummer

Demgegenüber kann ich mit -U UNIX-Domain-Sockets auswählen.

Die Option -n unterdrückt die Umwandlung von Netzadressen in Namen, -P die Umwandlung von Portnummern in Servicenamen und schließlich -l die Umwandlung von UID in Benutzernamen. Diese Optionen verwende ich, wenn ich mehr Klarheit haben will, oder wenn diese Umwandlung ihrerseits die Ausführung von lsof verzögert, weil DNS- oder NIS-Anfragen für die Auflösung notwendig sind.

Mit der Option -u $s lassen sich die Prozesse nach UID oder Benutzernamen auswählen, während ich mit -p $s die Prozesse direkt nach PID auswählen kann.

Starte ich lsof mit Option -r [$t], so liefert es die Informationen wiederholt in dem mit $t spezifizierten Zeitabstand (ohne Angabe 15 Sekunden).

Diese Option kann ich mit -F [$f] kombinieren um die Ausgabe für die einfachere Verarbeitung in einem Skript zu modifizieren. Dabei gebe ich mit dem optionalen Argument $f die Felder, die lsof ausgibt.

ltrace

Ltrace ist, ähnlich strace, ein Programm, mit dem ich einem Prozess bei der Arbeit zusehen kann. Im Gegensatz zu strace, welches nur die Kernel-Schnittstelle beobachtet, zeigt ltrace den Aufruf von Bibliotheksfunktionen.

Beim Start eines Programmes via ltrace läßt dieses den Prozess laufen, bis das Programm endet. Dabei fängt ltrace Aufrufe von Bibliotheksfunktionen durch den Prozess und Signale an den Prozess ab und zeigt sie auf STDERR an. Das macht strace auch, da ltrace aber Bibliotheksaufrufe abfängt, ist die Ausgabe viel feiner granuliert und umfangreicher.

Für eine komplette Liste der Optionen verweise ich auf die Handbuchseite. Die folgenden Optionen sind für die Fehlersuche mit ltrace interessant.

-S
Mit dieser Option zeigt ltrace zusätzlich zu den Bibliotheksaufrufen auch Systemaufrufe an der Kernelschnittstelle. Diesen stellt es in der Ausgabe SYS_ voran.
-L
Die Option ist nur zusammen mit -S sinnvoll, da sie die Ausgabe der Bibliotheksaufrufe unterdrückt. Mit beiden Optionen zusammen zeigt ltrace etwa das gleiche an wie strace, der auffälligste Unterschied ist das vorangestellte SYS_ bei ltrace.
-e $expr
Damit kann ich die Ereignisse und Funktionen einschränken, die ltrace anzeigen soll. Funktionen, die ich nicht sehen will, kennzeichne ich mit vorangestelltem !. So kann ich zum Beispiel mit ltrace -e malloc,free nachschauen, ob angeforderter Speicher auch wieder freigegeben wird. Das interessiert mich insbesondere bei lange laufenden Prozessen.
-f
Mit dieser Option beobachtet ltrace auch Kindprozesse.
-o $dateiname
Damit gibt ltrace, genau wie strace, die Ausgabe in die angegebene Datei anstatt zu STDERR aus.
-p $pid
Mit dieser Option kann ich, wie bei strace, einen bereits laufenden Prozess untersuchen.
-i
Mit dieser Option zeigt ltrace den Befehlszeiger zu jedem Funktionsaufruf.
-r
Damit fügt ltrace relative Zeitstempel in die Ausgabe, mit denen ich die Verzögerungen durch Timeouts genauer eingrenzen kann.
-T
Mit dieser Option zeigt ltrace die Zeit, die der Prozess für Funktionsaufrufe benötigt.
-l $dateiname
Diese Option erlaubt mir, die Beobachtung einzuschränken auf Funktionsaufrufe aus der angegebenen Bibliothek. Ich muss den kompletten Pfad zur Bibliothek angeben. Welche Bibliotheken ein Programm verwendet, bekomme ich mit ldd heraus. Wenn ich an mehreren Bibliotheken interessiert bin, kann ich diese Option mehrfach angeben.

netstat

Netstat ist ein Werkzeug, das mir sowohl bei der lokalen, als auch bei der Fehlersuche im Netzwerk hilft. Auf den Aspekt Netzwerkfehlersuche gehe ich im dritten Teil des Buches näher ein. Hier konzentriere ich mich auf die Fehlersuche bei lokalen Problemen.

Dafür verwende ich meist die Optionen --protocol=unix (alternativ: -A unix) oder --unix (-x) um mir die UNIX-Sockets ausgeben zu lassen.

Mit --program (-p) erhalte ich die PID und den Namen des Prozesses, der den Socket benutzt. Mit der PID kann ich dann zum Beispiel den Prozess mit strace oder ltrace näher betrachten. Für einige Informationen zu Prozessen anderer Benutzer benötige ich die Privilegien von root oder die Capability CAP_DAC_READ_SEARCH. Diese kann ich, wie in Kapitel 6 beschrieben, vergeben.

Normalerweise zeigt netstat nur aktive, das heißt verbundene Sockets an. Mit der Option --listening (-l) kann ich dagegen die Ausgabe auf Sockets einschränken, die auf eine Verbindung warten, oder mit --all (-a) alle ausgeben.

Mehr Informationen bekomme ich, wenn ich zusätzlich die Optionen --verbose (-v) oder --extend (-e) angebe.

Die Ausgabe von netstat kommt als Tabelle, deren Spalten die folgende Bedeutung haben:

RefCnt
Zeigt die Anzahl der Prozesse, die sich mit dem Socket verbunden haben.
Flags
geben weitere Informationen zum Zustand des Sockets:
ACC
der Socket wartet auf eine Verbindung
W
der Socket wartet auf Daten
N
der Socket hat im Moment nicht genug Platz zum Schreiben
Typ
kann stehen für
DGRAM
verbindungslose Sockets
STREAM
verbundene Sockets
RAW
ungefilterte Sockets
RDM
zuverlässig ausgelieferte Nachrichten (Reliable Delivered Messages)
State
kann für einen der folgenden Zustände des Sockets stehen:
Free
nicht benutzte Sockets
Listening
nicht verbundene Sockets
Connecting, Connected, Disconnected
die Phasen einer Socketverbindung
(empty)
für unverbundene Sockets
PID
enthält die Prozess-ID und den Namen des Prozesses
Path
zeigt den Pfad zum Socket an

Perf

Seit Jahren gibt es in den Prozessoren Performance-Counter, spezielle Zähler, die performance- und verhaltensrelevante Daten ermitteln, mit denen die Laufzeiteigenschaften von Programmen analysiert werden können.

Da die Mechanismen von CPU zu CPU unterschiedlich sind, gab es lange Zeit keine einheitliche Schnittstelle unter Linux, um diese Daten zu nutzen.

Mit perf gibt es nun ein Werkzeug, das eine einheitliche Schnittstelle für den Zugang zu diesen Daten bietet. Dieses Werkzeug besteht aus zwei Teilen: dem Kernelcode für den Zugriff auf die Daten und dem Programm zur Auswertung.

Da beide Teile eng gekoppelt sind, werden die Quellen des Programms perf zusammen mit dem Kernelcode geführt. Baue ich meinen Kernel selbst, brauche ich lediglich im Unterverzeichnis tools/perf/ den Befehl make aufrufen, um das für diesen Kernel passende Programm zu erhalten. In den verschiedenen Distributionen finde ich perf in eigenen Paketen, die von der Kernelversion abhängig sind, bei Debian oder Ubuntu beispielsweise in linux-tools.

Das Grundprinzip funktioniert so, dass von der CPU verwaltete Zählerregister auf bestimmte Messereignisse programmiert werden können. Bei Erreichen eines Maximalwertes wird ein Interrupt ausgelöst, der zum Aufruf eines Handlers führt, welcher den aktuellen Wert des Befehlszählers in einem Ringpuffer speichert. Das Analyseprogramm wertet den Ringpuffer aus und stellt den Bezug vom Befehlszählerinhalt zu den CPU-Befehlen an dieser Stelle her.

Dadurch ergibt sich ein geringer Overhead beim Beobachten der Programme und diese können auch innerhalb von Funktionen beobachtet werden. Allerdings kann es vorkommen, dass der ermittelte Wert des Befehlszählers auf Grund von Verzögerungen bis zum Aufruf des Handlers nicht exakt ist. Diesen Umstand darf ich bei der Analyse nicht aus den Augen lassen.

Neben Hardwarezählern stellt perf auch Softwarezähler aus dem Kernel für die Auswertung zur Verfügung. Damit kann ich global oder eingeschränkt auf eine Task oder eine cgroup messen.

Aufruf des Programmes

Ich kann das Programm perf auf eine der folgenden Arten verwenden:

$ perf stat [$optionen] $programm

Bei diesem Aufruf startet perf das Programm $programm, sammelt Performancedaten und gibt diese nach Beendigung des Programms aus.

# perf top [$optionen]

So gibt das Programm laufend aktualisierte Daten aus, ähnlich dem Programm top für Prozesse. Dafür benötige ich erweiterte Rechte. Ich kann das Programm als root aufrufen, oder über /proc/sys/kernel/perf_event_paranoid die Einstellungen für perf lockern.

$ perf record [$optionen] $programm

Dieser Aufruf ist ähnlich dem von perf stat, allerdings werden die Performancedaten nicht ausgegeben, sondern in der Datei perf.data gespeichert.

$ perf report [$optionen]

Zeigt die in perf.data gespeicherten Daten an.

$ perf list [$optionen]

Damit zeigt perf alle bekannten symbolischen Ereignistypen an.

Details finden sich, wie gewöhnlich, in den Handbuchseiten, die für die oben genannten fünf Aufrufe auch via

$ perf help $befehl

angesehen werden können.

Im Perf Wiki gibt es ein englischsprachiges Tutorial, das den Einsatz von perf anhand von Beispielläufen erläutert.

Perl

Zwischen den Spezialwerkzeugen für die Fehlersuche und der Shell als Kommandozentrale benötige ich hin und wieder ein Werkzeug, mit dem sich auch knifflige Probleme angehen lassen, die so vorher noch nicht untersucht wurden. Etwas, das sich so schnell wie die Shell programmieren läßt, aber ausdrucksstärker ist und auch sehr komplexe Probleme angehen kann. Für mich ist das Perl. Für andere vielleicht Python oder eine der anderen Skriptsprachen.

Die Programmiersprache Perl umfasst Konzepte von einfachen Werkzeugen wie sed oder awk bis hin zu anspruchsvollen Programmiersprachen wie C oder Lisp. Es gibt umfangreiche Fachliteratur sowohl offline als auch online sowie Communities, an die man sich bei Problemen wenden kann.

Was Perl aber heraushebt gegenüber vielen anderen Skriptsprachen ist CPAN, das Comprehensive Perl Archive Network, ein umfangreiches Reservoir an Softwaremodulen für fast alle erdenklichen Zwecke. Dieses macht es möglich, die meisten Skripts auf wenige Zeilen zu beschränken. Die besten und meist verwendeten Module schaffen es mit der Zeit in die Standarddistribution und stehen dann nach der Installation von Perl gleich zur Verfügung. In vielen Fällen muß ich Perl auch gar nicht zusätzlich installieren, weil es bereits Bestandteil des Systems ist.

Besonders hilfreich ist das Perl Kochbuch von Tom Christiansen und Nathan Torkington [CT2000]. In diesem Buch sind Lösungen für viele Probleme in Rezeptform aufbereitet und vor allen Dingen erläutert. Die Codebeispiele aus dem Kochbuch sind online verfügbar, den meisten Wert ziehe ich jedoch aus den Erläuterungen im Buch.

Der Zeilenumbruch kann Probleme bei Skripts bereiten, wenn man ein Skript ohne besondere Vorkehrungen von Windows nach UNIX kopiert.

Traditionell gibt es drei verschiedene Standards, den Zeilenumbruch bei ASCII-Dateien zu kodieren:

Da der Text sich scheinbar nicht unterscheidet und viele Programme, wie zum Beispiel less die Zeilen normal anzeigen, auch wenn der Zeilenumbruch nicht zum System passt, merkt man mitunter erst, wenn man das Skript ausführen will, dass es die falschen Zeilenumbrüche verwendet:

$ bash: ./test.pl: /usr/bin/perl^M: bad interpreter:\
 No such file or directory
$ od -c test.pl|head -2
0000  #  !  /  u  s  r  /  b  i  n  /  p  e  r  l \r
0020 \n  #     v  i  m  :     s  e  t     t  s  =  4

In diesem Fall muss ich den Zeilenumbruch der Skript-Datei ändern. Das kann ich mit dem Programm dos2unix erledigen, oder mit dem folgenden Perl-Einzeiler:

$ perl -pi.bak -e 's/\r\n$/\n/g' test.pl

Damit kopiert Perl die Original-Datei mit der Endung .bak und ersetzt das Skript durch eine Version mit passenden Zeilenumbrüchen für Linux/Unix.

Syslog auswerten

Es kommt vor, dass ich die Zeilen in den Systemprotokollen miteinander verknüpfen muss, um eine ganz spezielle Auswertung zu bekommen. Finde ich kein geeignetes Programm, beginne ich mit folgendem Fragment, dass ich dann für die Lösung des konkreten Problems ausbaue:

read-syslog.pl


 1 #!/usr/bin/perl
 2 use Time::Local;
 3 my $wanted = 'CRON';
 4 my $syslogline = qr/^
 5   (\S{3})\s{1,2}           # $1 month name
 6   (\d{1,2})\s              # $2 day of month
 7   (\d\d):(\d\d):(\d\d)\s   # $3,$4,$5 hour, min, sec
 8   (\S+)\s                  # $6 host
 9   ([^[:]+)                 # $7 process name
10   (?:\[(\d+)\])?           # $8 process id
11   :\s
12   (.+)                     # $9 log line
13   $/x;
14 my ($sec,$min,$hour,$mday,$mon,
15   $year,$wday,$yday,$dst) = localtime time;
16 while (<>) {
17   if (/$syslogline/) {
18     my $time = utctime($1, $2, $3, $4, $5);
19     if ($7 eq $wanted) {
20       process_line($time,$6,$7,$8,$9);
21     }
22   }
23 }
24 sub utctime {
25   my ($mon,$dom,$hour,$min,$sec) = @_;
26   my %mons = (
27     Jan => 0, Feb => 1, Mar => 2, Apr => 3,
28     May => 4, Jun => 5, Jul => 6, Aug => 7,
29     Sep => 8, Oct => 9, Nov => 10, Dec => 11,
30   );
31   my $mod = $mons{$mon};
32   return timegm($sec,$min,$hour,$dom,$mod,$year);
33 }
34 sub process_line {
35   # print join(" ",@_,"\n");
36   # ... your code here ...
37 }

In Zeile 3 trage ich den Prozessnamen ein, an dessen Logzeilen ich interessiert bin. Sind es mehrere Prozesse, muss ich auch Zeile 19 anpassen.

Die Zeilen 4-13 definieren einen regulären Ausdruck mit dessen Hilfe ich die Protokollzeilen in ihre Bestandteile aufspalte.

Die Zeitfelder fasse ich in der Funktion utctime() zu einer Zahl zusammen. Als Jahr nimmt das Skript das aktuelle an, dieses wird in den Zeilen 14, 15 ermittelt.

In Funktion process_line() kann ich die gesamte Auswertelogik für die Protokollzeilen schreiben und dabei auf die bereits separierten allgemeinen Felder und die eigentliche Nachricht zurückgreifen.

Wenn ich mehrere Protokollzeilen miteinander in Beziehung setzen will, verwende ich eine globale Datenstruktur und gebe das Ergebnis entweder am Ende, nach der while (<>) Schleife oder zwischendurch in der Funktion process_line() aus.

Shell

Die Shell ist für mich ein wichtiges Hilfsmittel bei der Fehlersuche. Dies in zweierlei Hinsicht: zum einen starte ich in einer interaktiven Shell die Kommandos, mit denen ich den Fehler eingrenzen will, zum anderen verwende ich die Shell für simple, schnell zusammengeschriebene Programme, die mich bei der Fehlersuche unterstützen.

Bei der Arbeit auf der Kommandozeile bevorzuge ich eine Shell mit History-Funktion und Kommandozeilenergänzung (command line completion). Die History-Funktion benutze ich, um bereits ausgeführte Befehle wieder hervorzuholen, gegebenenfalls geringfügig zu ändern und noch einmal auszuführen. Die Kommandozeilenergänzung beschleunigt den Zusammenbau von neuen Befehlen, indem die Shell, meist nach Eingabe von <TAB>, die Zeile komplettiert oder die nächsten Argumente vorschlägt, wenn mehrere Vervollständigungen möglich sind. Das halte ich für unverzichtbar, um den Gedankenflug nicht abreißen zu lassen. Ich bin an die Bash gewöhnt, aber andere Shells können das ebensogut.

Für Shell-Scripts bevorzuge ich als kleinsten gemeinsamen Nenner die POSIX Shell (/bin/sh). Auf manchen Systemen ist das nur ein Link auf die Bash. Das ist kein Problem, weil diese in der Lage ist, POSIX Shell-Skripts auszuführen. Da aber nicht jede POSIX-kompatible Shell in der Lage ist, Bash-Erweiterungen zu verstehen, beschränke ich mich auf den kleinsten gemeinsamen Nenner, um auf der sicheren Seite zu sein, wenn das Skript in einer anderen Umgebung läuft.

Ich will hier keine komplette Einführung in die Programmierung mit der POSIX Shell geben, sondern verweise stattdessen auf die Handbuchseiten und die Beschreibung im Standard [POSIX.1-2008]. Stattdessen stelle ich ein Skript vor, das ich bereits mehrfach für die Fehlersuche verwendet habe

Strace Invocator

Bei einem schwierigen Problem greife ich oft auf strace zurück, um den Prozess bei der Arbeit zu beobachten. Wenn dieser Prozess jedoch nicht von Kommandozeile, sondern von einem anderen Prozess gestartet wird, verwende ich folgenden Trick: Ich benenne das Programm um, indem ich an den Namen die Endung .orig anhänge. Unter dem ursprünglichen Programmnamen platziere ich einen Link auf dieses Skript:

strace-invocator


 1 #!/bin/sh
 2 progname=$(basename $0)
 3 origin=$0.orig
 4 die () {
 5   ev=$1
 6   shift
 7   echo $* 1>&2
 8   exit $ev
 9 }
10 [ -x $origin ] || die 2 "Can't execute $origin"
11 tmpdir=$(mktemp -d /tmp/$progname.XXXXX)
12 date > $tmpdir/invocation
13 echo "#----- args -----" >> $tmpdir/invocation
14 for arg in "$@"; do
15   echo $arg >> $tmpdir/invocation;
16 done
17 echo "#----- id -----" >> $tmpdir/invocation
18 id >> $tmpdir/invocation
19 echo "#----- env -----" >> $tmpdir/invocation
20 env >> $tmpdir/invocation
21 strace -f -o $tmpdir/strace.out $origin "$@"

In Zeile 2 bestimme ich den Namen des aufgerufenen Programms und in Zeile 3 den Namen des eigentlichen Programms.

In Zeile 4 bis 9 definiere ich eine Funktion, mit der ich das Skript mit Fehlermeldung und -code beenden kann.

In Zeile 10 teste ich, ob das Originalprogramm da und ausführbar ist und breche andernfalls ab. Dazu nutze ich die oben definierte Funktion.

In Zeile 11 lege ich ein temporäres Verzeichnis mit eindeutigem Namen an. Damit kann ich mich bei der Auswertung in aller Ruhe auf jeden einzelnen Aufruf des Programms konzentrieren.

In den Zeilen 12 bis 20 halte ich verschiedene Informationen zum Aufruf fest. Und zwar die aktuelle Zeit, die übergebenen Argumente, die Benutzer-Id, unter der der Prozess läuft und die Umgebungsvariablen.

In Zeile 21 schließlich rufe ich via strace das Originalprogramm mit allen Argumenten auf. Diesen Aufruf kann ich noch modifizieren, wenn ich an der Standardeingabe für das Programm interessiert bin:

tee $tmpdir/stdin \
  | strace -f -o $tmpdir/strace.out $origin "$@"

Oder, wenn ich sowohl an der Standardeingabe als auch an der Standardausgabe interessiert bin:

tee $tmpdir/stdin \
| strace -f -o $tmpdir/strace.out $origin "$@" \
| tee $tmpdir/stdout

Fehler in Shell-Skripts suchen

Manchmal habe ich ein Problem in einem Shell-Skript selbst. Dann hilft es, die Shell mit der Option -x zu starten, um zu sehen, wie sie das Skript abarbeitet.

Als Beispiel nehme ich ein Skript, das mir ausgibt, in welchem Verzeichnis ich mich gerade befinde und zu welchem Projekt dieses gehört:

 1 #!/bin/sh
 2 HERE=$PWD
 3 echo "($HERE)"
 4 while test "/" != "$HERE"; do
 5   if [ -f "$HERE/.project" ]; then
 6 	echo "===" $(cat "$HERE/.project") "==="
 7 	break
 8   fi
 9   HERE=$(dirname $HERE)
10 done

Die Ausgabe des Skripts sieht beispielsweise so aus:

$ where
(/home/mathias/A/2012/12/07/t1)
=== GDB Tests ===

Mit Shell-Debugging dagegen so:

$ sh -x ~/bin/where
+ PROJECT=.project
+ HERE=/home/mathias/A/2012/12/07/t1
+ echo (/home/mathias/A/2012/12/07/t1)
(/home/mathias/A/2012/12/07/t1)
+ test / != /home/mathias/A/2012/12/07/t1
+ [ -f /home/mathias/A/2012/12/07/t1/.project ]
+ dirname /home/mathias/A/2012/12/07/t1
+ HERE=/home/mathias/A/2012/12/07
+ test / != /home/mathias/A/2012/12/07
+ [ -f /home/mathias/A/2012/12/07/.project ]
+ cat /home/mathias/A/2012/12/07/.project
+ echo === GDB Tests ===
=== GDB Tests ===
+ break

Damit sehe ich genau, was beim Abarbeiten des Skripts passiert. Alle Variablenzuweisungen mit den zugehörigen Werten sowie die Befehlsaufrufe mit ihren Argumenten stehen auf je einer Zeile, die mit '+ ' eingeleitet wird.

strace

Strace setze ich ein, wenn mir das Verhalten eines Programmes unklar ist. Wenn ich Probleme mit Zugriffsrechten vermute, aber keinen Anhaltspunkt in den Fehlermeldungen oder Systemprotokollen finde. Wenn ich mit einem Programm noch wenig Erfahrung habe, keine Hilfe im Internet finde, aber das Problem trotzdem so schnell wie möglich beheben will. In [Koenig2012] führt Harald König sehr gut in die Arbeit mit strace ein.

Strace hilft mir, wenn ich beobachten will, wie ein Programm mit seiner Umgebung interagiert. Es setzt an der Kernelschnittstelle an und protokolliert alle Systemaufrufe mit den Parametern und Ergebnissen. Diese stellt es im Protokoll ähnlich den Systemaufrufen in der Programmiersprache C dar, mit folgendem Unterschied: das Ergebnis steht hinter einem Gleichheitszeichen am Ende der Zeile, die Systemzeit steht vor dem Systemaufruf. Wenn ich mehrere Prozesse beobachte und in dieselbe Datei protokolliere, steht zusätzlich die Prozess-ID am Anfang der Zeile.

Um die Ausgabe von strace interpretieren zu können, ist es hilfreich, die Sektion 2 der Handbuchseiten installiert zu haben. Diese befinden sich bei Debian-basierten Systemen im Paket manpages-dev.

Da strace Systemaufrufe der beobachteten Prozesse protokolliert, verlangsamt sich deren Ablauf, was zu zusätzlichen Problemen bei der Fehlersuche führen kann. Dessen muss ich mir beim Einsatz von strace immer bewusst sein.

Ich setze strace bei der Fehlersuche meist auf eine der folgenden Weisen ein.

Bei Programmen, die ich von der Kommandozeile aus aufrufe, starte ich das betreffende Programm wie gewohnt, allerdings setze ich an den Anfang strace mit einigen Optionen. So wird aus

$ make xyz

dann

$ strace -f -o make.strace make xyz

Dabei bedeuten die Optionen

-f
Strace soll auch von make gestartete Programme beobachten.
-o make.strace
Die Ausgabe geht in die Datei make.strace.

Bei Problemen mit bereits gestarteten Prozessen verwende ich die Option -p $PID um mich mit dem Prozess mit dieser PID zu verbinden. Alle weiteren Optionen bleiben wie gehabt, auf die Angabe des Programmnamens und der Programmparameter kann ich verzichten. Prozesse, die bereits vorher von dem untersuchten Prozess gestartet wurden, beobachtet strace nicht, wohl aber neu gestartete Prozesse, wenn ich die Option -f angebe.

Schwieriger ist es, wenn ein Programm ein zweites aufruft, dieses ein drittes und so weiter. Wenn ich nur an einem der Programme interessiert bin und nicht genau weiß, von welchem Prozess beziehungsweise Programm dieses gestartet wird, helfe ich mir mit einem Trick. Ich benenne das Programm um und ersetze es durch ein Skript, welches alle mich interessierenden Werte protokolliert und schließlich das Originalprogramm via strace aufruft. Dieses Skript ist im Abschnitt zur Shell näher beschrieben. Natürlich darf ich am Ende nicht vergessen, das Skript wieder durch das Originalprogramm zu ersetzen.

Mache Dich mit strace vertraut, indem Du ein einfaches Program wie ls mit strace beobachtest:

strace -o ls.strace ls -l

Studiere die einzelnen Systemaufrufe mit Hilfe der Handbuchseiten und versuche zu verstehen, was beim Ablauf des Programmes passiert.

sysstat

Das Paket sysstat enthält Accounting-Programme, mit denen ich den Ressourcenbedarf des Systems erfassen kann.

Üblicherweise startet cron aller 10 Minuten ein Skript, das die Systemstatistiken sichert. Diese Statistiken kann ich mit verschiedenen Auswertewerkzeugen auslesen.

Alternativ können diese Werkzeuge selbst periodisch die Performancedaten sammeln und als aktuelle Schnappschüsse ausgeben.

Zur Auswertung mit sysstat stehen die folgenden Werkzeuge zur Verfügung:

cifsiostat
liefert CIFS Statistiken
iostat
liefert CPU- und I/O-Statistiken für Geräte und Partitionen
mpstat
liefert (multi-)prozessorbezogene Statistiken
nfsiostat
liefert NFS-bezogene I/O-Statistiken
pidstat
liefert Statistiken über Linux-Tasks (-Prozesse)
sar, sadf
sar sammelt, berichtet und sichert Informationen zu Systemaktivitäten, sadf gibt die von sar gesammelten Daten in verschiedenen Formaten aus.

Die Auswertung der Statistikdaten mit sar ist in [Loukides1996] beschrieben. Da sich die Software seitdem weiterentwickelt hat, ist ein Blick in die Handbuchseiten unerlässlich.

Für die grafische Auswertung gibt es verschiedene Programme. Ein Programm, sargraph, wird als Beispiel mit sysstat verteilt. Dieses wertet die XML-Ausgabe von sadf aus und übergibt sie an gnuplot zur Darstellung.

vmstat

Das Programm vmstat verwende ich, um bei lokalen Performanceengpässen einen Überblick über das Gesamtsystem zu bekommen. Es liefert mir statistische Informationen über Prozesse, Speicher, I/O, Platten- und CPU-Aktivitäten. Durch die kompakte Darstellung kann ich im Wiederholungsmodus ein Gefühl für die Aktivität des Gesamtsystems bekommen. Wenn ein Performanceengpass auftritt, sehe ich sofort, ob es ein allgemeines Ressourcenproblem ist - zuwenig Speicher, zu schwache CPU, zu langsame Platte - oder ob ich mich eher mit dem betreffenden Programm beschäftigen muss.

Für die meisten Optionen sind keine speziellen Privilegien erforderlich. Ich kann mit vmstat als normaler Benutzer nachsehen, wie es dem System geht.

Im Wiederholungsmodus zeigt das Programm in der ersten Zeile für alle Werte den Durchschnitt seit Systemstart an und in den folgenden Zeilen die Werte für die betreffende Abtastperiode. Diesen Modus verwende ich am häufigsten:

vmstat [-a] [-n] [$periode [$anzahl]]

Lasse ich die beiden Zahlen für $periode und $anzahl weg, dann bekomme ich nur die Durchschnittswerte seit Systemstart angezeigt. Das brauche ich eigentlich nur, wenn ich das System sehr gut kenne oder mehrere ähnliche Systeme vergleichen will.

Gebe ich eine Zahl für $periode an, dann verwendet vmstat diese als Länge der Periode in Sekunden mit der es kontinuierlich die Werte der letzten Periode ausgibt. Auch hier steht in der ersten Zeile der Durchschnitt seit Systemstart, so dass ich gleich ab der zweiten Zeile sehen kann, wie das System im Moment beschäftigt ist.

Gebe ich eine Zahl für $anzahl an, beendet sich vmstat nach soviel Perioden, wie angegeben.

Die Ausgabe von vmstat in diesem Modus bedeutet folgendes:

Procs
Unter r steht die Anzahl der Prozesse, die laufen können und unter b die Anzahl der Prozesse in uninteruptible sleep, das sind Prozesse, die im Kernelcode auf I/O warten.
Memory
Hier habe ich unter swpd die Menge des verwendeten virtuellen Speichers, also des ausgelagerten Hauptspeichers. Unter free finde ich den unbenutzten Speicher, unter buff Speicher, den der Kernel für Datei- und Socketpuffer verwendet. Den unter cache aufgeführte Speicher verwendet er für zwischengespeicherte Daten von Blockdevices.

Habe ich die Option -a angegeben, zeigt vmstat statt buffer/cache aktiven und inaktiven Speicher an.

IO
Unter der Spalte bi finde ich die Anzahl der von Blockdevices gelesenen Blöcke (blocks in) und unter bo die geschriebenen (blocks out).
System
Hier finde ich unter in die Anzahl der Interrupts pro Sekunde und unter cs die Anzahl der Kontextwechsel pro Sekunde.
CPU
Diese Spalten zeigen prozentual, wie die CPU ihre Zeit verbringt. Unter us steht der Anteil, den die CPU mit Code in Benutzerprogrammen verbringt. Unter sy steht die Zeit für das Abarbeiten von Systemaufrufen und unter id die untätige (idle) Zeit. Bis zum Kernel 2.5.41 zählte die Zeit, in der die CPU auf I/O wartete hierzu, ab dann gab es dafür die Spalte wa.

Im Wiederholungsmodus kann ich mit Option -a die Anzeige des Speichers von buffer/cache auf inactive/active umschalten und mit -n die wiederholte Anzeige der Kopfzeilen abschalten. Letztere sind insbesondere bei länger laufender Ausgabe praktisch, wenn ich nicht genau im Kopf habe, welche Spalte was anzeigt. Daher verwende ich -n so gut wie nie.

Mit der Option -m, die Superuserprivilegien erfordert, zeigt vmstat Informationen zum Slab Allocator an. Das ist ein Verfahren zur Verwaltung des Arbeitsspeichers. Weitere Informationen dazu finden sich in [Bonwick1994].

Mit Option -d zeigt vmstat einmalig Diskstatistiken für alle Disk Devices an, das können auch RAM-Disks und Loop Devices sein. Dabei bedeuten:

Reads/Writes
für Lese-/Schreib-Zugriffe:
total
alle erfolgreichen Zugriffe
merged
gruppierte Zugriffe, die in einem I/O-Vorgang resultieren
sectors
erfolgreich gelesene/geschriebene Sektoren
ms
Anzahl der lesend/schreibend verbrachten Millisekunden
I/O
Unter cur die gerade laufende I/O und unter s die Sekunden, die das System mit I/O verbracht hat.

Mit Option -D zeigt vmstat einmalig zusammengefasste Statistiken zu allen Disks.

Und mit -p $partition schließlich zeigt vmstat einmalig Statistiken für diese Partition. Hierbei bedeuten in der Ausgabe:

reads
die Gesamtzahl der Lesezugriffe auf diese Partition
read sectors
die Gesamtzahl der gelesenen Sektoren dieser Partition
writes
die Gesamtzahl der Schreibzugriffe auf diese Partition
requested writes
die Gesamtzahl der Schreibanforderungen für diese Partition