用 DTrace 进行动态跟踪
Solaris、FreeBSD 和 Mac OS X 内置的 Dynamic Tracing (DTrace) 功能提供一个更加动态的跟踪环境。与 truss 和相似的工具不同,可以使用 DTrace 检查正在运行的程序的内部情况,而不只是查看系统调用。另外,可以使用 DTrace 编写应用程序跟踪脚本,从而定制在跟踪过程中希望提取的信息。
DTrace 概述
DTrace 结合了您在 truss 和 strace 中已经看到的许多跟踪原理,但是在跟踪应用程序所用的方法和机制方面更加灵活。
truss 和 strace 只列出内核空间中的函数,而 DTrace 可以显示应用程序的函数名、它所依赖的任何库以及调用的内核函数。这把跟踪的方便性提高到与调试器差不多的水平。但是,与调试器不同,不能修改值,也不能暂停或以其他方式改变应用程序的执行过程。只能跟踪执行过程,而不能控制它。
与大多数跟踪工具和调试器相比,DTrace 的另一个独特之处是可以编写跟踪脚本,跟踪脚本定义要跟踪应用程序中的哪些方面,以及在执行应用程序时应该报告哪些信息。例如,可以指定 DTrace 只报告关于某个函数的信息,让它只输出函数调用中的一个参数。
除了输出特定信息之外,DTrace 还为许多实用程序值和函数提供内置支持。例如,在脚本中可以记录调用函数时的时间戳,然后把这个时间戳值与函数完成时的时间戳进行比较。通过比较这两个值,可以得到特定函数或操作的执行时间,可以使用这种跟踪信息提供执行和性能统计数据。
探测和提供者
DTrace 会在应用程序中添加检测机制,从而识别不同的执行点。这些执行点称为探测(probe),包括在内核、库和程序中定义的探测。内核、库和用户程序中的所有函数都可以指定为探测。另外,可以使用静态定义的探测识别感兴趣的特殊执行点。例如,在内核中可以使用探测识别向磁盘写数据的执行点。开发人员可以在程序中添加特定的探测,从而允许用户启用跟踪。这些探测称为 User-land Statically Defined Tracing(USDT)。
指定探测所用的结构是 provider:module:function:name,其中的 provider 是提供者的名称(例如,程序名、内核或操作系统的特定部分),module 是内核模块或库,function 常常是模块或程序中的函数名,name 标识探测。提供者还有一个特殊的标识符,PID;这用来标识正在运行的任何程序,可以用它跟踪正在运行的程序中的任何函数。
名称常常是系统或程序中已经定义的探测的名称。DTrace 还支持函数边界跟踪 (FBT),这可以跟踪进入和退出内核、库或应用程序中任何函数的执行点。当调用函数时,触发进入探测;当函数返回或完成执行时,触发返回探测。
指定探测的结构中的任何部分都可以忽略(在这种情况下,与所有项目匹配),也可以使用通配符。例如,可以使用 provider::: 指定某个提供者中的所有探测。还可以只指定一个 PID 提供者中的进入探测:pid$target:::entry。
上面指定的探测与 truss 的操作相似,但是涉及的函数包括程序、操作系统和程序依赖的任何库。这提供比 truss 更广的范围。
在使用 PID 提供者时,可以使用模块标识程序。如果不指定模块,就会跟踪程序调用的每个函数。通过指定程序名,可以把输出限制在程序内定义的函数。例如,可以使用 pid$target:ageindays:: 跟踪 ageindays 应用程序中的函数的执行情况。
可以使用 dtrace 跟踪任何程序;不需要以特殊方式编译程序。因此,与 truss 不同,即使您不掌握程序的源代码,也可以深入了解应用程序。
可以使用 dtrace 工具获得探测列表;-l 命令行选项列出系统中定义的所有探测:$ dtrace -l。
这个列表可能非常长。
单行跟踪
正如前面提到的,可以使用 DTrace 跟踪任何应用程序和其中的任何函数。从命令行使用 dtrace 有三种方式:指定一个命令;指定一个进程 ID;指定一个命名的静态探测。
在命令行上指定要执行的命令(使用 -c),使用 -n 选项指定希望监视的探测(见清单 10)。
清单 10. 指定希望监视的探测
$ dtrace -n 'pid$target:ageindays::entry' -c './ageindays 24/1/1980 26/3/1980'
dtrace: description 'pid$target:ageindays::entry' matched 7 probes
You have been alive 62 days
You were born on 24/1/1980 which is a Thursday
dtrace: pid 15925 has exited
CPU ID FUNCTION:NAME
1 57147 _start:entry
1 57148 __fsr:entry
1 57153 main:entry
1 57152 check_day:entry
1 57152 check_day:entry
1 57151 calc_diff:entry
1 57150 leap_year:entry
...
|
现在,指定一个正在运行的程序的进程 ID 以及希望跟踪的探测。例如,清单 11 跟踪 inetd 守护进程对 syslog 系统的调用。
清单 11. 跟踪对 syslog 系统的调用
$ dtrace -n 'pid$target::syslog:entry { printf("%d %s", arg0, copyinstr(arg1)) }'
-p `pgrep -x inetd`
|
还可以指定静态探测的名称,这与正在运行的任何进程中的所有探测匹配。例如,清单 12 跟踪对任何 exec 函数的调用(例如,当用户在 shell 中运行命令时)。因为没有指定特定的进程,这个探测会跟踪系统上任何用户对此函数的任何调用(见清单 12)。
清单 12. 指定静态探测的名称
$ dtrace -n 'syscall::exec*:entry'
dtrace: description 'syscall::exec*:entry' matched 2 probes
CPU ID FUNCTION:NAME
0 56750 exece:entry
0 56750 exece:entry
0 56750 exece:entry
|
在上面这些示例中,只使用了基本输出,输出的信息只包括函数或探测名称以及触发探测的 CPU 和进程 ID。可以通过使用 DTrace 脚本编程语言产生更丰富、更有选择性的探测输出。
编写 DTrace 脚本
DTrace 脚本编程语言提供一种简单的编程机制,可以在提供者触发探测时执行特定的操作(称为动作)。这种语言不具备 PHP 或 Perl 等完整语言环境的灵活性,但是可以使用它在变量中记录信息、执行基本的计算和支持基本的决策。
下面的基本示例在执行特定的探测点时输出值。可以使用别名 arg0、arg1 等获取探测或函数的参数。这个示例监视给定的进程中以 open 开头的所有函数,显示每个函数打开的文件。
清单 13. 监视给定的进程中以 open 开头的所有函数
#!/bin/dtrace -s
#pragma D option quiet
pid$target::open*:entry
{
printf("Opened: %s\n",copyinstr(arg0));
}
|
可以把这段文本保存在 open.t 文件中,然后在此文件上设置执行位:$ chmod +x open.t。
这个文件现在是一个可执行的脚本。在使用它时,应该通过 -p 命令行选项指定进程 ID。例如,对一个 shell 运行它,就会获得 shell 在执行期间打开的文件的列表(见清单 14)。
清单 14. 使用 -p 选项
$ ./open.t -p 15930
Opened: /root/.bash_path
Opened: /root/.bash_vars
Opened: /root/.bashrc
Opened: /root/.bash_aliases
...
|
DTrace 脚本的一种典型用途是合并和汇总信息,提供不同操作的计数和时间差。DTrace 脚本中的探测在一个线程中依次执行,这意味着可以监视一系列探测的执行情况。许多探测是成对提供的,启始探测表示操作开始的位置,结束探测表示操作完成的位置。
通过记录触发启始探测和结束探测的时间,就可以判断出操作花费的时间。在 DTrace 脚本中,可以使用 self 变量保存启始时间。例如,清单 15 中的脚本使用 MySQL 数据库系统中的命名探测监视查询的执行时间。
清单 15. 使用 MySQL 中的命名探测监视查询的执行时间
#!/usr/sbin/dtrace -s
#pragma D option quiet
dtrace:::BEGIN
{
printf("%-80s %6s\n", "Query", "Duration (ms)");
}
mysql*:::query-start
{
self->query = copyinstr(arg5);
self->querystart = timestamp;
}
mysql*:::query-done
{
this->elapsed = (timestamp - self->querystart) /1000000;
printf("%-80s %6d\n", self->query, this->elapsed);
}
|
这个跟踪显示典型 DTrace 脚本的许多元素。首先,BEGIN 块输出一些标题信息,这在输出表格式数据时很有用。
query-start 探测包含许多参数,第六个参数 (arg5) 包含完整的查询文本。
在运行 MySQL 的服务器上执行查询时,可以获得执行每个查询花费的时间的统计数据(见清单 16)。
清单 16. 获得执行每个查询花费的时间的统计数据
$ ./basic.d
Query Duration (ms)
show tables 203
select * from t1 where i <5 131526
|
这些示例只涉及 DTrace 的基本功能。更多示例和信息见 参考资料。
|