Solaris 动态跟踪指南

第 34 章 为用户应用程序静态定义跟踪

DTrace 为用户应用程序开发者提供了用于在应用程序代码中定义自定义探测器,以扩充 pid 提供器功能的工具。这些静态探测器在禁用时几乎不会产生任何开销,且可以像任何其他 DTrace 探测器一样动态启用。您可以使用这些静态探测器向 DTrace 用户说明应用程序的语义,不需要具有或了解应用程序的实现知识。本章介绍如何在用户应用程序中定义静态探测器,以及如何使用 DTrace 在用户进程中启用此类探测器。

选择探测器位置

DTrace 允许开发者在应用程序代码(包括完整的应用程序和共享库)中嵌入静态探测器点。无论应用程序或库在什么位置(在开发环境下或在生产环境下)运行,都可以启用这些探测器。所定义探测器的语义应易于被 DTrace 用户群理解。例如,对于与提交请求的客户机对应的 Web 服务器和响应该请求的 Web 服务器,可以定义 query-receivequery-respond 探测器。这些示例探测器易于被大多数 DTrace 用户理解,它们对应于应用程序的最高级别的抽象,而不是较低级别的实现信息。DTrace 用户可以使用这些探测器来了解请求的时间分配。如果 query-receive 探测器将 URL 请求字符串显示为参数,则 DTrace 用户可以通过将此探测器与 io 提供器结合使用,确定哪些请求产生的磁盘 I/O 最多。

在选择探测器名称和位置时还应考虑所描述的抽象对象的稳定性。在应用程序的未来发行版中,是否仍将保留此探测器,即使实现发生变化。此探测器是在所有系统体系结构中都有意义,还是特定于特殊的指令集?本章将详细讨论这些决定如何指导您完成静态跟踪定义。

向应用程序中添加探测器

库和可执行文件的 DTrace 探测器在相应应用程序的二进制文件的 ELF 部分中定义。本节介绍如何定义探测器,如何将探测器添加到应用程序源代码中,以及如何扩充应用程序的生成过程以包含 DTrace 探测器的定义。

定义提供器和探测器

.d 源文件中定义 DTrace 探测器,该源文件将在编译和链接应用程序时使用。首先,选择用户应用程序提供器的正确名称。对于正在执行应用程序代码的每个进程,会在进程标识符前附加所选择的提供器名称。例如,如果为以进程 ID 1203 执行的 Web 服务器选择提供器名称 myserv,则与此进程对应的 DTrace 提供器名称应为 myserv1203。在 .d 源文件中,添加与以下示例类似的提供器定义:

provider myserv {
	...
};			

接下来,为每个探测器和相应参数添加定义。以下示例定义选择探测器位置中讨论的两个探测器。第一个探测器有两个参数,都为 string 类型,第二个探测器没有参数。D 编译器会将任何探测器名称中的两个连续下划线 (__) 都转换为一个连字符 (-)。

provider myserv {
	probe query__receive(string, string);
	probe query__respond();
};

应该向提供器定义中添加稳定性属性,以便探测器的使用者了解在应用程序的将来版本中发生变化的可能性。有关 DTrace 稳定性属性的更多信息,请参见第 39 章。稳定性属性的定义如下例所示:


示例 34–1 myserv.d:静态定义的应用程序探测器

#pragma D attributes Evolving/Evolving/Common provider myserv provider
#pragma D attributes Private/Private/Unknown provider myserv module
#pragma D attributes Private/Private/Unknown provider myserv function
#pragma D attributes Evolving/Evolving/Common provider myserv name
#pragma D attributes Evolving/Evolving/Common provider myserv args

provider myserv {
	probe query__receive(string, string);
	probe query__respond();
};


注 –

使用用户添加的探测器中的非整型参数的 D 脚本必须使用 copyin()copyinstr() 函数来检索这些参数。有关更多信息,请参见第 33 章


向应用程序代码中添加探测器

至此,已在 .d 文件中定义了探测器,接下来需要扩充源代码以指示应触发探测器的位置。以下面的 C 应用程序源代码为例:

void
main_look(void)
{
	...
	query = wait_for_new_query();
	process_query(query)
	...
}

要添加探测器位置,请添加对 <sys/sdt.h> 中定义的 DTRACE_PROBE() 宏的引用,如下例所示:

#include <sys/sdt.h>
...

void
main_look(void)
{
	...
	query = wait_for_new_query();
	DTRACE_PROBE2(myserv, query__receive, query->clientname, query->msg);
	process_query(query)
	...
}

宏名称 DTRACE_PROBE2 中的后缀 2 表示将传递到探测器的参数个数。探测器宏的前两个参数是提供器名称和探测器名称,它们必须与 D 提供器和探测器定义对应。其余的宏参数是在触发探测器时将赋给 DTrace arg0..9 变量的参数。应用程序源代码可以包含对同一个提供器和探测器名称的多次引用。如果源代码中存在对同一个探测器的多次引用,则任何宏引用都将导致触发探测器。

生成包含探测器的应用程序

您必须扩充应用程序的生成过程以包含 DTrace 提供器和探测器定义。通常的生成过程会获取每个源文件,然后编译这些文件以创建相应的对象文件。然后再将编译后的对象文件链接在一起以创建最终的应用程序二进制文件,如下例所示:


cc -c src1.c
cc -c src2.c
...
cc -o myserv src1.o src2.o ...

要在应用程序中包含 DTrace 探测器定义,请向执行 dtrace 命令的生成过程中添加相应的 make 程序的描述文件规则,如下例所示:


cc -c src1.c
cc -c src2.c
...
dtrace -G -32 -s myserv.d src1.o src2.o ...
cc -o myserv myserv.o src1.o src2.o ...

如上所示的 dtrace 命令将对前面的编译器命令生成的对象文件进行后期处理,并生成对象文件 myserv.o(通过 myserv.d)和其他对象文件。dtrace -G 选项用于将提供器和探测器定义与用户应用程序链接起来。-32 选项用于生成 32 位应用程序二进制文件。-64 选项用于生成 64 位应用程序二进制文件。