一、systemtap介绍
SystemTap是一个强大的调试工具,是监控和跟踪运行中的Linux 内核的操作的动态方法,确切的说应该是一门调试语言,因为它有自己的语法,也有解析、编译、运行等过程(准确的说有五个阶段)。
但它主要解决的问题是收集Linux内核或者用户进程的信息,主要目的是调试。
gdb、kgdb同是linux最强大的调试器,gdb和SystemTap不是竞争关系,而是互补关系,gdb能做的事情SystemTap做不到,比如断点/watch变量等等这些SystemTap都做不到,
而SystemTap能做的事情gdb做不到或者非常麻烦才做到,比如很方便查看内核调试栈/嵌入C语言等等gdb就很难。
SystemTap 是一款诊断Linux系统性能的工具,可以跟踪内核以及用户态程序中的任意函数、syscall、语句甚至指令,可以用来动态地收集调试和性能信息的工具,不需要我们重新编译、重启内核。缺点:用户需要自己编辑脚本测试文件。
假如现在有这么一个需求:需要获取正在运行的 Linux 系统的信息,如我想知道系统什么时候发生系统调用,发生的是什么系统调用等这些信息,有什么解决方案呢?
- 最原始的方法是,找到内核系统调用的代码,加上我们需要获得信息的代码、重新编译内核、安装、选择我们新编译的内核重启。这种做法对于内核开发人员简直是梦魇,因为一遍做下来至少得需要1个多小时,不仅破坏了原有内核代码,而且如果换了一个需求又得重新做一遍上面的工作。所以,这种调试内核的方法效率是极其底下的。
- 之后内核引入了一种Kprobe机制,可以用来动态地收集调试和性能信息的工具,是一种非破坏性的工具,用户可以用它跟踪运行中内核任何函数或执行的指令等。相比之前的做法已经有了质的提高了,但Kprobe并没有提供一种易用的框架,用户需要自己去写模块,然后安装,对用户的要求还是蛮高的。
- systemtap 是利用Kprobe 提供的API来实现动态地监控和跟踪运行中的Linux内核的工具,相比Kprobe,systemtap更加简单,提供给用户简单的命令行接口,以及编写内核指令的脚本语言。对于开发人员,systemtap是一款难得的工具。
二、systemtap安装
1.安装步骤:
查看环境:uname -r
yum install kernel-devel
yum install systemtap
install kernel/glib debug:
glibc-debuginfo-2.17-106.el7_2.8.x86_64.rpm
glibc-debuginfo-common-2.17-106.el7_2.8.x86_64.rpm
kernel-debuginfo-3.10.0-327.28.3.el7.x86_64.rpm
kernel-debuginfo-common-x86_64-3.10.0-327.28.3.el7.x86_64.rpm
ref: http://debuginfo.centos.org/7
下载kernel-debuginfo 以及kernel-debuginfo-common,要下载对应内核版本的(错误版本会提示semantic error: no match等报错);
测试是否安装ok:
1.1 测试stap
[root@root conf]# stap -V
Systemtap translator/driver (version 3.2/0.168/0.166, commit release-3.1-210-g3decfea497ac + changes)
Copyright (C) 2005-2017 Red Hat, Inc. and others
This is free software; see the source for copying conditions.
tested kernel versions: 2.6.18 ... 4.11
enabled features: JAVA PYTHON2 LIBSQLITE3 LIBXML2 NLS
1.2 测试kernel-debuginfo
[root@root conf]# stap -L 'kernel.function("printk")' kernel.function("printk@kernel/printk.c:1693") $fmt:char const* $args:va_list
1.3 测试glibc-debuginfo
[root@kuber-worker-2 conf]# stap -L 'process("/lib64/libc.so.6").function("malloc")' process("/usr/lib64/libc-2.17.so").function("malloc")
1.4 其他测试示例 打印hello systemtap
以root用户或者具有sudo权限的用户运行以下命令:
[root@root conf]# stap -ve 'probe begin { log("hello systemtap!") exit() }' Pass 1: parsed user script and 480 library scripts using 193340virt/58416res/2252shr/56848data kb, in 380usr/40sys/420real ms. Pass 2: analyzed script: 1 probe, 2 functions, 0 embeds, 0 globals using 194660virt/59996res/2488shr/58168data kb, in 20usr/0sys/14real ms. Pass 3: using cached /root/.systemtap/cache/2f/stap_2fdbfdf450adaf1c0592d368ccee2be4_1142.c Pass 4: using cached /root/.systemtap/cache/2f/stap_2fdbfdf450adaf1c0592d368ccee2be4_1142.ko Pass 5: starting run. hello systemtap! Pass 5: run completed in 10usr/60sys/413real ms.
1.5 其他测试示例 输出4s内所有open系统调用的信息
创建systemtap脚本文件test2.stp:
#!/usr/bin/stap probe begin { log("begin to probe") } probe syscall.open { printf ("%s(%d) open (%s) ", execname(), pid(), argstr) } probe timer.ms(4000) # after 4 seconds { exit () } probe end { log("end to probe") }
将该脚本添加可执行的权限 chmod +x test2.stp
,使用./test2.stp
运行该脚本,即可输出4s内所有open系统调用的信息,打印格式为:进程名(进程号)打开什么文件。
大家可以自行去测试,如果两个示例都能正确运行,基本上算是安装成功了!
systemtap 工作原理
systemtap 的核心思想是定义一个事件(event),以及给出处理该事件的句柄(Handler)。
当一个特定的事件发生时,内核运行该处理句柄,就像快速调用一个子函数一样,处理完之后恢复到内核原始状态。
这里有两个概念:
- 事件(Event):systemtap 定义了很多种事件,例如进入或退出某个内核函数、定时器时间到、整个systemtap会话启动或退出等等。
- 句柄(Handler):就是一些脚本语句,描述了当事件发生时要完成的工作,通常是从事件的上下文提取数据,将它们存入内部变量中,或者打印出来。
Systemtap 工作原理是通过将脚本语句翻译成C语句,编译成内核模块。
模块加载之后,将所有探测的事件以钩子的方式挂到内核上,当任何处理器上的某个事件发生时,相应钩子上句柄就会被执行。
最后,当systemtap会话结束之后,钩子从内核上取下,移除模块。整个过程用一个命令 stap
就可以完成。
上面只是简单的原理,更多背后的机理参考网上资料和相应的论文。
systemtap官网: https://sourceware.org/systemtap/documentation.html
三、实战应用及常用工具瓶颈
1.故障处理中遇到的困境:
1.1 iostat等命令看到的是系统级的统计,比如下例中我们看到/dev/vdb很忙,如果要追查是哪个进程导致的I/O繁忙,应该怎么办?
进程的内核数据结构中包含了I/O数量的统计:
struct task_struct {
另外还有一个常用的命令 iotop 也可以观察进程的动态I/O:
pidstat 和 iotop 也有不足之处,它们无法具体到某个硬盘设备,如果系统中有很多硬盘设备,都在忙,而我们只想看某一个特定的硬盘的I/O来自哪些进程,这两个命令就帮不上忙了。
怎么办呢?
2.SystemTap查找方法:
可以用上万能SystemTap工具。比如:我们希望找出访问/dev/vdb的进程,可以用下列脚本,它的原理是对submit_bio下探针:
[root@template ~]# cat io_vdb.stap #! /usr/bin/env stap global device_of_interest probe begin { device_of_interest = $1 printf ("device of interest: %x ", device_of_interest) } probe kernel.function("submit_bio") { dev = $bio->bi_bdev->bd_dev if (dev == device_of_interest) printf ("[%s](%d) dev:0x%x rw:%d size:%d ", execname(), pid(), dev, $rw, $bio->bi_size) }
这个脚本需要在命令行参数中指定需要监控的硬盘设备号,得到这个设备号(fc,10)的方法如下:
十六进制: (fc,10)---->(主设备号Major number(12-bit),次设备号Minor number(20-bit)) 需要转换为10进制作为io_vdb.stap的入参数($1)
十进制:(252,10)
stat /dev/vdb:
cat /proc/devices:
3.SystemTap 查证结果:
3.1 查看某个分区或目录io访问情况:
脚本执行命令:./io_vdb 264241171
(264241171为 fc00013的十进制表示,因为 fc00010为/dev/vdb,我们的测试目录为/dev/vdb3,所以对应的次设备号变为13(fc,13))
执行grep命令进行验证io实际执行情况:
执行前:
执行后:
通过结果,我们看到是进程号为8446的grep命令在对/dev/vdb3进行读操作(rw:0)。
Enjoyjing youeself!
参考文章:
http://linuxperf.com/?cat=4
http://www.xbwolf.com/507
http://blog.csdn.net/wangzuxi/article/details/42849053