LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1511|回复: 0

OpenSolaris新特性解析之五: dtrace2-语法

[复制链接]
发表于 2009-1-14 15:22:31 | 显示全部楼层 |阅读模式
该系列前面的帖子:

OpenSolaris新特性解析之一:Opensolaris之前身今世    http://bbs.chinaunix.net/viewthread.php?tid=1221539
OpenSolaris新特性解析之二:ZFS                              http://bbs.chinaunix.net/viewthread.php?tid=1234907
OpenSolaris新特性解析之三:SMF                             http://bbs.chinaunix.net/viewthread.php?tid=1273723
OpenSolaris新特性解析之四:FMA                             http://bbs.chinaunix.net/viewthread.php?tid=1295237
OpenSolaris新特性解析之五: dtrace1-基础                http://bbs.chinaunix.net/viewthread.php?tid=1315928


这篇帖子是Dtrace的第二部分,准备比较系统的介绍下Dtrace,最好在看这篇帖子之前先看看它的上一篇OpenSolaris新特性解析之五: dtrace1-基础

一 架构:
下面这张图是Dtrace的架构


我们从上向下看,a.d和b.d就是我们的dtrace脚本,比如我们在上一篇里面使用的那些脚本,他们都是用.d来结尾的,dtrace(1M)就是我们在终端里面使用的dtrace命令,这个是dtrace的用户接口,它的作用是把我们的dtrace脚本读进来,然后转换为字节码,就像JAVA的字节码一样,然后libdtrace是一个库,在这个库里面实现了一个虚拟机,这个虚拟机把字节码读进来进行解释执行,通过dtrace(7D)这个伪设备把执行结果传入内核,下面的就是dtrace的内核部分,大家可以看到dtrace的最终执行是在内核里面进行的,这就是dtrace功能强大的原因。另外需要注意的是这种虚拟机的构架,我们知道很多调试工具,如果只有用户态,那必然功能有限,如果有内核态,可以调试内核,则需要较多的经验才能安全使用,否则容易导致kernel panic。但是dtrace的虚拟机是放在用户态的,如果你的调试脚本有问题,它是先经过虚拟机,导致虚拟机出现问题,而虚拟机在用户态,所以保护了系统的安全。再下面的比如sysinfo,vminfo等等就是dtrace的探测点的内核提供模块了,这些模块提供了这些探测点。

二:语法
如果看过上一篇帖子,可能对语法都已经有一定的感受了,比较简单,和C语言差不多。

1。探测点和谓词
探测点实际上在上一篇帖子里面已经描述过了,探测点随着solaris版本的更新在不断增多,以后探测点还会做到Mysql,java里面,我们还知道用户还可以自己创建探测点,比如下面的这个例子
                                profile:::tick-1msec
{
stack()}
stack是dtrace的内置函数,用于打印出solaris的内核栈,而profile:::tick-1msec就是我们自己创建的探测点,这个探测点是profile这个内核模块提供的,用于定时,tick-1msec制定这个探测点每1msec激活一次,探测点的详细说明见上一篇帖子。在这里,也就是说让系统每1msec打印一次内核栈。
dtrace的语句总是用下面的方式来组织的
probe description
/predicate/
{
  actions
}
大家可以结合上面的例子来看下这个结构,例子里面没有的东西是predicate这个部分,也就是谓词部分,这个部分相当于流程控制语句,因为 dtrace里面没有例如if这样的流程控制语句,而是用谓词来完成类似的功能。下面是一个例子,这个例子用于                                计算一个下落物体的速度并且打印出他花了多长时间速度达到50 m/s,大家可以看看里面的谓词用法。                                #!/usr/sbin/dtrace-qs
dtrace:::BEGIN
{
        msecond= 0;
        speed= 0;
}
profile:::tick-1msec
/speed< 50/
{
        speed= 10/2*msecond/1000;
       msecond++;
}
profile:::tick-1msec
/speed>=50/
{
       printf("0 to %dm/s in %d milli seconds\n", speed, msecond-1);
       exit(0);
}
下面是探测点的内核提供者的作用,大家结合着上面的图来看
syscall: 系统调用相关的探测
proc: 进程LWP相关的探测
sched: 调度相关的探测
io: io方面的探测
mib: 网络方面的探测
profile: 可以用于我们调优
fbt: 内核调试方面的探测
lockstat: 锁方面的探测
vminfo: 提供类似vmstat发面的数据探测


2.一些内置函数
trace,printf,printa都是打印函数,trace和printf的用法和C语言里面的用法一样,printa用于打印聚合,聚合的初步概念可以先看下前面的那个帖子。
stack用于打印内核栈,ustack()用于打印用户栈
exit退出dtrace
copyin,copyinstr,copyout,copyoutstr用于在内核和用户态之间传递数据,因为dtrace是在内核态运行的,如果需要用户态的数据,可以用copyin,copyinstr来将用户态的数据拷入,比如上一篇帖子里面的fileopenedd.d那个例子

3.数据类型和运算符
D语言和C语言很类似,但是比C语言还要小,它支持的数据类型有char,short,int,long,long long,float,double,long double,string
支持的运算符有+,-,*,/,%,>, >=, <, <=, ==, !=,&&, ||, ^^,=, +=

4.变量
D语言里面的变量不需要声明,可以直接使用,这点又有点像VB,默认的变量属性都是全局的,比如下面的例子
BEGIN
{
   x = 123;
   exit();

}
END
{
   trace(x);
}
另外用得较多的是线程变量,用self->来标注,比如self->x,我们可以看看下面这个例子
syscall::read:entry
{
    t = timestamp;
}
sycall::read:return
/t != 0/
{
  printf(“%d/%d spent %d secs in read\n”, pid, tid, timestamp –t);
}
这个例子用于测量系统中每一个线程在read这个系统调用里面所花费的时间,我们先来看探测点syscall::read:entry和 sycall::read:return,syscall是内核模块,read是系统调用的名字,entry和return表明了这两个探测点被激活的时间点是在read被调用和返回的时候。
syscall::read:entry
{
    t = timestamp;
}
里面的timestamp是dtrace的内置变量,用于取当前的时间,这个就是说当read被调用的时间点
sycall::read:return
/t != 0/
{
  printf(“%d/%d spent %d secs in read\n”, pid, tid, timestamp –t);
}
先来看谓词,这个谓词是为了防止下面这种情况,当dtrace启动的时候,某个线程已经在read里面了,所以entry那段并未被执行,而当这个线程退出read的时候,return被执行,但这个时候t=0(因为entry那段并没有被执行),所以return里面的timestamp-t的结果是错误的,并不是在read里面的真实执行时间。
  printf(“%d/%d spent %d secs in read\n”, pid, tid, timestamp –t);这句我们看到让系统打印该线程的pid和tid,也就是进程号和线程好,然后打印出花费的时间。

大家可以看看这段程序有什么问题?
大家可以看到t这个变量是多个线程共享的,这样当1线程进去后改了t后,但是还没有退出read,这个时候线程2进入了read,又把t改了,所以我们需要把t改成线程变量,每个线程都有自己的版本
syscall::read:entry
{
  self -> t = timestamp;
}
sycall::read:return
/self -> t != 0/
{
  printf(“%d/%d spent %d secs in read\n”, pid, tid, timestamp –
   t);
}


5.内置变量
pid: 进程ID
execname: 线程对应的可执行程序的名字
timestamp: 时间点
probefunc:探测点里面函数的名字,如上面的read。
下面有个例子,大家看看应该就很清楚了
syscall::write:entry
/execname=="firefox-bin"/
{

@[probefunc] = count();
}
@是聚合,下面介绍

6.聚合
聚合,怎么讲呢,这个概念比较难理解,我们线把它当成一个数组,比如@a[index]
其中a是聚合的名字,@表明这是一个聚合,index可以理解成聚合的数组下标。
聚合有自己的函数,比如上面的count,上面的那个例子是打印出firefox调用了那些函数(聚合下标),每个函数调用了多少次(count),大家可以看到聚合和数组的区别,每次调用count的时候,是在聚合对应的上一个值的基础上+1,对应是指函数名要一样,推荐大家把这里的每一个例子都真正执行下
聚合函数有
  count() : 求调用次数
  sum() :求和
  avg() : 求平均
  min() : 求最小值
  max(): 求最大值
  quantize() : 进行概率统计
比如下面这个例子
syscall::read*:entry
{
  self ->ts=timestamp;
}
syscall::read*:return
/self -> ts/
{
  @time[execname] = quantize(timestamp – self->ts);
  self->ts = 0;
}
在我的机器上执行结果是
  firefox-bin                                       
           value  ------------- Distribution ------------- count   
            1024 |                                         0        
            2048 |@@@@@@@@@@@@@@@@@@@@@@@@@                223      
            4096 |@@@@@@@@@@@@@@                           130      
            8192 |@                                        6        
           16384 |                                         0        

  Xorg                                             
           value  ------------- Distribution ------------- count   
             512 |                                         0        
            1024 |                                         7        
            2048 |@@@@@@@@@@@@@@@@@                        374      
            4096 |@@@@@@@@@@@@@@@@@@@@@                    448      
            8192 |@                                        27      
           16384 |                                         2        
           32768 |                                         0        
大家可以看到对于浏览器执行的read,执行时间在1024-2028之间,共发生了223次,结果里面的@的数量表明了次数的多少,执行时间在2048-4096之间有130次
而对于xorg也有自己的结果。

这部分就介绍到这里,这部分是最常用的内容,可以起到入门使用的作用。更加详细的东西,大家可以看附件。dtrace这部分并没有完,还有一篇帖子这部分就完全写完了。













dtrace用户手册.pdf (1.83 MB)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表