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.
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.
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 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.
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 .
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.
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.
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 |