Solaris 动态跟踪指南

聚合

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) 的第一个参数,所以下面的示例使用由 execnamearg0 组成的关键字:

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 而不是聚合常量字符串。