目录

2.5 Dtrace

今天我们来讲解第一个动态追踪技术 DTrace, 它是动态追踪技术的鼻祖

1. Dtrace 简介

Solaris 系统的 DTrace 是动态追踪技术的鼻祖,它提供了一个通用的观测框架,并可以使用 D 语言进行自由扩展。

DTrace 的工作原理如下图所示。它的运行常驻在内核中,用户可以通过 dtrace 命令,把 D 语言编写的追踪脚本,提交到内核中的运行时来执行。

/images/linux_pf/dtrace_structure.png /images/linux_pf/dtrace_arch.png

DTrace 本身依然无法在 Linux 中运行。很多工程师都尝试过把 DTrace 移植到 Linux 中,这其中,最著名的就是 RedHat 主推的 SystemTap。

2. DTrace 语法

2.1 探针

provider:module:function:name:

  1. provider: 相关探针的集合
  2. module,function: 探针指示的代码位置的代号
  3. name: 探针的名字
  4. 可以使用通配符,"::" == “:*:”

provider

DTrace 包含的 provider 如下所示:

provider 作用
syscal 系统调用
vminfo 虚拟内存统计
sysinfo 系统统计
profile 任意频率的采样
sched 内核调度事件
proc 进程级别事件
io 块设备接口跟踪,即磁盘I/O
pid 用户级别动态跟踪
tcp TCP协议事件,连接、发送和接收
ip IP 协议事件,发送和接收
fbt 内核级别动态追踪
高级语言的 provider

参数

探针通过一组称为参数的变量来提供数据。例如系统调用 syscal 给每一个系统调用都做了入口(entry)和返回(return)探针。这组参数变量如下:

  1. 入口: arg0….argN,表示系统调用的参数
  2. 返回: arg0 或 arg1,表示返回值,errno 也会设置

2.2 D 语言

D 语言定义了DTrace 的语法。DTrace 语句如下:

probe_description /predicate/ {action}:

  1. probe_description: 探针
  2. predicate: 可选的过滤表达式
  3. action: 探针触发时执行的操作,分号分隔的语句
1
2
proc:::exec-success /execname == "httpd"/ {trace{pid};}
# exec-success 用于跟踪新进程的创建和系统调用 exec() 的执行

内置变量

内置变量用来计算和判断

变量 描述
execname 执行在CPU上的进程名
uid 执行在CPU上的用户ID
pid 执行在CPU上的进程PID
timestamp 自启动以来的纳秒数
vtimestamp CPU上的线程时间,单位是纳秒
arg0..N 探针参数(uint64_t)
args[0]…[N] 探针参数(类型化的)
curthread 指向当前线程内核结构的指针
probefunc 探针描述的函数组件
probename 当前探针名称
curpsinfo 当前进程信息

变量类型

类型 前缀 作用域 开销 多CPU安全 赋值示例
聚合变量 @ 全局 @x = count();
带键聚合变量 @[] 全局 @x[pid] = count();
从句局部变量 this-> 从句实例 非常低 this->x = 1;
线程局部变量 self-> 线程内 中等 self->x = 1;
标量 全局 中下 x = 1;
关联数组 全局 中上 x[y] = 1

说明:

  1. 线程局部变量: 作用域线程内,像时间戳这样的数据容易与线程关联
  2. 从句局部变量: 用于中间计算,只在针对同一探针描述的 action 子句有效
  3. 聚合变量: 可以由 CPU 单独计算汇总后在传递到用户空间

action

action 作用
trace(arg) 打印arg
printf(format, arg…) 格式化输出
stringof(addr) 返回来自内核空间的字符串
copyinstr(addr) 返回用户空间地址的字符串
内核会执行一次从用户空间到内核空间的复制
stack(count) 打印内核级别栈追踪,如果有 count 按 count 截断
ustack(count) 打印用户级别栈追踪,如果有 count 按 count 截断
func(pc) 从内核程序计数器,返回内核函数名
ufunc(pc) 从用户程序计数器,返回用户函数名
exit(status) 退出DTrace并返回状态

聚合变量的特有的 action

action 作用
trunc(@agg, count) 截断聚合变量
删除全部键,或者按照 count 指定的键数目截断
clear(@agg) 删除聚合变量的值,键保留
printa(format, @agg) 格式化打印聚合变量
count() 发生计数
sum(value) value 求和
min(value)
max(value)
quantize(value) 用 2 的幂次方直方图统计 value
lquantize(value,min,max,step) 用给定最下值,最大值和步进值做线性直方图记录 value
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 显示系统调用 read(),返回的尺寸,使用2的幂次直方图显示
> dtrace -n 'syscall::read:return { @["rval (bytes)"] = quantize(arg0); }'

# 跟踪系统调用 open(),打印进程名和文件路径名
> dtrace -n 'syscall::open:entry { printf("%s, %s", execname, copyinstr(arg0)); }'

# 按进程名归纳所有的 CPU 交叉调用
> dtrace -n 'sysinfo:::xcalls { @[execname] = count(); }'

# 按 99Hz 采样内核级栈
> dtrace -n 'profile:::profile-99 { @[stack()] = count() }'

2.3 DTrace 脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/sbin/dtrace -s

dtrace:::BEGIN
{
    printf("Tracing .... Hit Ctrl-C to end. \n")
}

io:::start
{
    this-size = arg[0]->b_bcount;
    @Size[pid, curpsinfo->pr_psargs] = quantize(this->size)
}

dtrace:::END
{
    printf("\n%8s  %s\n", "PID", "CMD")
    printa("%8d  %S\n%@d\n", @Size)
}