级别: 初级 Manjunath S. Karikatti, 软件工程师, IBM
2009 年 8 月 27 日 因为存在复杂的环境和相互交互的各种软件组合,判断生产环境中发生的问题往往很困难。本文简要介绍 Solaris 动态跟踪工具和 AIX® 上的 ProbeVue 工具,并通过示例说明使用方法。
简介
如果在非生产环境中无法重现问题,就必须在生产环境中应用各种问题判断技术,以寻找问题的根源,同时不能影响生产系统的性能和稳定性。
在不同的 UNIX® 平台上有不同的工具和技术,但是这些并不都能够用来诊断生产系统中的问题,因为其中一些会造成性能和稳定性问题。
Solaris 和 AIX 平台都提供用于诊断生产系统问题的动态跟踪工具。
Solaris 上的 Dtrace 动态跟踪工具
Dtrace 是在 Solaris 10 中引入的动态跟踪工具,这种调试工具可以调试用传统的问题判断机制难以诊断的问题。
Dtrace 让用户能够动态地修改操作系统内核和用户进程,从而记录额外数据。管理员和开发人员可以使用记录的数据研究应用程序和操作系统,寻找正在运行的系统的异常行为的原因。
术语
-
探测(Probe):内核或应用程序中用户感兴趣的位置,需要记录来自这些地方的数据。
-
提供者(Provider):执行创建探测所需的操作的内核模块。
-
断言(Predicate):修改控制流的逻辑表达式,可以有条件地跟踪数据。
D 语言
Dtrace 动态跟踪工具使用 D 语言,用户可以使用 D 语言把动作与探测点关联起来,以及指定什么时候触发探测和在触发时记录哪些数据。dtrace 命令解释使用 D 语言编写的脚本。
这种语言支持与 C 语言相似的数据对象,比如 integer、char、short、long 等等。它还提供 ANSI-C 操作符的子集,可以用来操作数据对象。
D 语言结构
下面的示例说明 D 语言的结构:
probe1
/ predicate /
{
action
}
probe2
/ predicate /
{
action
}
|
D 语言结构由一个或多个探测子句组成。每个子句指定在满足断言条件的情况下探测要执行的动作。动作和断言是可选的。
探测分散在 Solaris 内核中的不同位置。为了从感兴趣的位置收集数据,用户必须在这个位置上启用所需的探测。当触发探测时,执行指定的动作。
可以使用命令行实用程序 dtrace 和用 D 语言编写的脚本启用探测。
探测是一个惟一的四元组,如下所示:
provider:module:function:name
-
name:探测的名称
-
function:探测的函数
-
module:函数所属的模块
-
provider:执行探测的内核模块
dtrace –l 命令列出系统上可用的所有探测。
# dtrace -l
ID PROVIDER MODULE FUNCTION NAME
1 dtrace - BEGIN
2 dtrace - END
3 dtrace - ERROR
4 vminfo fasttrap fasttrap_uwritesoftlock
5 vminfo fasttrap fasttrap_uread softlock
6 fbt pfil pfil_list_add entry
7 fbt pfil pfil_list_add return
|
dtrace 实用程序解释使用 D 语言编写的脚本,启用指定的探测,在触发探测时根据脚本记录感兴趣的数据。
可以使用 dtrace -s mydscript.d 这样的 dtrace 命令执行 D 脚本,也可以通过在脚本开头插入一行 #!/usr/sbin/dtrace -s 直接执行脚本。
AIX 上的 ProbeVue 动态跟踪工具
ProbeVue 是 AIX 6.1 中引入的动态跟踪工具。它的作用与 Solaris 操作系统上的 dtrace 相当。
术语
探测(probe):中断正常系统动作,获取关于当前上下文的信息。
探测点(probe point):标识正常系统活动期间能够进行探测的一个点。
启用/禁用探测(Enabling/disable probe):把探测与探测点连接起来/解除连接。
探测位置(Probe Location):用户/内核空间中执行跟踪动作的位置。
探测事件(Probe Event):在发生这种事件时执行跟踪动作。
断言(Predicate):执行跟踪动作所需满足的条件。
探测管理器(probe manager):用来定义和提供一组相同探测类型的探测点的软件代码,比如系统调用探测管理器。
ProvbVue 使用一种称为 “Vue” 的编程语言和 ProbeVue 命令,这个命令启动跟踪会话并解释 Vue 脚本。
Vue 脚本或 Vue 程序可以用来:
- 指定要动态启用探测的探测点。
- 指定在触发探测时执行动作必须满足的条件(如果有的话)。
- 指定要执行的动作,包括要捕捉哪些跟踪数据。
Vue 语言概述
探测脚本包含一个声明部分(可选),然后是一个或多个子句。探测脚本的布局如下:
declaration statement 1; /* declaration */
declaration statement 2; /* declaration */
@@BEGIN /* BEGIN clause optional */
{
statement 1;
statement 2;
}
/* Probe clause start */
<probe point tuple>,<proble point tuple> /* probe point specification statement*/
when(<predicate>) /* optional predicate */
{
statement 1; /* action block */
statement 2;
}
/* Probe clause end */
|
声明部分包含声明语句,它们声明后续子句中使用的局部和全局变量。
它可以包含:
- _ 类型、结构、联合体和枚举定义。
- _ 要探测的函数的函数原型声明。
- _ 脚本中要访问的全局和内核变量的声明。
Vue 语言支持大多数 C 操作符和 C-89 数据类型,还有它自己的内置变量 (_pid, _tid) 和函数 (stacktrace, timestamp)。
例如:
int count; /* user defined global counter variable */ |
每个探测子句由一个探测点指定语句、动作块和可选的断言组成。
探测点指定语句指定应该触发探测动作的代码位置或事件。
AIX 6.1.0 支持下面的探测类型:
- 用户函数进入探测(即 uft 探测)
- 系统调用进入/退出探测(即 syscall 探测)
- 以特定的时间间隔触发的探测(即 interval 探测)
探测点指定语句格式
@@<probetype>:<一个或多个与探测类型相关的字段,由冒号分隔>
|
每个探测点至少包含三个字段。
- 第一个字段总是指定探测类型,由此指定它的探测管理器。
- 对于支持与进程相关的跟踪的探测管理器,第二个字段必须是进程 ID。
- 对于支持函数进入或退出探测的探测管理器,位置字段(最后一个字段)必须使用 “entry” 或 “exit” 关键字。
- 字段由冒号(“:”)分隔。
- 探测点元组字段中的星号(“*”)表示与这个字段的任何可能值匹配。
例如:
- @@uft:34568:*:foo:entry |
这指定在进程 ID 为 34568 的进程中在进入任何名为 foo() 的函数时触发探测。第三个字段中的星号表示,在这个进程的任何模块中找到的 foo() 函数都应该探测。
这指定在退出 read 系统调用时触发探测。星号表示所有进程的 read 系统调用都应该探测。
这指定每隔 500 毫秒(时钟时间)触发一次探测。星号是一个占位符,用于在以后支持更细粒度的探测点。
说明 Solaris dtrace 使用方法的场景
场景 1
客户报告在运行他的应用程序(假设是 C 应用程序)时出现了一个内存泄漏问题。技术支持团队很难重现这个问题,因为很难在实验室中复制客户的环境。
下面的示例说明如何使用 D 脚本帮助收窄内存泄漏原因的范围,而不需要修改应用程序或系统环境,也不需要停止系统运行。这个示例可以应用于任何使用 malloc 和 free 例程分配和释放进程内存的 C 应用程序。
为了使用 dtrace 找到应用程序中可能的内存泄漏位置,创建一个名为 malloc.d 的 D 脚本,其内容如下:
pid$target::malloc:entry
{
printf("malloc: PID %d requested %d bytes\n",$target,arg0);
}
pid$target::malloc:return
{
printf("malloc: PID %d returned address 0X%x\n",$target,arg1);
}
pid$target::free:entry
{
printf("free: PID %d freeing address 0X%x \n",$target,arg0);
}
|
用 dtrace 实用程序调用这个应用程序(myapp,源代码文件 myapp.c):
dtrace –c ./myapp –s malloc.d
|
输出如下:
dtrace: script 'malloc.d' matched 20 probes
dtrace: pid 6592 exited with status 1
CPU ID FUNCTION:NAME
0 39972 malloc:entry malloc: PID 6592 requested 100 bytes
0 39974 malloc:return malloc: PID 6592 returned address 0X20fa0
0 39972 malloc:entry malloc: PID 6592 requested 100 bytes
0 39974 malloc:return malloc: PID 6592 returned address 0X21010
0 39981 free:entry free: PID 6592 freeing address 0X20fa0
0 39981 free:entry free: PID 6592 freeing address 0X21010
|
可以重定向输出并使用脚本解析输出,检查应用程序是否有未释放的已分配内存。malloc.d 脚本捕捉应用程序执行的所有 malloc 调用。另外,通过在前面的 D 脚本中的进入探测中添加 ustack() 函数,可以显示 malloc 调用的堆栈跟踪,这有助于识别造成泄露的代码位置。
这个示例使用 PID 提供者。PID 探测提供者的名称形式为字符串 ‘pid’ 加上进程 ID。因此,对于进程 1234,PID 提供者为 pid1234。
arg0 是传递给要探测的函数的参数,用于进入探测。
arg1 是要探测的函数的返回值,用于退出探测。
在 malloc.d 脚本中,在运行时把 $target 替换为应用程序 myapp 的进程 ID。
如果用户需要跟踪某个用户函数中的 malloc 和 free 调用,那么可以按以下方式修改 malloc.d 脚本。如果在探测指定语句中没有指定模块部分,探测就应用于与这个应用程序链接的所有模块。
pid$target::functionXX:entry
{
traceflag=1;
}
pid$target::functionXX:return
{
traceflag=0;
}
pid$target::malloc:entry
/traceflag != 0/
{
printf("malloc: PID %d requested %d bytes\n",$target,arg0);
}
pid$target::malloc:return
/traceflag != 0/
{
printf("malloc: PID %d returned address 0X%x\n",$target,arg1);
}
pid$target::free:entry
/traceflag != 0/
{
printf("free: PID %d freeing address 0X%x \n",$target,arg0);
}
|
场景 2
在中间件环境中(例如 WebSphere® 或 TXSeries®),应用程序的业务逻辑在应用服务器上运行。在这种复杂的环境中,运行应用程序的客户常常抱怨响应时间太长。为了找到性能瓶颈,必须了解在为最终用户请求服务的过程中应用程序所交互的软件花费的时间。
中间件软件作为驻留应用程序的环境,可以提供跟踪不同软件组合花费的时间所需的代码。但是,这会产生比较大的开销。
可以使用 dtrace 了解这方面的信息,这不会影响性能,也不需要修改应用程序或中间件软件。可以使用它跟踪各个软件模块的进入和退出时间戳,从而收窄导致性能瓶颈的软件范围。
例如,如果一个应用程序与 DB2® 数据库交互,用户发现它的响应很慢,那么可以编写一个 D 脚本,跟踪应用程序内花费的时间和执行数据库操作花费的时间。
可以使用下面这样的 D 脚本完成这个任务:
pid$target::sqlastrt:entry
{
self->time=timestamp;
}
pid$target::sqlastop:entry
/self->time != 0/
{
printf("Time spent in sql call %d nsecs\n",timestamp - self->time);
self->time=0;
}
|
这里的 self 标识符声明一个线程局部变量 time。应用程序中的每个线程都有自己的局部变量 time,因此每个应用程序线程可以跟踪自己的时间。
AIX ProbeVue 使用示例
下面的探测脚本使用用户函数进入探测跟踪 malloc 例程,显示分配的内存量。
#!/usr/bin/probevue
char * malloc(int size);
@@uft:$1:*:malloc:entry
{
printf("malloc(%d)\n",__arg1);
}
|
调用这个脚本,传递希望跟踪其 malloc 调用的进程的进程 ID。
__arg1、__arg2、__arg3 等是内置的进入类变量,它们引用传递给函数的参数。$1 替换为传递给脚本的进程 ID。
要想查看 malloc 调用的调用堆栈,可以在前面的脚本中调用 stktrace() 探测函数。
结束语
本文简要介绍了动态跟踪工具,通过示例演示了几个探测的使用方法。在 Solaris 和 AIX 系统上还有许多探测可用,它们有助于诊断生产系统中的问题。在 Solaris 和 AIX 的手册中可以找到所有探测的完整列表及其使用方法。
参考资料 学习
讨论
关于作者  | |  | Manjunath Karikatti 是 IBM India Software Labs 的软件工程师。他当前在 TXSeries 开发团队工作。 |
对本文的评价
|