DTrace 将聚合函数的结果存储在称为聚合的对象中。聚合结果使用类似于关联数组所用的表达式元组来建立索引。在 D 中,聚合的语法如下所示:
@name[ keys ] = aggfunc ( args );
其中,name 是聚合的名称,keys 是用逗号分隔的 D 表达式列表,aggfunc 是其中一个 DTrace 聚合函数,而 args 是用逗号分隔的适用于聚合函数的参数列表。聚合 name 是使用特殊字符 @ 作为前缀的 D 标识符。D 程序中指定的所有聚合都为全局变量;没有线程局部聚合或子句局部聚合。聚合名称保存在与其他 D 全局变量不同的标识符名称空间中。请记住,如果重用名称,则 a 和 @a 不是同一变量。特殊聚合名称 @ 可用于在简单 D 程序中命名匿名聚合。D 编译器将此名称视为聚合名称 @_ 的别名。
下表中显示了 DTrace 聚合函数。大多数聚合函数仅接受表示新数据的单个参数。
表 9–1 DTrace 聚合函数
函数名 |
参数 |
结果 |
---|---|---|
count |
无 |
调用次数。 |
sum |
标量表达式 |
所指定表达式的总计值。 |
avg |
标量表达式 |
所指定表达式的算术平均值。 |
min |
标量表达式 |
所指定表达式中的最小值。 |
max |
标量表达式 |
所指定表达式中的最大值。 |
lquantize |
标量表达式、下限、上限、步长值 |
所指定表达式的值的线性频数分布(按指定范围确定大小)。递增最高存储桶中小于指定表达式的值。 |
quantize |
标量表达式 |
所指定表达式的值的二次方频数分布。递增最高二次方存储桶中小于所指定表达式的值。 |
例如,要计算系统中 write(2) 系统调用数,可以使用 count() 聚合函数,并以提示性字符串作为关键字:
syscall::write:entry { @counts["write system calls"] = count(); }
缺省情况下,当因为显式 END 操作或用户按 Ctrl-C 组合键而使进程终止时,dtrace 命令将列显聚合结果。以下示例输出显示了运行此命令、等待几秒钟,然后按 Ctrl-C 组合键的结果:
# dtrace -s writes.d dtrace: script './writes.d' matched 1 probe ^C write system calls 179 # |
可以通过将 execname 变量用作聚合的关键字来按每个进程名称计算系统调用:
syscall::write:entry { @counts[execname] = count(); }
以下示例输出显示了运行此命令、等待几秒钟,然后按 Ctrl-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 # |
或者,您可能想要进一步查看按可执行文件的名称和文件描述符组织的输出内容。文件描述符是 write(2) 的第一个参数,所以下面的示例使用由 execname 和 arg0 组成的关键字:
syscall::write:entry { @counts[execname, arg0] = count(); }
运行此命令将会生成包含可执行文件的名称和文件描述符的表,如下例所示:
# 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 # |
以下示例显示按进程名组织的用于写入的系统调用中花费的平均时间。此示例使用 avg() 聚合函数,并将用于求平均值的表达式指定为参数。该示例对系统调用中花费的挂钟时间求平均值。
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = avg(timestamp - self->ts); self->ts = 0; }
以下示例输出显示了运行此命令、等待几秒钟,然后按 Ctrl-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 # |
平均值非常有用,但通常不能提供足够的详细信息来帮助您了解数据点的分布。要更详细地了解分布情况,请使用 quantize() 聚合函数,如下例所示:
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = quantize(timestamp - self->ts); self->ts = 0; }
因为每一行输出都会产生频数分布图,所以此脚本的输出实际上比之前的输出要长一些。以下示例显示了选择的样本输出:
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 |
请注意,频数分布的行数始终是二次方值。每一行表示大于或等于对应值,但小于下一个更大行值的元素数目。例如,以上输出说明 iropt 在 8,192 纳秒和 16,383 纳秒之间(含 8,192 纳秒和 16,383 纳秒)进行了 4149 次写入操作。
虽然 quantize() 可用于快速了解数据,但您可能想要检查线性值的分布情况。要显示线性值的分布,请使用 lquantize() 聚合函数。除 D 表达式外,lquantize() 函数还接受三个参数:下限、上限和步长。例如,如果想要按文件描述符查看写入分布,则使用二次方量化可能不再有效。应改为使用范围较小的线性量化,如下例所示:
syscall::write:entry { @fds[execname] = lquantize(arg0, 0, 100, 1); }
运行此脚本几秒钟后将会生成大量信息。以下示例显示了一组典型输出:
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 |
还可使用 lquantize() 聚合函数来聚合自过去某个时间点以来的时间。通过此方法,可以观察一段时间内行为的变化。以下示例显示了执行 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; }
执行许多 date(1) 进程时,上面的脚本有助于更好地了解系统调用行为。要查看此结果,请在一个窗口中运行 sh -c 'while true; do date >/dev/null; done',同时在另一个窗口中执行 D 脚本。该脚本会生成 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 |
通过此输出,可以大致了解与内核的必需服务相关的 date(1) 命令的不同阶段。为了更好地了解这些阶段,您可能需要了解何时进行哪些系统调用。如果这样,可以更改 D 脚本来聚合变量 probefunc 而不是聚合常量字符串。