Für die Instrumentierung des Systems zur Beantwortung leistungsbezogener Fragestellungen bietet es sich an, zu überlegen, wie Daten zu einer Antwort auf eine bestimmte Frage zusammengefasst werden können, anstatt die Daten von mehreren verschiedenen Prüfpunkten erfassen zu lassen. Wenn beispielsweise die Anzahl der von einer Benutzer-ID durchgeführten Systemaufrufe festgestellt werden soll, sind die bei den einzelnen Systemaufrufen erfassten Daten wahrscheinlich weniger interessant. Wir möchten einfach nur eine Tabelle mit Benutzer-IDs und Systemaufrufen sehen. Traditionsgemäß würde man zum Beantworten dieser Frage bei jedem Systemaufruf Informationen sammeln und die Informationen mit einem Tool wie awk(1) oder perl(1) nachträglich verarbeiten. In DTrace ist die Zusammenfassung von Daten jedoch eine Operation der ersten Klasse. Dieses Kapitel befasst sich mit den DTrace-Einrichtungen für die Manipulation von Aggregaten.
Eine Aggregatfunktion ist eine Funktion mit den folgenden Eigenschaften:
f(f(x0) U f(x 1) U ... U f(xn)) = f(x 0 U x1 U ... U xn)
wobei xn eine Menge beliebiger Daten ist. Das bedeutet, dass die Anwendung einer Aggregatfunktion auf Untergruppen des Ganzen und anschließend auf die Ergebnisse dasselbe Resultat erbringt, wie die Anwendung der Funktion auf das Ganze. Betrachten wir zum Beispiel eine Funktion SUM, die die Summe eines gegebenen Datensatzes ergibt. Wenn die unverarbeiteten Daten aus {2, 1, 2, 5, 4, 3, 6, 4, 2} bestehen, dann führt die Anwendung von SUM auf den gesamten Datensatz zum Ergebnis {29}. Analog führt die Anwendung von SUM auf die aus den ersten drei Elementen bestehende Untergruppe zum·Ergebnis \{5\}. Wenn wir SUM auf die drei nachfolgenden Elemente anwenden, erhalten wir \{12\}, und die Anwendung von SUM auf die übrigen drei Elemente ergibt ebenso \{12}. SUM ist eine Aggregatfunktion, da wir durch ihre Anwendung auf die Menge der Ergebnisse, {5, 12, 12}, dasselbe Ergebnis erhalten wie bei Anwendung von SUM auf die ursprünglichen Daten, nämlich \{29\}.
Nicht alle Funktionen haben diese zusammenfassende Wirkung. Ein Beispiel für eine Nicht-Aggregatfunktion ist die Funktion MEDIAN, die das mittlere Element eines Datensatzes ermittelt. (Ein Element, für das in einem Datensatz gleich viele größere wie kleinere Elemente vorhanden sind, ist das mittlere Element im Datensatz.) Der MEDIAN wird durch Sortieren des Datensatzes und Auswahl des mittleren Elements abgeleitet. Kehren wir zu den ursprünglichen unverarbeiteten Daten zurück und wenden wir MEDIAN auf die Untergruppe der ersten drei Elemente an. Das Ergebnis ist {2}. (Der sortierte Satz lautet {1, 2, 2}; {2} ist der aus dem mittleren Element bestehende Satz.) Analog ergibt die Anwendung von MEDIAN auf die nächsten drei Elemente \{4\} und die Anwendung von MEDIAN auf die letzten drei Elemente ebenfalls \{4}. Wenn wir MEDIAN auf die einzelnen Untergruppen anwenden, erhalten wir also den Datensatz {2, 4, 4}. Wenden wir MEDIAN nun auf diesen Satz an, beträgt das Ergebnis {4}. Wenn wir aber den ursprünglichen Satz sortieren, erhalten wir {1, 2, 2, 2, 3, 4, 4, 5, 6}. Das Ergebnis der Anwendung von MEDIAN auf diesen Satz beträgt {3}. Da diese beiden Ergebnisse nicht übereinstimmen, ist MEDIAN keine Aggregatfunktion.
Viele gebräuchliche Funktionen für die Analyse von Datensätzen sind Aggregatfunktionen. Dazu gehören Funktionen zum Zählen der Elemente im Datensatz, zum Berechnen des niedrigsten oder des höchsten Werts im Datensatz und zum Addieren aller Elemente im Datensatz. Die Bestimmung des arithmetischen Mittels einer Menge lässt sich aus der Funktion zum Zählen der Elemente und jener zum Berechnen der Summe aller Elemente im Datensatz kombinieren.
Verschiedene nützliche Funktionen sind jedoch keine Aggregatfunktionen. Hierzu gehören Funktionen zum Berechnen des Modus (des häufigsten Elements), des Mittelwerts oder der Standardabweichung einer Menge.
Die Anwendung von Aggregatfunktionen auf Daten während der Ablaufverfolgung bringt eine Reihe von Vorteilen mit:
Es muss nicht der gesamte Datensatz gespeichert werden. Jedes Mal, wenn dem Satz ein neues Element hinzugefügt werden soll, wird die Aggregatfunktion auf den aus dem aktuellen Zwischenergebnis bestehenden Datensatz und das neue Element angewendet. Nach der Berechnung des neuen Ergebnisses kann das neue Element verworfen werden. Durch diesen Prozess lässt sich die erforderliche Speicherkapazität um den Faktor der Anzahl Datenpunkte verringern, die häufig sehr hoch ist.
Die Datensammlung verursacht keine schädlichen Skalierbarkeitsprobleme. Aggregatfunktionen ermöglichen die Aufbewahrung von Zwischenergebnissen per CPU anstatt in einer gemeinsamen Datenstruktur. DTrace wendet dann die Aggregatfunktion auf die Menge aus den Zwischenergebnissen per CPU an, um das systemweite Endergebnis zu produzieren.
DTrace speichert die Ergebnisse von Aggregatfunktionen in einem Objekt namens Aggregat. Die Aggregatergebnisse werden ähnlich wie bei assoziativen Vektoren mit einem Tupel von Ausdrücken indiziert. Die Syntax für Aggregate in D lautet:
@Name[ Schlüssel ] = Aggfunk ( Args );
wobei Name für den Namen des Aggregats, Schlüssel für eine Liste mit Komma getrennter D-Ausdrücke, Aggfunkt für eine der DTrace-Aggregatfunktionen und Argumente für eine Liste mit Komma getrennter Argumente für die Aggregatfunktion stehen. Der Aggregatname ist ein D-Bezeichner mit dem vorangestellten Sonderzeichen @. Alle in Ihren D-Programmen benannten Aggregate sind globale Variablen. Es gibt keine thread- oder klausel-lokalen Aggregate. Die Aggregatnamen werden, getrennt von anderen globalen D-Variablen, in einem separaten Bezeichner-Namensraum geführt. Denken Sie bei der Wiederverwendung von Namen daran, dass a und @a nicht dieselbe Variable sind. Der spezielle Aggregatname @ kann in einfachen D-Programmen zum Benennen anonymer Aggregate eingesetzt werden. Der D-Compiler behandelt den Namen als Alias für den Aggregatnamen @_.
Die folgende Tabelle zeigt die Aggregatfunktionen von DTrace. Die meisten Aggregatfunktionen nehmen nur ein einziges Argument an, das die neue Information darstellt.
Tabelle 9–1 Aggregatfunktionen in DTrace
Funktionsname |
Argumente |
Ergebnis |
---|---|---|
count |
none |
Anzahl der Aufrufe. |
sum |
Skalarer Ausdruck |
Gesamtwert der angegebenen Ausdrücke. |
avg |
Skalarer Ausdruck |
Arithmetisches Mittel der angegebenen Ausdrücke. |
min |
Skalarer Ausdruck |
Kleinster Wert in den angegebenen Ausdrücken. |
max |
Skalarer Ausdruck |
Größter Wert in den angegebenen Ausdrücken. |
lquantize |
Skalarer Ausdruck, Untergrenze, Obergrenze, Schrittwert |
Lineare Häufigkeitsverteilung der Werte der angegebenen Ausdrücke, beschränkt durch den angegebenen Bereich. Inkrementiert den Wert in der höchsten Menge, die kleiner als der angegebene Ausdruck ist. |
quantize |
Skalarer Ausdruck |
Eine Quadrat-Häufigkeitsverteilung der Werte der angegebenen Ausdrücke. Inkrementiert den Wert in der höchsten Quadratmenge, die kleiner als der angegebene Ausdruck ist. |
Wenn beispielsweise die Anzahl der write(2)-Systemaufrufe im System gezählt werden sollen, könnten Sie eine informative Zeichenkette als Schlüssel und die Aggregatfunktion count() verwenden:
syscall::write:entry { @counts["write system calls"] = count(); }
Der Befehl dtrace gibt die Aggregatergebnisse standardmäßig nach Abschluss des Prozesses aus, entweder als Resultat einer expliziten END-Aktion oder auf die Betätigung von Strg-C durch den Benutzer hin. Die folgende Beispielausgabe zeigt das Ergebnis nach Ausführung dieses Befehls, einer Wartezeit von einigen Sekunden und der anschließenden Betätigung von Strg-C:
# dtrace -s writes.d dtrace: script './writes.d' matched 1 probe ^C write system calls 179 # |
Mit der Variable execname als Schlüssel für ein Aggregat lassen sich die Systemaufrufe per Prozessnamen zählen:
syscall::write:entry { @counts[execname] = count(); }
Die folgende Beispielausgabe zeigt das Ergebnis nach Ausführung dieses Befehls, einer Wartezeit von mehreren Sekunden und Drücken von Strg-C:
# dtrace -s writesbycmd.d dtrace: script './writesbycmd.d' matched 1 probe ^C dtrace 1 cat 4 sed 9 head 9 grep 14 find 15 tail 25 mountd 28 expr 72 sh 291 tee 814 def.dir.flp 1996 make.bin 2010 # |
Alternativ können die Schreibzugriffe nach Prozessnamen und Dateibezeichner zusammen organisiert werden. Der Dateibezeichner ist das erste Argument für write(2). Im folgenden Beispiel wird also ein aus execname und arg0 zusammengesetzter Schlüssel verwendet:
syscall::write:entry { @counts[execname, arg0] = count(); }
Die Ausführung dieses Befehls ergibt eine Tabelle wie in folgendem Beispiel, mit Prozessnamen und Dateibezeichner:
# dtrace -s writesbycmdfd.d dtrace: script './writesbycmdfd.d' matched 1 probe ^C cat 1 58 sed 1 60 grep 1 89 tee 1 156 tee 3 156 make.bin 5 164 acomp 1 263 macrogen 4 286 cg 1 397 acomp 3 736 make.bin 1 880 iropt 4 1731 # |
Im nächsten Beispiel wird die durchschnittlich im write-Systemaufruf verbrachte Zeit je Prozess angezeigt. Dabei wird der Aggregatfunktion avg() der Ausdruck als Argument übergeben, aus dem der Durchschnitt berechnet werden soll. In diesem Beispiel wird der Durchschnitt der im Systemaufruf abgelaufenen Gesamtzeit berechnet:
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = avg(timestamp - self->ts); self->ts = 0; }
Die folgende Beispielausgabe zeigt das Ergebnis nach Ausführung dieses Befehls, einer Wartezeit von mehreren Sekunden und Drücken von Strg-C:
# dtrace -s writetime.d dtrace: script './writetime.d' matched 2 probes ^C iropt 31315 acomp 37037 make.bin 63736 tee 68702 date 84020 sh 91632 dtrace 159200 ctfmerge 321560 install 343300 mcs 394400 get 413695 ctfconvert 594400 bringover 1332465 tail 1335260 # |
Ein Durchschnittswert kann zwar nützlich sein, liefert aber häufig nicht genug Detailinformationen zum Verstehen der Verteilung von Datenpunkten. Um die Verteilung genauer zu verstehen, setzen wir wie folgt die Aggregatfunktion quantize() ein:
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = quantize(timestamp - self->ts); self->ts = 0; }
Da aus jeder Ausgabezeile ein Häufigkeitsverteilungsdiagramm wird, fällt die Ausgabe dieses Skripts bedeutend länger aus als die vorherigen. Dieses Beispiel zeigt nur eine Auswahl der Beispielausgabe:
lint value ------------- Distribution ------------- count 8192 | 0 16384 | 2 32768 | 0 65536 |@@@@@@@@@@@@@@@@@@@ 74 131072 |@@@@@@@@@@@@@@@ 59 262144 |@@@ 14 524288 | 0 acomp value ------------- Distribution ------------- count 4096 | 0 8192 |@@@@@@@@@@@@ 840 16384 |@@@@@@@@@@@ 750 32768 |@@ 165 65536 |@@@@@@ 460 131072 |@@@@@@ 446 262144 | 16 524288 | 0 1048576 | 1 2097152 | 0 iropt value ------------- Distribution ------------- count 4096 | 0 8192 |@@@@@@@@@@@@@@@@@@@@@@@ 4149 16384 |@@@@@@@@@@ 1798 32768 |@ 332 65536 |@ 325 131072 |@@ 431 262144 | 3 524288 | 2 1048576 | 1 2097152 | 0 |
Beachten Sie, dass die Zeilen der Häufigkeitsverteilung immer Zweierpotenzen darstellen. Jede Zeile gibt die Anzahl der Elemente an, die größer gleich dem entsprechenden Wert, aber kleiner als der Wert der nächsthöheren Zeile sind. So zeigt beispielsweise die obige Ausgabe, dass iropt 4.149 Schreibzugriffe mit einer Dauer von 8.192 bis 16.383 (einschließlich) Nanosekunden hatte.
quantize() eignet sich für einen schnellen Einblick in die Daten, aber nicht so sehr, wenn Sie eine Verteilung über lineare Werte untersuchen müssen. Zum Anzeigen einer linearen Werteverteilung verwenden Sie die Aggregatfunktion lquantize. () Die Funktion lquantize() nimmt zusätzlich zu einem D-Ausdruck drei Argumente an: eine Untergrenze, eine Obergrenze und eine Schrittweite. Zur Betrachtung der Verteilung der Schreibzugriffe nach Dateibezeichner wäre eine Zweierpotenz-Quantisierung beispielsweise nicht wirkungsvoll. Gehen Sie hierzu stattdessen mit einer linearen Quantisierung und kleinem Wertebereich vor, wie im nächsten Beispiel dargestellt:
syscall::write:entry { @fds[execname] = lquantize(arg0, 0, 100, 1); }
Wenn Sie dieses Skript einige Sekunden lang ausführen, erhalten Sie eine große Menge an Informationen. Dieses Beispiel zeigt einen Ausschnitt einer typischen Ausgabe:
mountd value ------------- Distribution ------------- count 11 | 0 12 |@ 4 13 | 0 14 |@@@@@@@@@@@@@@@@@@@@@@@@@ 70 15 | 0 16 |@@@@@@@@@@@@ 34 17 | 0 xemacs-20.4 value ------------- Distribution ------------- count 6 | 0 7 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 521 8 | 0 9 | 1 10 | 0 make.bin value ------------- Distribution ------------- count 0 | 0 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3596 2 | 0 3 | 0 4 | 42 5 | 50 6 | 0 acomp value ------------- Distribution ------------- count 0 | 0 1 |@@@@@ 1156 2 | 0 3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 6635 4 |@ 297 5 | 0 iropt value ------------- Distribution ------------- count 2 | 0 3 | 299 4 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 20144 5 | 0 |
Die Funktion lquantize() ermöglicht außerdem die Zusammenfassung der Zeit ab einem bestimmten Punkt in der Vergangenheit. Mit dieser Technik können Sie Änderungen des Verhaltens im Verlauf der Zeit beobachten. Das folgende Beispiel zeigt die Änderung im Systemaufruf-Verhalten über die Lebensdauer eines Prozesses, der den Befehl date(1) ausführt:
syscall::exec:return, syscall::exece:return /execname == "date"/ { self->start = vtimestamp; } syscall:::entry /self->start/ { /* * We linearly quantize on the current virtual time minus our * process's start time. We divide by 1000 to yield microseconds * rather than nanoseconds. The range runs from 0 to 10 milliseconds * in steps of 100 microseconds; we expect that no date(1) process * will take longer than 10 milliseconds to complete. */ @a["system calls over time"] = lquantize((vtimestamp - self->start) / 1000, 0, 10000, 100); } syscall::rexit:entry /self->start/ { self->start = 0; }
Je mehr date(1)-Prozesse ausgeführt werden, desto tiefer der Einblick in das Systemaufruf-Verhalten, das das obige Skript bietet. Um das Ergebnis zu sehen, führen Sie sh -c 'while true; do date >/dev/null; done' in einem Fenster und das D-Skript in einem anderen aus. Das Skript erzeugt ein Profil des Systemaufruf-Verhaltens des Befehls date(1):
# dtrace -s dateprof.d dtrace: script './dateprof.d' matched 218 probes ^C system calls over time value ------------- Distribution ------------- count < 0 | 0 0 |@@ 20530 100 |@@@@@@ 48814 200 |@@@ 28119 300 |@ 14646 400 |@@@@@ 41237 500 | 1259 600 | 218 700 | 116 800 |@ 12783 900 |@@@ 28133 1000 | 7897 1100 |@ 14065 1200 |@@@ 27549 1300 |@@@ 25715 1400 |@@@@ 35011 1500 |@@ 16734 1600 | 498 1700 | 256 1800 | 369 1900 | 404 2000 | 320 2100 | 555 2200 | 54 2300 | 17 2400 | 5 2500 | 1 2600 | 7 2700 | 0 |
Diese Ausgabe gibt einen groben Eindruck der verschiedenen Phasen des Befehls date(1) in Bezug auf die vom Kernel benötigten Dienste. Zum besseren Verständnis dieser Phasen kann es nützlich sein, festzustellen, welche Systemaufrufe wann stattfinden. Dafür könnten wir das D-Skript so abändern, dass die Aggregation an der Variable probefunc anstatt an einer konstanten Zeichenkette erfolgt.
Standardmäßig werden Aggregate in der Reihenfolge ihres Auftretens im D-Programm angezeigt. Mit der Funktion printa() für die Ausgabe der Aggregate können Sie dieses Verhalten außer Kraft setzen. Die Funktion printa() bietet auch die Möglichkeit, die Aggregatdaten genau zu formatieren. Hierzu setzen Sie, wie in Kapitel 12Formatierung der Ausgabe beschrieben, eine Formatzeichenkette ein.
Wenn ein Aggregat nicht durch eine printa()-Anweisung im D-Programm formatiert wird, erstellt der Befehl dtrace eine Momentaufnahme der Aggregatdaten und gibt die Ergebnisse nach Abschluss der Ablaufverfolgung im Standard-Aggregatformat aus. Bei Verwendung einer printa()-Anweisung zur Formatierung eines bestimmten Aggregats wird das Standardverhalten deaktiviert. Indem Sie die Anweisung printa(@ Aggregatname) in eine dtrace:::END-Prüfpunktklausel in Ihrem Programm einfügen, erhalten Sie äquivalente Ergebnisse. Im Standardausgabeformat für die Aggregatfunktionen avg(), count(), min(), max() und sum() wird ein ganzzahliger Dezimalwert für den Aggregatwert jedes Tupels angezeigt. Das Standardausgabeformat der Aggregatfunktionen lquantize() und quantize() zeigt die Ergebnisse in Form einer ASCII-Tabelle. Aggregat-Tupel werden so ausgegeben, als hätte man trace() auf jedes Tupelelement angewendet.
Beim Anhäufen von Daten über einen gewissen Zeitraum kann es hilfreich sein, die Daten in Bezug auf einen konstanten Faktor zu normalisieren. Dank dieser Technik lassen sich unzusammenhängende Daten leichter vergleichen. So sollen möglicherweise beim Aggregieren von Systemaufrufen die Systemaufrufe nicht als absoluter Wert über den Verlauf der Ausführung, sondern in Häufigkeit pro Sekunde angezeigt werden. Die DTrace-Aktion normalize() ermöglicht eine derartige Normalisierung der Daten. Die Parameter für normalize() sind ein Aggregat und ein Normalisierungsfaktor. Die Ausgabe der Aggregatfunktion zeigt jeden Wert dividiert durch den Normalisierungsfaktor.
Das folgende Beispiel verdeutlicht das Aggregieren von Daten nach Systemaufruf:
#pragma D option quiet BEGIN { /* * Get the start time, in nanoseconds. */ start = timestamp; } syscall:::entry { @func[execname] = count(); } END { /* * Normalize the aggregation based on the number of seconds we have * been running. (There are 1,000,000,000 nanoseconds in one second.) */ normalize(@func, (timestamp - start) / 1000000000); }
Wenn das obige Skript kurz ausgeführt wird, erhalten wir auf einem Desktoprechner die folgende Ausgabe:
# dtrace -s ./normalize.d ^C syslogd 0 rpc.rusersd 0 utmpd 0 xbiff 0 in.routed 1 sendmail 2 echo 2 FvwmAuto 2 stty 2 cut 2 init 2 pt_chmod 3 picld 3 utmp_update 3 httpd 4 xclock 5 basename 6 tput 6 sh 7 tr 7 arch 9 expr 10 uname 11 mibiisa 15 dirname 18 dtrace 40 ksh 48 java 58 xterm 100 nscd 120 fvwm2 154 prstat 180 perfbar 188 Xsun 1309 .netscape.bin 3005 |
normalize() legt den Normalisierungsfaktor für das angegebene Aggregat fest. Die zugrunde liegenden Daten werden dadurch jedoch nicht geändert. denormalize() übernimmt nur ein Aggregat. Wenn wir dem vorigen Beispiel die Denormalisierungsaktion hinzufügen, erhalten wir sowohl die unverarbeitete Anzahl der Systemaufrufe als auch die Frequenz pro Sekunde:
#pragma D option quiet BEGIN { start = timestamp; } syscall:::entry { @func[execname] = count(); } END { this->seconds = (timestamp - start) / 1000000000; printf("Ran for %d seconds.\n", this->seconds); printf("Per-second rate:\n"); normalize(@func, this->seconds); printa(@func); printf("\nRaw counts:\n"); denormalize(@func); printa(@func); }
Wenn das obige Skript kurz ausgeführt wird, erhalten wir eine ähnliche Ausgabe wie in folgendem Beispiel:
# dtrace -s ./denorm.d ^C Ran for 14 seconds. Per-second rate: syslogd 0 in.routed 0 xbiff 1 sendmail 2 elm 2 picld 3 httpd 4 xclock 6 FvwmAuto 7 mibiisa 22 dtrace 42 java 55 xterm 75 adeptedit 118 nscd 127 prstat 179 perfbar 184 fvwm2 296 Xsun 829 Raw counts: syslogd 1 in.routed 4 xbiff 21 sendmail 30 elm 36 picld 43 httpd 56 xclock 91 FvwmAuto 104 mibiisa 314 dtrace 592 java 774 xterm 1062 adeptedit 1665 nscd 1781 prstat 2506 perfbar 2581 fvwm2 4156 Xsun 11616 |
Aggregate können auch renormalisiert werden. Wenn normalize() mehrmals für dasselbe Aggregat aufgerufen wird, gilt der im neuesten Aufruf angegebene Normalisierungsfaktor. Das folgende Beispiel gibt die Häufigkeit pro Sekunde im Verlauf der Zeit aus:
#pragma D option quiet BEGIN { start = timestamp; } syscall:::entry { @func[execname] = count(); } tick-10sec { normalize(@func, (timestamp - start) / 1000000000); printa(@func); }
Wenn Sie mit DTrace einfache Überwachungsskripten erzeugen, können Sie die Werte in einem Aggregat mit der Funktion clear() regelmäßig löschen lassen. Diese Funktion nimmt als einzigen Parameter ein Aggregat an. Die Funktion clear() löscht nur die Werte des Aggregats; die Aggregatschlüssel bleiben erhalten. Ein Schlüssel mit dem Wert Null in einem Aggregat deutet folglich darauf hin, dass der Schlüssel zuvor einen Nicht-Nullwert besessen hatte, der bei einem clear()-Vorgang auf Null gesetzt wurde. Um sowohl die Werte als auch die Schlüssel eines Aggregats zu löschen, verwenden Sie die Funktion trunc(). Ausführliche Informationen dazu finden Sie unter·Abschneiden von Aggregaten.
Im nächsten Beispiel fügen wir clear() in Beispiel 9–1 ein:
#pragma D option quiet BEGIN { last = timestamp; } syscall:::entry { @func[execname] = count(); } tick-10sec { normalize(@func, (timestamp - last) / 1000000000); printa(@func); clear(@func); last = timestamp; }
Während Beispiel 9–1 die Frequenz der Systemaufrufe über die Lebensdauer des dtrace-Aufrufs zeigt, gibt das obige Beispiel nur die Rate für die letzten zehn Sekunden aus.
Bei der Betrachtung von Aggregatergebnissen interessieren häufig nur die oberen Resultate. Die zu allen anderen außer den höchsten Werten gehörenden Schlüssel und Werte sind irrelevant. Auch kann es nützlich sein, durch Entfernen der Schlüssel und der Werte ein ganzes Aggregatergebnis zu löschen. Zu beiden Zwecken dient die DTrace-Funktion trunc().
Die Parameter für trunc() sind ein Aggregat und ein optionaler Kürzungswert. Ohne Kürzungswert verwirft trunc() sowohl die Aggregatwerte als auch die Aggregatschlüssel für das gesamte Aggregat. Ist ein Kürzungswert n vorhanden, löscht trunc() die Aggregatwerte und -schlüssel mit Ausnahme der Werte und Schlüssel, die zu den n höchsten Werten gehören. Das heißt, dass trunc(@foo, 10) das Aggregat namens foo nach den zehn höchsten Werten abschneidet, während trunc(@foo) das gesamte Aggregat löscht. Es wird auch dann das gesamte Aggregat gelöscht, wenn als Kürzungswert 0 angegeben wurde.
Um anstelle der n oberen die n unteren Werte anzuzeigen, übergeben Sie der Funktion trunc() einen negativen Kürzungswert. So schneidet beispielsweise trunc(@foo, -10) das Aggregat foo hinter den zehn niedrigsten Werten ab.
Erweitern wir nun das Systemaufruf-Beispiel so, dass über einen Zeitraum von zehn Sekunden die Häufigkeit der Systemaufrufe pro Sekunde für die zehn oberen aufrufenden Anwendungen angezeigt wird:
#pragma D option quiet BEGIN { last = timestamp; } syscall:::entry { @func[execname] = count(); } tick-10sec { trunc(@func, 10); normalize(@func, (timestamp - last) / 1000000000); printa(@func); clear(@func); last = timestamp; }
Das nächste Beispiel zeigt die Ausgabe des obigen Skripts bei Ausführung auf einem Laptop mit geringer Systemlast:
FvwmAuto 7 telnet 13 ping 14 dtrace 27 xclock 34 MozillaFirebird- 63 xterm 133 fvwm2 146 acroread 168 Xsun 616 telnet 4 FvwmAuto 5 ping 14 dtrace 27 xclock 35 fvwm2 69 xterm 70 acroread 164 MozillaFirebird- 491 Xsun 1287 |
Da DTrace einige Aggregatdaten im Kernel zwischenspeichert, kann es unter Umständen vorkommen, dass nach Hinzufügen eines neuen Schlüssels für ein Aggregat nicht genügend Speicherplatz zur Verfügung steht. In diesem Fall werden die Daten ausgelassen, ein Zähler wird erhöht, und dtrace generiert eine Meldung über die Aggregatauslassung. Diese Situation ist eher unwahrscheinlich, da DTrace den Dauerstatus (bestehend aus Aggregatschlüssel und Zwischenergebnis) auf Benutzerebene hält, wo der Speicherplatz dynamisch erweitert werden kann. Sollten tatsächlich Aggregatauslassungen auftreten, können Sie die Puffergröße für Aggregate mit der Option aggsize erhöhen, um dieses Risiko herabzusetzen. Diese Option ermöglicht außerdem die Minimierung des Speicherplatzbedarfs von DTrace. Wie jede Größenoption kann auch aggsize mit jedem Größensuffix angegeben werden. Die Richtlinien zur Veränderung der Größe dieses Puffers wird von der Option bufresize vorgegeben. Weitere Informationen zur Pufferung finden Sie in Kapitel 11Puffer und Pufferung. Weitere Informationen zu Optionen finden Sie in Kapitel 16Optionen und Tunables .
Eine alternative Methode zur Vermeidung von Aggregatauslassungen besteht darin, die Häufigkeit zu erhöhen, mit der die Aggregatdaten auf Benutzerebene verbraucht werden. Standardmäßig beläuft sich diese Rate auf einmal pro Sekunde, sie kann aber mit der Option aggrate angepasst werden. Wie jede Frequenzoption kann auch aggrate mit einem beliebigen Zeitsuffix angegeben werden. Standardmäßig gilt Häufigkeit pro Sekunde. Weitere Informationen zur Option aggsize finden Sie in Kapitel 16Optionen und Tunables .