Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 27 Der Provider io

Der Provider io stellt Prüfpunkte für die Festplatten-E/A zur Verfügung. Mithilfe des Providers iolässt sich einem mit E/A-Überwachungstools wie beispielsweise iostat(1M) beobachteten Verhalten schnell auf den Grund gehen. So können Sie mit dem Provider io die Ein- und Ausgabe beispielsweise nach Gerät, nach E/A-Typ, nach E/A-Größe, nach Prozess, Anwendungsnamen, Dateinamen oder nach Dateiversatz untersuchen.

Prüfpunkte

Die io-Prüfpunkte sind in Tabelle 27–1 beschrieben.

Tabelle 27–1 io-Prüfpunkte

Prüfpunkt 

Beschreibung 

start

Prüfpunkt, der ausgelöst wird, kurz bevor eine E/A-Anforderung an ein Peripheriegerät oder einen NFS-Server ausgegeben wird. Auf die Struktur bufinfo_t der E/A-Anforderung zeigt args[0]. Auf devinfo_t des Geräts, an das die E/A-Anforderung gestellt wird, zeigt args[1]. Auf die Struktur fileinfo_t der Datei, die mit der E/A-Anforderung übereinstimmt, zeigt args[2]. Beachten Sie, dass die Verfügbarkeit von Dateiinformationen davon abhängig ist, welches Dateisystem die E/A-Anforderung stellt. Weitere Informationen finden Sie unter fileinfo_t.

done

Prüfpunkt, der nach der Erfüllung einer E/A-Anforderung ausgelöst wird. Auf die Struktur bufinfo_t der E/A-Anforderung zeigt args[0]. Der Prüfpunkt done wird nach Abschluss der E/A ausgelöst, aber noch bevor im Puffer die Abschlussverarbeitung durchgeführt wird. B_DONE ist also zum Zeitpunkt der Auslösung des Prüfpunkts done nicht in b_flags gesetzt. Auf devinfo_t des Geräts, an das die E/A-Anforderung gesendet wurde, zeigt args[1]. Auf die Struktur fileinfo_t der Datei, die mit der E/A-Anforderung übereinstimmt, zeigt args[2].

wait-start

Prüfpunkt, der ausgelöst wird, unmittelbar bevor ein Thread beginnt, auf die Durchführung einer gegebenen E/A-Anforderung zu warten. args[0] zeigt auf die Struktur buf(9S) der E/A-Anforderung, auf die der Thread wartet. Auf devinfo_t des Geräts, an das die E/A-Anforderung gesendet wurde, zeigt args[1] . Auf die Struktur fileinfo_t der Datei, die mit der E/A-Anforderung übereinstimmt, zeigt args[2]. Einige Zeit, nachdem der Prüfpunkt wait-start ausgelöst wurde, wird der Prüfpunkt wait-done in demselben Thread ausgelöst.

wait-done

Prüfpunkt, der ausgelöst wird, wenn ein Thread aufhört, auf die Durchführung einer E/A-Anforderung zu warten. Auf die Struktur bufinfo_t, die der E/A-Anforderung entspricht, auf die der Thread wartet, zeigt args[0]. Auf devinfo_t des Geräts, an das die E/A-Anforderung gesendet wurde, zeigt args[1]. Auf die Struktur fileinfo_t der Datei, die mit der E/A-Anforderung übereinstimmt, zeigt args[2]. Der Prüfpunkt wait-done wird erst nach der Auslösung des Prüfpunkts wait-start in demselben Thread ausgelöst.

Beachten Sie, dass die io-Prüfpunkte für sämtliche E/A-Anforderungen an Peripheriegeräte und sämtliche Lese- und Schreibzugriffsanforderungen für Dateien an einen NFS-Server ausgelöst werden. Anforderungen von Metadaten von einem NFS-Server lösen io-Prüfpunkte aufgrund einer readdir(3C)-Anforderung beispielsweise nicht aus.

Argumente

Die Argumenttypen für io-Prüfpunkte sind in Tabelle 27–2 aufgeführt. In Tabelle 27–1 finden Sie eine Beschreibung der Argumente.

Tabelle 27–2 Argumente für io-Prüfpunkte

Prüfpunkt 

args[0]

args[1]

args[2]

start

struct buf *

devinfo_t *

fileinfo_t *

done

struct buf *

devinfo_t *

fileinfo_t *

wait-start

struct buf *

devinfo_t *

fileinfo_t *

wait-done

struct buf *

devinfo_t *

fileinfo_t *

Jeder io-Prüfpunkt besitzt Argumente in Form eines Zeigers auf eine buf(9S)-Struktur, eines Zeigers auf eine devinfo_t- und eines Zeigers auf eine fileinfo_t-Struktur. Dieser Abschnitt befasst sich ausführlich mit diesen Strukturen.

Die Struktur bufinfo_t

Die Struktur bufinfo_t ist die eine E/A-Anforderung beschreibende Abstraktion. Auf den Puffer für eine E/A-Anforderung wird mit args[0] in den Prüfpunkten start, done, wait-start und wait-done gezeigt. Die Definition der Struktur bufinfo_t lautet:

typedef struct bufinfo {
	int b_flags;                    /* flags */
	size_t b_bcount;                /* number of bytes */
	caddr_t b_addr;                 /* buffer address */
	uint64_t b_blkno;               /* expanded block # on device */
	uint64_t b_lblkno;              /* block # on device */
	size_t b_resid;                 /* # of bytes not transferred */
	size_t b_bufsize;               /* size of allocated buffer */
	caddr_t b_iodone;               /* I/O completion routine */
	dev_t b_edev;                   /* extended device */
 } bufinfo_t;

Die Komponente b_flags gibt den Status des E/A-Puffers an und besteht aus einem Bitwise- oder anderen Statuswerten. Tabelle 27–3 zeigt die gültigen Statuswerte.

Tabelle 27–3 Werte für b_flags

B_DONE

Zeigt an, dass die Datenübertragung abgeschlossen ist. 

B_ERROR

Deutet auf einen E/A-Übertragungsfehler hin. Wird in Verbindung mit dem Feld b_error gesetzt.

B_PAGEIO

Gibt an, dass der Puffer für eine per Paging behandelte E/A-Anforderung verwendet wird. In der Beschreibung des Felds b_addr finden Sie weitere Informationen.

B_PHYS

Gibt an, dass der Puffer für eine physische (direkte) E/A an einen Benutzerdatenbereich verwendet wird. 

B_READ

Gibt an, dass die Daten aus dem Peripheriegerät in den Hauptspeicher eingelesen werden müssen. 

B_WRITE

Gibt an, dass die Daten aus dem Hauptspeicher an das Peripheriegerät übertragen werden müssen. 

B_ASYNC

Die E/A-Anforderung ist asynchron. Auf sie wird nicht gewartet. Die Prüfpunkte wait-start und wait-done werden für asynchrone E/A-Anforderungen nicht ausgelöst. Beachten Sie, dass B_ASYNC bei manchen als asynchron vorgesehenen E/A nicht gesetzt ist: Das Subsystem für asynchrone E/A kann die asynchrone Anforderung unter Umständen durch einen separaten Arbeiter-Thread implementieren, der eine synchrone E/A-Operation durchführt.

Das Feld b_bcount gibt die Anzahl der im Rahmen der E/A-Anforderung zu übertragenden Byte an.

Das Feld b_addr ist die virtuelle Adresse der E/A-Anforderung, sofern B_PAGEIO nicht gesetzt ist. Bei der Adresse handelt es sich um eine virtuelle Kernel-Adresse, sofern B_PHYS nicht gesetzt ist. In diesem Fall handelt es sich um eine virtuelle Benutzer-Adresse. Wenn B_PAGEIO gesetzt ist, enthält das Feld b_addr private Kerneldaten. Es kann entweder genau eines der Flags B_PHYS und B_PAGEIO oder aber keines gesetzt sein.

Das Feld b_lblkno gibt an, auf welchen logischen Block auf dem Gerät zugegriffen werden muss. Die Zuordnung zwischen einem logischen und einem physischen Block (z. B. Zylinder oder Spur) ist durch das Gerät definiert.

Das Feld b_resid ist auf die Anzahl der aufgrund eines Fehlers nicht übertragenen Byte gesetzt.

Das Feld b_bufsize enthält die Größe des zugewiesenen Puffers.

Das Feld b_iodone gibt eine bestimmte Routine im Kernel an, die aufgerufen wird, wenn die E/A abgeschlossen ist.

Das Feld b_error kann einen im Fall eines E/A-Fehlers vom Treiber gelieferten Fehlercode enthalten. b_error wird in Verbindung mit dem B_ERROR-Bit in der Komponente b_flags gesetzt.

Das Feld b_edev enthält die Geräteklasse und Unternummer des Geräts, auf das zugegriffen wird. Die Verbraucher können die Geräteklasse und Unternummer mithilfe der D-Subroutinen getmajor() und getminor() aus dem Feld b_edev extrahieren.

devinfo_t

Die Struktur devinfo_t liefert Informationen über Geräte. Auf die Struktur devinfo_t, die dem Zielgerät einer E/A entspricht, wird mit args[1] in den Prüfpunkten start, done, wait-start und wait-done gezeigt. Die Komponenten von devinfo_t lauten:

typedef struct devinfo {
	int dev_major;                  /* major number */
	int dev_minor;                  /* minor number */
	int dev_instance;               /* instance number */
	string dev_name;                /* name of device */
	string dev_statname;            /* name of device + instance/minor */
	string dev_pathname;            /* pathname of device */
} devinfo_t;

Das Feld dev_major stellt die Geräteklasse des Geräts dar. Weitere Informationen finden Sie unter getmajor(9F).

Das Feld dev_minor stellt die Unternummer des Geräts dar. Weitere Informationen finden Sie unter getminor(9F).

Das Feld dev_instance gibt die Instanznummer des Geräts an. Die Instanz eines Geräts ist nicht mit der Unternummer identisch. Die Unternummer ist eine vom Gerätetreiber verwaltete Abstraktion. Bei der Instanznummer handelt es sich um eine Eigenschaft des Geräteknotens. Mit prtconf(1M) können die Instanznummern von Geräteknoten angezeigt werden.

Das Feld dev_name stellt den Namen des Gerätetreibers dar, der das Gerät verwaltet. Mit der Option - Dfür prtconf(1M) lassen sich die Gerätetreibernamen anzeigen.

Das Feld dev_statnamegibt den Namen des Geräts an, wie er von iostat(1M) gemeldet wird. Dieser Name entspricht auch dem Namen einer von kstat(1M) ausgegebenen Kernelstatistik. Das Feld wird bereitgestellt, damit sich abweichende iostat- oder kstat-Ausgaben schnell mit der tatsächlichen E/A-Aktivität in Verbindung bringen lassen.

Das Feld dev_pathname gibt den vollständigen Pfad des Geräts an. Dieser Pfad kann prtconf(1M) als Argument übergeben werden, wenn ausführliche Geräteinformationen gewünscht sind. Die Komponenten des mit dev_pathname angegebenen Pfads geben den Geräteknoten, die Instanznummer und den Unterknoten wieder. Der Statistikname enthält jedoch nicht unbedingt alle drei Elemente. Bei einigen Geräten besteht der Statistikname aus dem Gerätenamen und der Instanznummer, bei anderen aus dem Gerätenamen und der Nummer des Unterknotens. Das bedeutet, dass zwei Geräte mit demselben dev_statname-Wert durchaus einen unterschiedlichen dev_pathname-Wert aufweisen können .

fileinfo_t

Die Struktur fileinfo_t liefert Informationen über Dateien. Auf die Datei, auf die sich eine E/A bezieht, wird mit args[2] in den Prüfpunkten start, done, wait-start und wait-done gezeigt. Ob Dateiinformationen vorliegen, hängt davon ab, ob das Dateisystem diese Informationen beim Dispatchen von E/A-Anforderungen bereitstellt. Einige Dateisysteme, insbesondere von anderen Herstellern, geben diese Informationen möglicherweise nicht an. Außerdem können E/A-Anforderungen aus Dateisystemen stammen, für die keine Dateiinformationen existieren. E/A-Operationen mit Dateisystem-Metadaten werden zum Beispiel mit keiner einzelnen Datei in Verbindung gebracht. Schließlich können einige hoch optimierte Dateisysteme E/A aus separaten Dateien in einer einzigen E/A-Anforderung zusammenfassen. In diesem Fall macht·das Dateisystem Dateiinformationen entweder für die Datei, die die meisten E/A-Operationen oder die Datei, die einige der E/A-Operationen durchführt, verfügbar. Alternativ dazu kann es sein, dass das Dateisystem in diesem Fall überhaupt keine Informationen zur Verfügung stellt.

Die Definition der Struktur fileinfo_t lautet:

typedef struct fileinfo {
	string fi_name;                 /* name (basename of fi_pathname) */
	string fi_dirname;              /* directory (dirname of fi_pathname) */
	string fi_pathname;             /* full pathname */
	offset_t fi_offset;             /* offset within file */
	string fi_fs;                   /* filesystem */
	string fi_mount;                /* mount point of file system */
} fileinfo_t;

Das Feld fi_name enthält den Namen der Datei, aber keine Verzeichniskomponenten. Wenn keine Dateiinformationen mit einer E/A in Verbindung gebracht werden können, wird das Feld fi_name auf die Zeichenkette <none> gesetzt. In seltenen Fällen ist der Pfadname für eine Datei unbekannt. Dann wird das Feld fi_name auf die Zeichenkette <unknown> gesetzt.

Das Feld fi_dirname enthält nur die Verzeichniskomponente des Dateinamens. Ebenso wie bei fi_name kann diese Zeichenkette auf <none> gesetzt sein, wenn keine Dateiinformationen vorliegen, oder auf <unknown>, wenn der Pfadname der Datei unbekannt ist.

Das Feld fi_pathname enthält den vollständigen Pfadnamen der Datei. Ebenso wie bei fi_name kann diese Zeichenkette auf <none> gesetzt sein, wenn keine Dateiinformationen vorliegen, oder auf <unknown>, wenn der Pfadname der Datei unbekannt ist.

Das Feld fi_offset enthält den Versatz innerhalb der Datei oder -1, wenn entweder keine Dateiinformationen vorliegen oder der Versatz auf andere Weise nicht vom Dateisystem angegeben wird.

Beispiele

Das folgende Beispielskript zeigt bei der Ausgabe jeder E/A einschlägige Informationen an:

#pragma D option quiet

BEGIN
{
	printf("%10s %58s %2s\n", "DEVICE", "FILE", "RW");
}

io:::start
{
	printf("%10s %58s %2s\n", args[1]->dev_statname,
	    args[2]->fi_pathname, args[0]->b_flags & B_READ ? "R" : "W");
}

Bei einem Kaltstart von Acrobat Reader auf einem x86-Laptop erhalten wir eine Ausgabe der Art:


# dtrace -s ./iosnoop.d
    DEVICE                                                       FILE RW
     cmdk0                                 /opt/Acrobat4/bin/acroread  R
     cmdk0                                 /opt/Acrobat4/bin/acroread  R
     cmdk0                                                  <unknown>  R
     cmdk0                           /opt/Acrobat4/Reader/AcroVersion  R
     cmdk0                                                  <unknown>  R
     cmdk0                                                  <unknown>  R
     cmdk0                                                     <none>  R
     cmdk0                                                  <unknown>  R
     cmdk0                                                     <none>  R
     cmdk0                 /usr/lib/locale/iso_8859_1/iso_8859_1.so.3  R
     cmdk0                 /usr/lib/locale/iso_8859_1/iso_8859_1.so.3  R
     cmdk0                 /usr/lib/locale/iso_8859_1/iso_8859_1.so.3  R
     cmdk0                                                     <none>  R
     cmdk0                                                  <unknown>  R
     cmdk0                                                  <unknown>  R
     cmdk0                                                  <unknown>  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0                                                     <none>  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0                                                  <unknown>  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0                                                     <none>  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0   /opt/Acrobat4/Reader/intelsolaris/lib/libreadcore.so.4.0  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0             /opt/Acrobat4/Reader/intelsolaris/bin/acroread  R
     cmdk0                                                  <unknown>  R
     cmdk0        /opt/Acrobat4/Reader/intelsolaris/lib/libAGM.so.3.0  R
     cmdk0                                                     <none>  R
     cmdk0        /opt/Acrobat4/Reader/intelsolaris/lib/libAGM.so.3.0  R
     cmdk0        /opt/Acrobat4/Reader/intelsolaris/lib/libAGM.so.3.0  R
       ...

Die <none>-Einträge in der Ausgabe deuten darauf hin, dass sich die E/A auf keine der Daten in einer bestimmten Datei bezieht: Diese E/A-Operationen beziehen sich auf Metadaten in der einen oder anderen Form. Die <unknown>-Einträge in der Ausgabe bedeuten, dass der Pfadname der Datei unbekannt ist. Diese Situation ergibt sich relativ selten.

Das Beispielskript ließe sich durch einen assoziativen Vektor zum Erfassen der Dauer jeder E/A ein wenig verfeinern. Dies zeigt das nächste Beispiel:

#pragma D option quiet

BEGIN
{
	printf("%10s %58s %2s %7s\n", "DEVICE", "FILE", "RW", "MS");
}

io:::start
{
	start[args[0]->b_edev, args[0]->b_blkno] = timestamp;
}

io:::done
/start[args[0]->b_edev, args[0]->b_blkno]/
{
	this->elapsed = timestamp - start[args[0]->b_edev, args[0]->b_blkno];
	printf("%10s %58s %2s %3d.%03d\n", args[1]->dev_statname,
	    args[2]->fi_pathname, args[0]->b_flags & B_READ ? "R" : "W",
	    this->elapsed / 10000000, (this->elapsed / 1000) % 1000);
	start[args[0]->b_edev, args[0]->b_blkno] = 0;
}

Das nächste Beispiel zeigt die Ausgabe des obigen Beispielskripts beim Hot-Plugging eines USB-Speichergeräts in ein ansonsten im Ruhezustand befindliches x86-Laptop-System:


# dtrace -s ./iotime.d
    DEVICE                                                 FILE RW      MS
     cmdk0                                 /kernel/drv/scsa2usb  R  24.781
     cmdk0                                 /kernel/drv/scsa2usb  R  25.208
     cmdk0                                    /var/adm/messages  W  25.981
     cmdk0                                 /kernel/drv/scsa2usb  R   5.448
     cmdk0                                               <none>  W   4.172
     cmdk0                                 /kernel/drv/scsa2usb  R   2.620
     cmdk0                                    /var/adm/messages  W   0.252
     cmdk0                                            <unknown>  R   3.213
     cmdk0                                               <none>  W   3.011
     cmdk0                                            <unknown>  R   2.197
     cmdk0                                    /var/adm/messages  W   2.680
     cmdk0                                               <none>  W   0.436
     cmdk0                                    /var/adm/messages  W   0.542
     cmdk0                                               <none>  W   0.339
     cmdk0                                    /var/adm/messages  W   0.414
     cmdk0                                               <none>  W   0.344
     cmdk0                                    /var/adm/messages  W   0.361
     cmdk0                                               <none>  W   0.315
     cmdk0                                    /var/adm/messages  W   0.421
     cmdk0                                               <none>  W   0.349
     cmdk0                                               <none>  R   1.524
     cmdk0                                            <unknown>  R   3.648
     cmdk0                                 /usr/lib/librcm.so.1  R   2.553
     cmdk0                                 /usr/lib/librcm.so.1  R   1.332
     cmdk0                                 /usr/lib/librcm.so.1  R   0.222
     cmdk0                                 /usr/lib/librcm.so.1  R   0.228
     cmdk0                                 /usr/lib/librcm.so.1  R   0.927
     cmdk0                                               <none>  R   1.189
       ...
     cmdk0                            /usr/lib/devfsadm/linkmod  R   1.110
     cmdk0         /usr/lib/devfsadm/linkmod/SUNW_audio_link.so  R   1.763
     cmdk0         /usr/lib/devfsadm/linkmod/SUNW_audio_link.so  R   0.161
     cmdk0           /usr/lib/devfsadm/linkmod/SUNW_cfg_link.so  R   0.819
     cmdk0           /usr/lib/devfsadm/linkmod/SUNW_cfg_link.so  R   0.168
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_disk_link.so  R   0.886
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_disk_link.so  R   0.185
     cmdk0        /usr/lib/devfsadm/linkmod/SUNW_fssnap_link.so  R   0.778
     cmdk0        /usr/lib/devfsadm/linkmod/SUNW_fssnap_link.so  R   0.166
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_lofi_link.so  R   1.634
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_lofi_link.so  R   0.163
     cmdk0            /usr/lib/devfsadm/linkmod/SUNW_md_link.so  R   0.477
     cmdk0            /usr/lib/devfsadm/linkmod/SUNW_md_link.so  R   0.161
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_misc_link.so  R   0.198
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_misc_link.so  R   0.168
     cmdk0          /usr/lib/devfsadm/linkmod/SUNW_misc_link.so  R   0.247
     cmdk0     /usr/lib/devfsadm/linkmod/SUNW_misc_link_i386.so  R   1.735
       ... 

Diese Ausgabe lässt verschiedene Folgerungen über die Mechanik des Systems zu. Beachten Sie erstens, die lange Durchführungsdauer der ersten E/A-Operationen, die je 25 Millisekunden betrug. Diese Dauer kann darauf zurückzuführen sein, dass das Gerät cmdk0 dem Power-Management auf dem Laptop unterzogen wurde. Zweitens fällt die E/A durch das Laden des Treibers scsa2usb(7D) für die Behandlung des USB-Massenspeichergeräts auf. Drittens sind die Schreibzugriffe auf /var/adm/messages bei Meldung des Geräts zu beachten. Beachten Sie abschließend die Leszugriffe der Geräte-Link-Generatoren (die auf link.so endenden Dateien), die sich wahrscheinlich auf das neue Gerät beziehen.

Der Provider io ermöglicht eine tief greifende Interpretation der Ausgabe von iostat(1M). Nehmen wir an, es werde eine iostat-Ausgabe wie in folgendem Beispiel beobachtet:


extended device statistics
device       r/s    w/s   kr/s   kw/s wait actv  svc_t  %w  %b
cmdk0        8.0    0.0  399.8    0.0  0.0  0.0    0.8   0   1
sd0          0.0    0.0    0.0    0.0  0.0  0.0    0.0   0   0
sd2          0.0  109.0    0.0  435.9  0.0  1.0    8.9   0  97
nfs1         0.0    0.0    0.0    0.0  0.0  0.0    0.0   0   0
nfs2         0.0    0.0    0.0    0.0  0.0  0.0    0.0   0   0

Mit dem Skript iotime.d können wir diese E/A-Operationen, wie das nächste Beispiel zeigt, im Moment ihres Auftretens anzeigen:


    DEVICE                                               FILE RW      MS
       sd2                                  /mnt/archives.tar  W   0.856
       sd2                                  /mnt/archives.tar  W   0.729
       sd2                                  /mnt/archives.tar  W   0.890
       sd2                                  /mnt/archives.tar  W   0.759
       sd2                                  /mnt/archives.tar  W   0.884
       sd2                                  /mnt/archives.tar  W   0.746
       sd2                                  /mnt/archives.tar  W   0.891
       sd2                                  /mnt/archives.tar  W   0.760
       sd2                                  /mnt/archives.tar  W   0.889
     cmdk0                      /export/archives/archives.tar  R   0.827
       sd2                                  /mnt/archives.tar  W   0.537
       sd2                                  /mnt/archives.tar  W   0.887
       sd2                                  /mnt/archives.tar  W   0.763
       sd2                                  /mnt/archives.tar  W   0.878
       sd2                                  /mnt/archives.tar  W   0.751
       sd2                                  /mnt/archives.tar  W   0.884
       sd2                                  /mnt/archives.tar  W   0.760
       sd2                                  /mnt/archives.tar  W   3.994
       sd2                                  /mnt/archives.tar  W   0.653
       sd2                                  /mnt/archives.tar  W   0.896
       sd2                                  /mnt/archives.tar  W   0.975
       sd2                                  /mnt/archives.tar  W   1.405
       sd2                                  /mnt/archives.tar  W   0.724
       sd2                                  /mnt/archives.tar  W   1.841
     cmdk0                      /export/archives/archives.tar  R   0.549
       sd2                                  /mnt/archives.tar  W   0.543
       sd2                                  /mnt/archives.tar  W   0.863
       sd2                                  /mnt/archives.tar  W   0.734
       sd2                                  /mnt/archives.tar  W   0.859
       sd2                                  /mnt/archives.tar  W   0.754
       sd2                                  /mnt/archives.tar  W   0.914
       sd2                                  /mnt/archives.tar  W   0.751
       sd2                                  /mnt/archives.tar  W   0.902
       sd2                                  /mnt/archives.tar  W   0.735
       sd2                                  /mnt/archives.tar  W   0.908
       sd2                                  /mnt/archives.tar  W   0.753

Diese Ausgabe lässt die Vermutung zu, dass die Datei archives.tar von cmdk0 eingelesen (in /export/archives) und auf das Gerät sd2 (in /mnt) geschrieben wird. Das Vorhandensein von zwei Dateien mit dem Namen archives.tar, auf die separat, aber gleichzeitig zugegriffen wird, wirkt unwahrscheinlich. Um dem weiter auf den Grund zu gehen, können Sie, wie das nächste Beispiel zeigt, Gerät, Anwendung, Prozess-ID und übertragene Byte aggregieren:

#pragma D option quiet

io:::start
{
	@[args[1]->dev_statname, execname, pid] = sum(args[0]->b_bcount);
}

END
{
	printf("%10s %20s %10s %15s\n", "DEVICE", "APP", "PID", "BYTES");
	printa("%10s %20s %10d %15@d\n", @);
}

Wenn Sie dieses Skript einige Sekunden lang ausführen, erhalten Sie eine Ausgabe wie in folgendem Beispiel:


# dtrace -s ./whoio.d
^C
    DEVICE                  APP        PID           BYTES
     cmdk0                   cp        790         1515520
       sd2                   cp        790         1527808

Die Ausgabe zeigt, dass es sich bei dieser Aktivität tatsächlich um das Kopieren der Datei archives.tar von einem Gerät auf ein anderes handelt. Daraus folgt jedoch eine weitere nahe liegende Frage: Ist eines der Geräte schneller als das andere? Welches Gerät beschränkt den Kopiervorgang? Um diese Fragen zu beantworten, müssen wir anstelle der pro Sekunde übertragenen Byte den effektiven Datendurchsatz jedes Geräts in Erfahrung bringen. Dabei hilft uns das nächste Beispielskript:

#pragma D option quiet

io:::start
{
	start[args[0]->b_edev, args[0]->b_blkno] = timestamp;
}

io:::done
/start[args[0]->b_edev, args[0]->b_blkno]/
{
	/*
	 * We want to get an idea of our throughput to this device in KB/sec.
	 * What we have, however, is nanoseconds and bytes.  That is we want
	 * to calculate:
	 *
	 *                        bytes / 1024
	 *                  ------------------------
	 *                  nanoseconds / 1000000000
	 *
	 * But we can't calculate this using integer arithmetic without losing
	 * precision (the denomenator, for one, is between 0 and 1 for nearly
	 * all I/Os).  So we restate the fraction, and cancel:
	 *
	 *     bytes      1000000000         bytes        976562
	 *   --------- * -------------  =  --------- * -------------
	 *      1024      nanoseconds          1        nanoseconds
	 *
	 * This is easy to calculate using integer arithmetic; this is what
	 * we do below.
	 */
	this->elapsed = timestamp - start[args[0]->b_edev, args[0]->b_blkno];
	@[args[1]->dev_statname, args[1]->dev_pathname] =
	    quantize((args[0]->b_bcount * 976562) / this->elapsed);
	start[args[0]->b_edev, args[0]->b_blkno] = 0;
}

END
{
	printa("  %s (%s)\n%@d\n", @);
}

Wenn Sie das Beispielskript einige Sekunden lang ausführen, erhalten Sie die folgende Ausgabe:


  sd2 (/devices/pci@0,0/pci1179,1@1d/storage@2/disk@0,0:r)

           value  ------------- Distribution ------------- count
              32 |                                         0
              64 |                                         3
             128 |                                         1
             256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  2257
             512 |                                         1
            1024 |                                         0

  cmdk0 (/devices/pci@0,0/pci-ide@1f,1/ide@0/cmdk@0,0:a)

           value  ------------- Distribution ------------- count
             128 |                                         0
             256 |                                         1
             512 |                                         0
            1024 |                                         2
            2048 |                                         0
            4096 |                                         2
            8192 |@@@@@@@@@@@@@@@@@@                       172
           16384 |@@@@@                                    52
           32768 |@@@@@@@@@@@                              108
           65536 |@@@                                      34
          131072 |                                         0        

Die Ausgabe zeigt, dass sd2 eindeutig das beschränkende Gerät ist. Der Durchsatz von sd2 liegt zwischen 256K/s und 512K/s, während cmdk0 eine E/A-Leistung von 8 MB/s bis zu über 64 MB/s bietet. Das Skript gibt sowohl den Namen, wie er in iostat angezeigt wird, als auch den vollständigen Pfadnamen des Geräts aus. Wenn Sie mehr über das Gerät herausfinden möchten, könnten Sie wie folgt den Gerätepfad zu prtconf angeben:


# prtconf -v /devices/pci@0,0/pci1179,1@1d/storage@2/disk@0,0
disk, instance #2 (driver name: sd)
    Driver properties:
        name='lba-access-ok' type=boolean dev=(29,128)
        name='removable-media' type=boolean dev=none
        name='pm-components' type=string items=3 dev=none
            value='NAME=spindle-motor' + '0=off' + '1=on'
        name='pm-hardware-state' type=string items=1 dev=none
            value='needs-suspend-resume'
        name='ddi-failfast-supported' type=boolean dev=none
        name='ddi-kernel-ioctl' type=boolean dev=none
    Hardware properties:
        name='inquiry-revision-id' type=string items=1
            value='1.04'
        name='inquiry-product-id' type=string items=1
            value='STORAGE DEVICE'
        name='inquiry-vendor-id' type=string items=1
            value='Generic'
        name='inquiry-device-type' type=int items=1
            value=00000000
        name='usb' type=boolean
        name='compatible' type=string items=1
            value='sd'
        name='lun' type=int items=1
            value=00000000
        name='target' type=int items=1
            value=00000000

Die hervorgehobenen Bezeichnungen deuten darauf hin, dass es sich um einen austauschbaren USB-Datenträger handelt.

In den Beispielen dieses Abschnitts wurden alle E/A-Anforderungen untersucht. Möglicherweise interessiert Sie aber nur eine bestimmte Anforderungsart. Das folgende Beispiel protokolliert die Verzeichnisse, in denen Schreibzugriffe stattfinden, sowie die Anwendungen, die diese Schreibzugriffe durchführen.

#pragma D option quiet

io:::start
/args[0]->b_flags & B_WRITE/
{
	@[execname, args[2]->fi_dirname] = count();
}

END
{
	printf("%20s %51s %5s\n", "WHO", "WHERE", "COUNT");
	printa("%20s %51s %5@d\n", @);
}

Wenn Sie dieses Beispielskript eine Weile auf einem belegten Desktop ausführen, erhalten Sie, wie die nächste Beispielausgabe zeigt, einige interessante Ergebnisse:


# dtrace -s ./whowrite.d
^C
              WHO                                             WHERE COUNT
               su                                          /var/adm     1
          fsflush                                              /etc     1
          fsflush                                                 /     1
          fsflush                                          /var/log     1
          fsflush                                  /export/bmc/lisa     1
              esd   /export/bmc/.phoenix/default/78cxczuy.slt/Cache     1
          fsflush                              /export/bmc/.phoenix     1
              esd         /export/bmc/.phoenix/default/78cxczuy.slt     1
               vi                                          /var/tmp     2
               vi                                              /etc     2
              cat                                            <none>     2
             bash                                                 /     2
               vi                                            <none>     3
            xterm                                          /var/adm     3
          fsflush                                       /export/bmc     7
  MozillaFirebird                                            <none>     8
              vim                                       /export/bmc     9
  MozillaFirebird                                       /export/bmc    10
          fsflush                                          /var/adm    11
         devfsadm                                              /dev    14
              ksh                                            <none>    71
              ksh                                       /export/bmc    71
          fsflush         /export/bmc/.phoenix/default/78cxczuy.slt   119
  MozillaFirebird         /export/bmc/.phoenix/default/78cxczuy.slt   119
          fsflush                                            <none>   211
  MozillaFirebird   /export/bmc/.phoenix/default/78cxczuy.slt/Cache   591
          fsflush   /export/bmc/.phoenix/default/78cxczuy.slt/Cache   666
            sched                                            <none>  2385

Wie aus der Ausgabe hervorgeht, beziehen sich nahezu alle Schreibvorgänge auf den Cache für Mozilla Firebird. Die mit <none> gekennzeichneten Schreibzugriffe gehen wahrscheinlich auf das UFS-Protokoll zurück und wurden ihrerseits durch andere Schreibvorgänge im Dateisystem ausgelöst. Ausführliche Informationen zur Protokollierung finden Sie unter ufs(7FS) Dieses Beispiel demonstriert, wie mithilfe des Providers io Probleme auf wesentlich höheren Softwareebenen entdeckt werden können. In diesem Fall hat das Skript ein Konfigurationsproblem aufgespürt: Der Webbrowser würde sehr viel weniger E/A verursachen (sehr wahrscheinlich sogar überhaupt keine), wenn sich sein Cache in einem Verzeichnis in einem tmpfs(7FS)-Dateisystem befände.

In den bisherigen Beispielen kamen nur die Prüfpunkte start und done zum Einsatz. Mithilfe der Prüfpunkte wait-start und wait-done können wir der Frage auf den Grund gehen, weshalb und wie lange Anwendungen in Erwartung von E/A blockieren. Im folgenden Beispielskript werden sowohl io- als auch sched-Prüfpunkte (siehe Kapitel 26Der Provider sched) verwendet und die CPU-Zeit im Vergleich zur E/A-Wartezeit für die StarOffice-Software abgeleitet:

#pragma D option quiet

sched:::on-cpu
/execname == "soffice.bin"/
{
	self->on = vtimestamp;
}

sched:::off-cpu
/self->on/
{
	@time["<on cpu>"] = sum(vtimestamp - self->on);
	self->on = 0;
}

io:::wait-start
/execname == "soffice.bin"/
{
	self->wait = timestamp;
}

io:::wait-done
/self->wait/
{
	@io[args[2]->fi_name] = sum(timestamp - self->wait);
	@time["<I/O wait>"] = sum(timestamp - self->wait);
	self->wait = 0;
}

END
{
	printf("Time breakdown (milliseconds):\n");
	normalize(@time, 1000000);
	printa("  %-50s %15@d\n", @time);

	printf("\nI/O wait breakdown (milliseconds):\n");
	normalize(@io, 1000000);
	printa("  %-50s %15@d\n", @io);
}

Wenn Sie das Beispielskript während eines Kaltstarts von StarOffice ausführen, erhalten Sie folgende Ausgabe:


Time breakdown (milliseconds):
  <on cpu>                                                      3634
  <I/O wait>                                                   13114

I/O wait breakdown (milliseconds):
  soffice.tmp                                                      0
  Office                                                           0
  unorc                                                            0
  sbasic.cfg                                                       0
  en                                                               0
  smath.cfg                                                        0
  toolboxlayout.xml                                                0
  sdraw.cfg                                                        0
  swriter.cfg                                                      0
  Linguistic.dat                                                   0
  scalc.cfg                                                        0
  Views.dat                                                        0
  Store.dat                                                        0
  META-INF                                                         0
  Common.xml.tmp                                                   0
  afm                                                              0
  libsimreg.so                                                     1
  xiiimp.so.2                                                      3
  outline                                                          4
  Inet.dat                                                         6
  fontmetric                                                       6
  ...
  libucb1.so                                                      44
  libj641si_g.so                                                  46
  libX11.so.4                                                     46
  liblng641si.so                                                  48
  swriter.db                                                      53
  libwrp641si.so                                                  53
  liblocaledata_ascii.so                                          56
  libi18npool641si.so                                             65
  libdbtools2.so                                                  69
  ofa64101.res                                                    74
  libxcr641si.so                                                  82
  libucpchelp1.so                                                 83
  libsot641si.so                                                  86
  libcppuhelper3C52.so                                            98
  libfwl641si.so                                                 100
  libsb641si.so                                                  104
  libcomphelp2.so                                                105
  libxo641si.so                                                  106
  libucpfile1.so                                                 110
  libcppu.so.3                                                   111
  sw64101.res                                                    114
  libdb-3.2.so                                                   119
  libtk641si.so                                                  126
  libdtransX11641si.so                                           127
  libgo641si.so                                                  132
  libfwe641si.so                                                 150
  libi18n641si.so                                                152
  libfwi641si.so                                                 154
  libso641si.so                                                  173
  libpsp641si.so                                                 186
  libtl641si.so                                                  189
  <unknown>                                                      189
  libucbhelper1C52.so                                            195
  libutl641si.so                                                 213
  libofa641si.so                                                 216
  libfwk641si.so                                                 229
  libsvl641si.so                                                 261
  libcfgmgr2.so                                                  368
  libsvt641si.so                                                 373
  libvcl641si.so                                                 741
  libsvx641si.so                                                 885
  libsfx641si.so                                                 993
  <none>                                                        1096
  libsw641si.so                                                 1365
  applicat.rdb                                                  1580

Wie diese Ausgabe zeigt, wird die meiste Wartezeit beim StarOffice-Kaltstart vom Warten auf E/A-Operationen verursacht (13,1 s Wartezeit auf E/A im Vergleich zu 3,6 s Wartezeit auf CPU-Operationen). Führen wir das Skript bei einem Warmstart von StarOffice aus, wird deutlich, dass die E/A-Zeit durch Seiten-Caching eliminiert wurde:


Time breakdown (milliseconds):
  <I/O wait>                                                       0
  <on cpu>                                                      2860

I/O wait breakdown (milliseconds):
  temp                                                             0
  soffice.tmp                                                      0
  <unknown>                                                        0
  Office                                                           0

In der Kaltstart-Ausgabe sehen wir für die Datei applicat.rdb mehr E/A-Wartezeit als für jede andere Datei. Es ist anzunehmen, dass zahlreiche E/A-Zugriffe auf die Datei stattgefunden haben. Um diese weiter zu untersuchen, bedienen wir uns des folgenden D-Skripts:

io:::start
/execname == "soffice.bin" && args[2]->fi_name == "applicat.rdb"/
{
	@ = lquantize(args[2]->fi_offset != -1 ?
	    args[2]->fi_offset / (1000 * 1024) : -1, 0, 1000);
}

In diesem Skript wird das Feld fi_offset der Struktur fileinfo_t verwendet, das uns Aufschluss über die Dateibereiche geben soll, auf die zugegriffen wird. Dabei gilt eine Granularität von einem Megabyte. Wenn Sie dieses Skript während eines Kaltstarts von StarOffice ausführen, erhalten Sie eine Ausgabe wie diese:


# dtrace -s ./applicat.d
dtrace: script './applicat.d' matched 4 probes
^C


           value  ------------- Distribution ------------  count
             < 0 |                                         0
               0 |@@@                                      28
               1 |@@                                       17
               2 |@@@@                                     35
               3 |@@@@@@@@@                                72
               4 |@@@@@@@@@@                               78
               5 |@@@@@@@@                                 65
               6 |                                         0

Die Ausgabe sagt uns, dass nur auf die ersten sechs Megabyte der Datei zugegriffen wird, da die Datei möglicherweise sechs Megabyte groß ist. Außerdem deutet die Ausgabe darauf hin, dass nicht auf die gesamte Datei zugegriffen wird. Wollten wir nun die Kaltstartzeit für StarOffice verkürzen, wäre es hilfreich, das Zugriffsmuster der Datei zu verstehen. Wenn die benötigten Bereiche möglicherweise großteilig zusammenhängen, ließe sich die StarOffice-Kaltstartzeit beispielsweise mit einem Scout-Thread verbessern, der vor der Anwendung ausgeführt wird und die E/A-Zugriffe auf die Datei einleitet, noch bevor sie benötigt werden. (Dieser Ansatz ist dann besonders direkt, wenn der Zugriff auf die Datei über mmap(2) erfolgt.) Für die ca. 1,6 Sekunden Kaltstartzeit, die sich durch diese Strategie einsparen ließen, lohnt sich die zusätzliche Komplexität und Wartungslast in der Anwendung jedoch nicht. Auf jeden Fall ermöglichen die mit dem Provider io erhobenen Angaben ein genaues Verständnis der Vorteile, die durch einen solchen Aufwand zusätzlich erzielt werden könnten.

Stabilität

Der Provider io beschreibt die verschiedenen Stabilitäten anhand des DTrace-Stabilitätsmechanismus gemäß der folgenden Tabelle. Weitere Informationen zum Stabilitätsmechanismus finden Sie in Kapitel 39Stabilität.

Element 

Namensstabilität 

Datenstabilität 

Abhängigkeitsklasse 

Provider 

Evolving 

Evolving 

ISA

Modul 

Private 

Private 

Unknown 

Funktion 

Private 

Private 

Unknown 

Name 

Evolving 

Evolving 

ISA

Argumente 

Evolving 

Evolving 

ISA