systemtap系列之系统诊断
SystemTap 是监控和跟踪运行中的 Linux 内核的操作的动态方法。SystemTap 没有使用工具构建一个特殊的内核,而是允许您在运行时动态地安装该工具。它通过一个名为Kprobes 的应用编程接口(API)来实现该目的
SystemTap 的基本流程
SystemTap 的基本流程,涉及到 3 个交互实用程序和 5 个阶段。流程首先从 SystemTap 脚本开始。您使用 stap 实用程序将 stap 脚本转换成提供探针行为的内核模块。stap 流程从将脚本转换成解析树开始 (pass 1)。然后使用细化(elaboration)步骤 (pass 2) 中关于当前运行的内核的符号信息解析符号。接下来,转换流程将解析树转换成 C 源代码 (pass 3) 并使用解析后的信息和 tapset 脚本(SystemTap 定义的库,包含有用的功能)。stap 的最后步骤是构造使用本地内核模块构建进程的内核模块 (pass 4)。
有了可用的内核模块之后,stap 完成了自己的任务,并将控制权交给其他两个实用程序 SystemTap:staprun 和 stapio。这两个实用程序协调工作,负责将模块安装到内核中并将输出发送到 stdout (pass 5)。如果在 shell 中按组合键 Ctrl-C 或脚本退出,将执行清除进程,这将导致卸载模块并退出所有相关的实用程序。
从内核态/用户态来了解如下:
systemtap工作原理
SystemTap 脚本编写
SystemTap 脚本由探针和在触发探针时需要执行的代码块组成。
使用-L可以测试某条语句是否是正确的:
stap -L 'process("/usr/local/mysql/libexec/mysqld").function("apply_event")'关于探针
基本参考如下:
探针类型 说明
begin 在脚本开始时触发
end 在脚本结束时触发
kernel.function("sys_sync") 调用 sys_sync 时触发
kernel.function("sys_sync").call 同上
kernel.function("sys_sync").return 返回 sys_sync 时触发
kernel.syscall.* 进行任何系统调用时触发
kernel.function("*@kernel/fork.c:934") 到达 fork.c 的第 934 行时触发
module("ext3").function("ext3_file_write") 调用 ext3 write 函数时触发
timer.jiffies(1000) 每隔 1000 个内核 jiffy 触发一次
timer.ms(200).randomize(50) 每隔 200 毫秒触发一次,带有线性分布的随机附加时间(-50 到 +50)变量和类型
SystemTap 允许定义多种类型的变量,类型可以从上下文推断得出的,因此不需要使用类型声明。在 SystemTap 中,您可以找到数字(64 位签名的整数)、整数(64 位)、字符串和字面量(字符串或整数)。还可以使用关联数组和统计数据。
表达式
SystemTap 提供 C 语言中常用的所有必要操作符,并且用法也是一样的。还可以找到算术操作符、二进制操作符、赋值操作符和指针废弃。还看到从 C 语言带来的简化,其中包括字符串连接、关联数组元素和合并操作符。
语言元素
在探针内部,SystemTap 提供一组类似于 C 一样易于使用的语句。需要注意的是,尽管该语言允许您开发复杂的脚本,但每个探针只能执行 1000 条语句(这个数量是可配置的)。许多元素和 C 中的一样,尽管有一些附加的东西是特定于 SystemTap 的。
语句 说明
if (exp) {} else {} 标准的 if-then-else 语句
for (exp1 ; exp2 ; exp3 ) {} 一个 for 循环
while (exp) {} 标准的 while 循环
do {} while (exp) 一个 do-while 循环
break 退出迭代
continue 继续迭代
next 从探针返回
return 从函数返回一个表达式
foreach (VAR in ARRAY) {} 迭代一个数组,将当前的键分配给 VAR
SystemTap 提供许多内部函数,这些函数提供关于当前上下文的额外信息。可以使用 caller() 识别当前的调用函数,使用 cpu() 识别当前的处理器号码,以及使用 pid() 返回 PID。统计系统调用 sys_sync
调用内核系统调用 sys_sync 时触发。当该探针触发时,计算调用的次数,并发送这个计数以及表示调用进程 ID(PID)的信息。首先,声明一个任何探针都可以使用的全局值(全局名称空间对所有探针都是通用的),然后将它初始化为 0。其次,定义探针,它是一个探测内核函数 sys_sync 的条目。与探针相关联的脚本将递增 count 变量,然后发出一条消息,该消息定义调用的次数和当前调用的 PID。
global count=0
probe kernel.function("sys_sync") {
count++
printf( "sys_sync called %d times, currently by pid %d\n", count, pid() );
}
执行stap syscall.stp
stap命令与staprun命令
命令的区别在于:
stap命令的操作对象是stp文件或script命令等,而staprun命令的操作对象是编译生成的内核模块。监控所有系统调用
脚本如下:
global syscalllist
probe begin {
printf("System Call Monitoring Started (10 seconds)...\n")
}
probe syscall.* {
syscalllist[pid(), execname()]++
}
probe timer.ms(10000) {
foreach ( [pid, procname] in syscalllist ) {
printf("%s[%d] = %d\n", procname, pid, syscalllist[pid, procname] )
}
exit()
}
统计10秒内系统调用
脚本如,代码中定义了一个10秒的定时器:
global syscalllist
probe begin {
printf("System Call Monitoring Started (10 seconds).../n")
}
probe syscall.*
{
syscalllist[pid(), execname()]++
}
probe timer.ms(10000) {
foreach ( [pid, procname] in syscalllist ) {
printf("%s[%d] = %d/n", procname, pid, syscalllist[pid, procname] )
}
exit()
}收集网络包长度
参考如下脚本:
global recv, xmit
probe begin {
printf("Starting network capture (Ctl-C to end)\n")
}
probe netdev.receive {
recv[dev_name, pid(), execname()] <<< length
}
probe netdev.transmit {
xmit[dev_name, pid(), execname()] <<< length
}
probe end {
printf("\nEnd Capture\n\n")
printf("Iface Process........ PID.. RcvPktCnt XmtPktCnt\n")
foreach ([dev, pid, name] in recv) {
recvcount = @count(recv[dev, pid, name])
xmitcount = @count(xmit[dev, pid, name])
printf( "%5s %-15s %-5d %9d %9d\n", dev, name, pid, recvcount, xmitcount )
}
delete recv
delete xmit
}
按 Ctrl-C 时退出脚本,然后发送捕获的数据。
- 柱状显示数据
以柱状图的形式显示数据,将数据捕获到一个名为 histogram 的聚合中。然后,使用 netdev 接收和发送探针以捕捉包长度数据。当探针结束时,使用 @hist_log 提取器以柱状图的形式呈现数据。@hist_log 提取器是一个以 2 为底数的对数柱状图.
代码如下:
global histogram
probe begin {
printf("Capturing...\n")
}
probe netdev.receive {
histogram <<< length
}
probe netdev.transmit {
histogram <<< length
}
probe end {
printf( "\n" )
print( @hist_log(histogram) )
}
- systemtap其他作用
定位函数位置
stap -l 'process("/lib/x86_64-redhat-linux6E/lib64/libc.so").function("printf")'
- 查找内核代码中的函数
如果内核函数sys_open被新定义,可以通过如下命令进行查找。
#stap -L 'kernel.function("sys_open")'
kernel.function("SyS_open@fs/open.c:1036") $filename:long int $flags:long int $mode:long int
- 参考
linuxperformance-tools
http://www.brendangregg.com/linuxperf.html
Linux 自检和 SystemTap
https://www.ibm.com/developerworks/cn/linux/l-systemtap/
Linux 下的一个全新的性能测量和调式诊断工具 Systemtap, 第 3 部分: Systemtap
https://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/
systamtap编程参考
https://sourceware.org/systemtap/langref/
systemtap有用的IO监控脚本
https://segmentfault.com/a/1190000000680628
SystemTap使用技巧【一】
http://blog.csdn.net/wangzuxi/article/details/42849053
调试内核模块
http://blog.chinaunix.net/uid-14528823-id-4726046.html