欢迎加入IT云增值在线QQ交流群:342584734
分类:
2006-04-24 13:25:08
本文旨在帮助新用户学习如何使用DTrace在Sun Solaris 10系统中收集及使用系统和应用程序信息。文中介绍了创建D脚本的方法,并使大家明白,每位系统管理员和开发人员都应清楚何时要学习、使用DTrace。书中提供了一些例子,这将帮助读者开始使用DTrace。
读完本文之后,读者将能够创建脚本,以收集运行中应用程序的有用信息,这些信息也可用以提升Solaris的性能。DTrace介绍
DTrace是Sun Solaris内置的全面动态追踪工具,可供管理员和开发人员检查用户程序和操作系统自身的行为。您可利用DTrace浏览系统,以了解它的工作方式、追踪在软件的许多层面中存在的性能问题,或者探明异常行为的原因。在软件开发系统中使用该工具是安全的,且不必重启系统或应用程序。
DTrace动态地修改操作系统内核和用户进程,以记录特别位置的数据,这称为probe(s)。probe就是位置或活动,DTrace可将其与请求绑定在一起,以执行一组活动,如记录堆栈追踪、时间戳或函数的实参。probe就像遍布于Solaris系统中各个感兴趣位置的可编程传感器。DTrace probe来自一组称为“provider”的内核模块,每个模块都执行某种特殊方法的来创建probe。
DTrace包含一中称为“D”的脚本语言,它是专门为动态追踪而设计的。使用D语言,很容易编写动态启动probe、收集信息及处理信息的脚本。D脚本使用户可以方便地同其他人员共享知识和故障检修方法。Solaris 10中包含大量有用的D脚本,在Sun公司的BigAdmin 站点:sun.com/bigadmin/content/dtrace/和OpenSolaris项目站点:opensolaris.org/os/community/ dtrace/上可以找到更多脚本。
D脚本介绍
首先,对D脚本的构建进行简单讨论。D脚本由probe描述、predicate和action组成,如下所示:
prode description
/predicate/
{
actions
}
在执行D脚本时,在probe description中描述的probe就被启动了。当probe被激活且predicate计算为真时,就会执行动作语句。可将probe当作事件:当事件发生时,就激活probe。下面是一个简单的D脚本例子:
syscall::write:entry
/execname = = “bash”/
{
printf(“bash with pid %d called write system calln”,pid);
}
此处probe描述就是syscall::write:输入,它描述了写系统调用。predicate就是/ execname = = “bash”/。这将确认调用写系统调用的执行语句是否为bash shell。printf语句就是bash每次调用写系统调用时执行的动作。
probe描述
现在进一步了解probe描述。probe是使用四个字段描述的:provider、module、function和name。
l provider——指定要使用的实现方法。例如,syscall provider用以监控系统调用,而io provider用以监督磁盘IO。
l module和function——描述要观察的模块和函数。
l name——通常代表函数中的位置。例如,在输入函数时,使用名称输入实现功能。请注意,可以使用*和?通配符。空字段被解释为通配符(表1就是一些例子)。
probe描述 |
解释 |
syscall::open: entry |
open系统调用中的输入 |
syscall::open*: entry |
任何系统调用中以open(open和open64)开头的输入 |
syscall::: entry |
任何系统中被调用的输入 |
syscall::: |
系统调用提供者发布的所有probe |
表1:probe解释的例子
predicate语句
predicate可以是任何D表达式(表2演示了一些例子)。只要当predicate计算为真时,才执行动作。
predicate |
解释 |
cpu == 0 |
如果在cpu0上执行probe时为真 |
pid == 1029 |
如果激活probe的进程pid为1029,则为真 |
execname !=“ sched” |
如进程未被调度 (sched),则为真 |
ppid!= 0 && arg0 == 0 |
如果父进程id不为0,且第一个实参为0,则为真 |
表2:predicate的例子
action语句
action部分可以包含一系列以分号(;)隔开的动作命令。表3提供了一些例子。
action |
解释 |
printf() |
使用C格式的printf()命令打印 |
ustack() |
打印用户级堆栈 |
trace |
打印指定变量 |
表3:action的例子
请注意,predicate和action语句是可选的。如果predicate不存在,则总会执行action。如果action不存在,则打印被激活的probe的名称。
利用最少的信息就可开发有用的D脚本。在本文的下一部分,将专门从应用程序开发和系统管理两个视角介绍DTrace的使用方法。
DTrace开发人员指引
使用下列provider,可以获得应用程序开发人员感兴趣的信息类型:syscall、proc、pid、sdt和 vminfo。开发人员可以使用这些provider查看运行中进程,进程的创建和终止、LWP的创建和终止以及信号处理——本部分只介绍pid provider。
pid provider
pid provider从运行中进程的任何用户级函数中使用输入和返回。您可使用pid provider追踪系统上任何进程中的任何指令。pid provider的名称包括您感兴趣的正在运行的进程的pid。表4提供了一些使用pid provider的probe描述示例。
例子 |
解释 |
pid2439:libc:malloc: entry |
libc中进程id为2439的malloc函数的输入 |
pid1234:a.out:main: return |
从进程id为1234的主函数返回 |
pid1234:a.out:: entry |
进程id为1234的主程序中任何函数的输入 |
pid1234::: entry |
任何库中进程id为1234的任何函数的输入 |
表4:pid provider及相关Probe的例子
您可运行下列命令,以打印进程id1234调用的所有函数:
# dtrace –n pid1234:::entry
如下所示,同样的命令也可用在脚本中:
#!/usr/sbin/dtrace –s
/* The above line means that the script that follows needs to be
Interpreted using dtrace. D uses C – style comments.
*/
pid1234:::entry
{}
通过用进程pid替换1234,您使用该命令或脚本。
下列步骤可用于使用pid provider创建D脚本:
1. 现在试运行上述命令和脚本。您可看到,这并不容易配置。
2. 修改脚本,使用进程id作为形参。脚本将变成这样:
#!/usr/sbin/dtrace –s
pid$1:::entry
{}
3. 现在可以将进程id作为实参提供给脚本。请注意,如果不能给脚本提供一个且是惟一的实参,DTrace将提示错误,且无法执行。请记着给脚本添加执行权。
您可能已注意到,该脚本的输出太快,因此尚没有实际用处。D语言拥有一个优良的称为“Aggregation(聚合)”的构造函数,它用以收集内存中的所有详细信息,并打印摘要。聚合使您能够收集内存中的信息表。聚合具有如下构造函数:
@name[table indes(es)] =aggregate_function()
例如:
@count_table[probefunc] = count() ;
该聚合将收集信息,并将其归入以函数名命名的表中(probefunc是一个含有函数名的内置变量)。聚合函数count()追踪函数被调用的次数。其他常用的聚合函数有average、min、max和sum。
4. 下例将在您正在开发的脚本中添加聚合函数,以查看用户函数的摘要表:
#!/usr/sbin/dtrace –s
pid$1:::entry
{
@count_table[probeunc] = count() ;
}
该脚本将收集信息,然后将其归入表中,并且将持续运行,直至按下^ c(Control c键)。一旦脚本停止,DTrace将打印信息表。请注意,您不必编写任何代码来打印该表。DTrace将自动替您完成该动作。
您可能已注意到,probe并不是在运行该脚本时动态创建的。只要脚本一停止,probes就被禁用。您不必编写任何特殊代码以禁用这些probe。此外,DTrace将自动清理它分配的任何内存。您不必编写任何清理代码。
5. 通过修改toprobemod和probefunc索引,可以方便地修改该脚本,以收集含有函数名和库名的表。
#!/usr/sbin/dtrace –s
pid$1:::entry
{
@count_table[probemod,probefunc] = count();
}
6. 之后,查看每个函数耗费了多长时间。这可通过内置的时间戳变量而实现。您可以在输入中或函数的返回中创建probe,并根据从输入到返回的时间戳差异,计算耗费的时间。时间戳变量以纳秒为单位报告时间。以下就是修改后的脚本:
#!/usr/sbin/dtrace –s
pid$1:::entry
{
ts[probefunc] = timestamp;
}
pid$1:::return
{
@func_time[probefunc] = sum(timestamp – ts[probefunc]);
ts[probefunc] = 0;
}
注意,ts[]是数组,D已经自动替您声明并初始化了该数组。将变量置为0,这是节省空间的好方法。
7. 大多数情况该脚本都可正常运行。但是有一些边界异常。
l 异常情况1:监控函数输入和返回
由于活动中的正在运行的应用程序里创建probe,因此在运行D脚本时,程序很有可能正在执行函数。因此,虽然没有输入数据,但您仍可能看到函数的返回。通过向返回probe部分中添加/ts[ probefunc] != 0/的predicate,即可轻松解决此问题。该predicate将使您能够忽略上述边界情况。
l 异常情况2:多线程应用程序
第二个边界情况较为棘手,它涉及多线程应用程序。此时有可能造成竞争状态,即两个线程可能同时执行同一函数。在这种情况下,每个线程都需要ts[]的一个副本。DTrace通过self变量解决此问题。向self变量添加的任何数据都被设置为线程局部数据。
8.下列脚本已被修改,以处理这两种边界情况:
#!/usr/sbin/dtrace –s
pid$!:::entry
{
self - >ts[probefunc] = timestamp;
}
pid$1:::return
/self - >ts[probefunc]/
{
@func_time[probefunc] = sum(timestamp – ts[probefunc]);
ts[probefunc] = 0;
}
注意,/self->ts[ probefunc]/等同于/self->ts[ probefunc] != 0/。
对于大型应用程序而言,上述脚本将启用大量的probe(可能多达几十万个)。即使每个probe都是相当轻量的,但向活动中的正在运行的系统添加如此多的probe,将对应用程序的性能造成不良影响。
9. 通过修改probe描述,可以限制启用的probe数量。请参考表5中的例子。
probe描述 |
解释 |
pid$1:libc:: entry |
只限制指定的库 |
pid$1:a.out:: entry |
只限制非库函数的probe |
pid$1:libc:printf: entry |
只限制一个函数的probe |
表5:修改probe描述
10. 之后,您将看到如何监控进程开始到终止的整个过程。DTrace允许您利用e$ target变量和-c选项做到这一点。该脚本将统计指定应用程序调用libc函数的次数。
#!/usr/sbin/dtrace –s
pid$target:libc::entry
{
@[probefunc] = count();
}
11. 将该脚本存为libc_func.d,并运行该脚本。
# libc_fun.d –c “cat /etc/hosts”
12. 您可方便地用感兴趣的命令替换“cat/etc/ hosts”。
其他有用的脚本
还有一些示例脚本,可用以获取系统的附加信息。了解系统调用的次数、堆栈深度以及了解谁在运行什么,这将有助于您了解工作负荷,并发现可能存在的瓶颈。
1. 下列脚本将在系统进行写系统调用时,查找堆栈追踪。注意,您需要使用-c选项运行该脚本。
#!/usr/sbin/dtrace –s
syscall::write:entry
{
@[ustack()] = count();
}
2. 下列脚本统计在CPU中各个进程的运行次数。注意,当CPU运行时间切换到进程时,将激活sysinfo::: pswitch probe。需要几分钟后按下^ c(Control c)键。
#!/usr/sbin/dtrace –s
sysinfo:::pswitch
{
@[execname] = count();
}
3. 下列脚本在系统启动新进程时,将打印进程名称、pid和uid。注意,当新进程成功启动时,将激活proc:::exec- success。
#!/usr/sbin/dtrace –qs
proc:::exec-success
{
printf(“%s(pid=%d) started by uid - %d n”,execname,pid,uid);
}
DTrace系统管理员指引
每个系统管理员都会面对的任务,是那些预定义环境中运行的应用程序的行为或错误行为。这类信息可通过下列provider获得:syscall、proc、io、sched、sysinfo、vminfo、lockstat和profile。在这些provider中,syscall、proc、io和sched都是最容易使用的。使用这些provider,系统管理员可获得进程、线程、堆栈状态以及许多其他内核指标的有关信息。从syscall、proc、io和sched provider开始介绍较为容易。
syscall provider
这可能是要学习和使用的最重要的provider,因为系统调用是用户级应用程序和内核之间的主要通信渠道。通过了解其他信息中正在使用哪些系统调用,可建立系统利用指标,并识别可能的错误行为。使用syscall provider,可以很容易地识别谁在执行什么,以及某个操作耗费了多长时间,这有助于您识别系统错误行为的根本原因。
1. 使用下列脚本,可以在激活probe时列举probe每次出现的信息,且当进入正在执行close(2)系统调用的系统中时,提供有关系统调用的信息:
# dtrace –n syscall::close:entry
2. 要开始识别向特定进程发送了kill(2)信号的进程,可以使用下列脚本:
#!/usr/sbin/dtrace –s
syscall::kill:entry
{
trace(pid);
trace(execname);
}
3. 要确定webserver在read(2)上耗费的时间,可以使用下列脚本。注意,可以方便地修改该脚本,以将其用于其他进程。
#!/usr/sbin/dtrace –qs
BEGIN
{
printf(“sizettimen”);
}
syscall::read:return
/self - >start/
{
printf(“%dt%dn”,arg0 ,timestamp – self ->start);
self ->start = 0;
}
proc provider
该provider在进程和线程创建、终止以及发送信号时被激活。它是一种较为复杂的方法,用以注销probe,并将告诉您哪位用户向哪个进程发送了指定信号(3head)。
1. 追踪向当前在系统中运行的所有进程发送的所有信号。
#!/usr/sbin/dtrace –wqs
proc:::signal – send
{
printf(“%d was sent to %s by ”,args[2],args[1]->pr_fname);
system(“getent passwd %d | cut –d: -f5”,uid);
}
2. 在脚本中添加条件语句(/args[ 2] == SIGKILL/),并向来自不同用户的不同进程发送SIGKILL信号。
#!/usr/sbin/dtrace –wqs
proc:::signal –send
/args[2 ] = = SIGKILL/
{
printf(“SIGKILL war sent to %s by ”,args[1]->pr_fname);
system(“getent passwd %d | cut –d: -f5”,uid);
}
这里您可看到引入了pr_fname,它是接收进程的psinfo_t结构的一部分。
sched provider
该provider动态地追踪调度事件。使用该provider,可以了解线程何时及为何睡眠、运行、修改优先权或者唤醒其他线程。
下列脚本确定CPU耗费在I/O等待和工作上的时间。它还中断I/O进程,并指明在I/O等待时间内,数据由StarOffice检索。可以方便地修改该脚本,以适应您的特殊情形。
#!/usr/bin/dtrace –sq
sched:::on – cpu
/execname = = “soffice.bin”/
{
self->on = vtimestamp;
}
sched:::off –cpu
/self->on/
{
@time[“
self->on = 0
}
io provider
该provider监督磁盘输入和输出(I/O)子系统。使用io provider,您可深入了解ofiostat(1M)输出。io probe在所有的磁盘请求时都可被激活,包括NFS服务,但元数据请求除外。
下例基于/usr/demo/dtrace/iosnoop.d脚本。使用该脚本可追踪哪个设备上有哪些文件正在被访问,并可确定正在执行的任务是否为读操作或写操作。
#!/usr/bin/dtrace –qs
BEGIN
{
Printf(“%10s %58s %2sn”,”DEVICE”,”FILE”,”RW”);
}
io:::start
{
printf(“%10s %58s %2sn”,args[1]->dev_statname,
args[2]->fi_pathname,args[0]->b_flags & B_READ ? “R” : “W”);
}