DTrace stocke les résultats des fonctions de groupement dans des objets appelés groupements. Les résultats des groupements sont indexés avec un tuple d'expressions similaires à celles utilisées pour les tableaux associatifs. En D, la syntaxe d'un groupement est la suivante :
@name[ keys ] = aggfunc ( args );
où name est le nom du groupement, keys est une liste d'expressions D, séparées par des virgules, aggfunc est l'une des fonctions de groupement DTrace et args est une liste d'arguments séparés par des virgules, correspondant à la fonction de groupement. Le groupement nameest un identificateur D ayant comme préfixe le caractère spécial @. Tous les groupements de vos programmes D sont des variables globales ; il n'existe pas de groupement local de clause ou de thread. Les noms de groupement sont conservés dans un espace de noms d'identificateurs séparé des autres variables globales D. N'oubliez pas que a et @a ne sont pas la même variable si vous réutilisez les noms. Le nom de groupement spécial @ peut être utilisé pour nommer un groupement anonyme avec des programmes D simples. Le compilateur D traite ce nom en tant qu'alias du nom de groupement @_.
Les fonctions de groupement DTrace sont affichées dans le tableau suivant. La plupart des fonctions de groupement ne prennent qu'un seul argument représentant la nouvelle donnée.
Tableau 9–1 Fonctions de groupement DTrace
Nom de la fonction |
Arguments |
Résultat |
---|---|---|
count |
aucun |
Nombre d'appels. |
sum |
expression scalaire |
Valeur totale des expressions spécifiées. |
avg |
expression scalaire |
Moyenne arithmétique des expressions spécifiées. |
min |
expression scalaire |
Valeur la plus faible parmi les expressions spécifiées. |
max |
expression scalaire |
Valeur la plus élevée parmi les expressions spécifiées. |
lquantize |
expression scalaire, limite inférieure, limite supérieure, valeur d'étape |
Répartition linéaire des fréquences, comprises dans la plage spécifiée, des valeurs des expressions spécifiées. Incrémente la valeur dans le compartiment inférieur le plus proche de l'expression spécifiée. |
quantize |
expression scalaire |
Répartition des fréquences multiples de deux des valeurs des expressions spécifiées. Incrémente la valeur dans le compartiment multiple de deux inférieur le plus proche de l'expression spécifiée. |
Par exemple, pour compter le nombre d'appels système write(2) dans le système, vous pouvez utiliser une chaîne informative en tant que clé ainsi que la fonction de groupement count() :
syscall::write:entry { @counts["write system calls"] = count(); }
La commande dtrace affiche les résultats du groupement par défaut lorsque le processus se termine, soit en tant que résultat d'une action explicite END, soit lorsque l'utilisateur appuie sur Control-C. L'exemple de sortie suivant montre le résultat de l'exécution de cette commande, après une attente de quelques secondes et après avoir appuyé sur Control-C :
# dtrace -s writes.d dtrace: script './writes.d' matched 1 probe ^C write system calls 179 # |
Vous pouvez compter les appels système par nom de processus en utilisant la variable execname en tant que clé à un groupement :
syscall::write:entry { @counts[execname] = count(); }
L'exemple de sortie suivant montre le résultat de l'exécution de cette commande, après une attente de quelques secondes et après avoir appuyé sur Control-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 # |
Vous pouvez également vouloir examiner de manière plus approfondie les saisies, organisées par nom exécutable et descripteur de fichier. Le descripteur de fichier est le premier argument de write(2) ; l'exemple suivant utilise donc une clé comprenant à la fois execname et arg0 :
syscall::write:entry { @counts[execname, arg0] = count(); }
L'exécution de cette commande génère un tableau comprenant le nom exécutable et le descripteur de fichier, comme illustré dans l'exemple suivant :
# 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 # |
L'exemple suivant affiche le temps moyen écoulé lors de l'appel système en écriture, par nom de processus. Cet exemple utilise la fonction de groupement avg(), en spécifiant l'expression sur laquelle effectuer le calcul de la moyenne en tant qu'argument. L'exemple fait la moyenne du temps écoulé dans l'appel système :
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = avg(timestamp - self->ts); self->ts = 0; }
L'exemple de sortie suivant montre le résultat de l'exécution de cette commande, après une attente de quelques secondes et après avoir appuyé sur Control-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 # |
La moyenne peut s'avérer utile, mais c'est un élément qui ne fournit pas suffisamment de détails pour comprendre la répartition des points de données. Pour mieux comprendre la répartition, utilisez la fonction de groupement quantize(), comme illustré dans l'exemple suivant :
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = quantize(timestamp - self->ts); self->ts = 0; }
Étant donné que chaque ligne de sortie devient un diagramme de répartition de fréquences, ce script affiche une sortie nettement plus longue que les précédents : L'exemple suivant présente une sélection de la sortie de test :
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 |
Vous pouvez remarquer que les lignes de la répartition de fréquences sont toujours des multiples de deux. Chaque ligne indique le compte du nombre d'éléments supérieurs ou égaux à la valeur correspondante mais inférieurs à la valeur supérieure la plus proche. Par exemple, le résultat ci-dessus montre que iropt a 4,149 écritures prenant entre 8,192 nanosecondes et 16,383 nanosecondes incluses.
La fonction quantize() permet d'obtenir un aperçu rapide des données, mais vous souhaitez peut-être observer à la place une répartition linéaire des valeurs. Pour afficher une répartition linéaire des valeurs, utilisez la fonction de groupement lquantize(). La fonction lquantize() prend trois arguments, en plus d'une expression D : une limite inférieure, une limite supérieure et une étape. Par exemple, si vous souhaitez observer la répartition des écritures par descripteur de fichier, une quantification de multiples de deux n'est pas efficace. À la place, utilisez une quantification linéaire avec une plage restreinte, comme illustré dans l'exemple suivant :
syscall::write:entry { @fds[execname] = lquantize(arg0, 0, 100, 1); }
L'exécution de ce script pendant plusieurs secondes permet d'obtenir une grande quantité d'informations. L'exemple suivant montre une sélection de sortie classique :
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 |
Vous pouvez également utiliser la fonction de groupement lquantize() pour regrouper le temps écoulé depuis un certain point du passé. Cette technique permet d'observer une modification de comportement au fil du temps. L'exemple suivant affiche la modification dans le comportement de l'appel système sur une durée de vie d'un processus exécutant la commande date(1) :
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; }
Le script précédent fournit plus de détails sur le comportement de l'appel système lorsque de nombreux processus date(1) sont exécutés. Pour voir ce résultat, exécutez sh -c 'while true; do date >/dev/null; done' dans une fenêtre, tandis que le script D s'exécute dans une autre. Le script produit un profil du comportement de l'appel système de la commande 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 |
Ce résultat donne une idée approximative des différentes phases de la commande date(1) par rapport aux services requis du noyau. Pour mieux comprendre ces phases, vous souhaitez peut-être comprendre quels appels système font l'objet d'un appel et à quel moment. Si c'est le cas, vous pouvez modifier le script D pour regrouper la variable probefunc à la place d'une chaîne constante.