监控与性能分析系列:3)systemtap

来源:互联网 发布:mac鼠标右键怎么按 编辑:程序博客网 时间:2024/05/20 13:06
原理:
kprobes应用编程接口
SystemTap允许用户编写和重用一些简单的脚本来深入检查一个运行的Linux系统的活动。这些脚本可以用来提取数据、过滤数据和统计数据以快速和安全的方式,使得对复杂性能问题的诊断成为可能。
SystemTap背后的基本思想是事件,并且还有给他们绑定的handlers。当SystemTap运行脚本,它监控定义的事件,当该事件发生时,Linux内核就运行相应的handler作为一个快速子例程,然后再返回。
有几种事件:进入或退出一个函数,定时器到期,会话终止等等。handler中通常包括的工作有:从事件上下文提取数据,存储它们到内部变量,打印结果等。
SystemTap的处理过程:
1)首先检查脚本,根据存在的一些tapset库(通常在/usr/share/systemtap/tapset/),用tapset库中的定义来替代相应的定位到的tapsets。
2)然后将脚本翻译成C程序,根据它运行system C编译器来创建一个内核模块。systemtap包中包含了这个工具。
3)SystemTap装载这个模块,然后使能所有的探针(事件和相应的处理函数)。详见systemtap-runtime包中的starprun。
4)当事件发生时,相应的handlers就被执行。
5)当SystemTap会话终止,探针被disable,内核模块被卸载。
通过一个命令行程序stap来执行SystemTap。
一个事件和它的处理程序合起来称作一个探针。一个SystemTap可以有多个探针。
SystemTap脚本使得插入这些入侵的代码不需要重新编译原来的代码。

脚本文件使用.stp后缀。
SystemTap的probe代码格式:
probe ecent {statements}
还可以自定义函数:
function func_name(arguments) {statements}
probe event {func_name(arguments)}

SystemTap的事件可以分为两大部分:同步的和异步的。
1)同步事件:一个同步事件发生在任何一个进程在内核代码中一个特定位置执行一条指令的时候。更多的上下文数据可以使用。
同步事件包括:syscall.system_call,vfs.file_operation对于VFS的操作,kernel.function("fucntion")内核函数如sys_open,还可以使用*作为通配符跟踪一个内核源文件中的函数如*@net/socket.c,kernel.trace("tracepoint")静态探针在内核中的特定事件如kfree_skb,module("module").function("function")模块中的函数。
2)异步事件:不绑定在代码中特定的一条指令上。包括:begin当SystemTap会话启动时,end当SystemTap会话结束时,timer events周期性执行timer.s(4),同类的还有ms、us、ns、hz、jiffies。


SystemTap脚本一直执行指导exit()函数被执行或者Ctrl+C手动终止。
SystemTap的一些函数:这些函数可以作为printf()的参数,execname()是调用被监控的内核函数或是系统调用的那个进程,pid()就是当前进程ID,tid()是当前线程ID,uid()是当前用户ID,cpu()是当前CPU号,gettimeofday_s()返回1970年以来的秒数,ctime()将秒数转换成日期,pp()描述当前被执行的探针的字符串,thread_indent()缩进,name就是指定的系统调用,target()在指定要跟踪的进程或命令时用stap script -x process ID或者stap script -c command然后再脚本中就可以使用target()来获取指定的目标process ID比如在脚本中可以判断if (pid() == target()) 再进行处理。
使用thread_indent()组织输出格式,类似于缩进的功能,这个函数返回一些trace数据包括时间戳(第一次调用thread_indent()到现在)、进程名、线程ID、哪个函数被调用了:
thread_indent.stp:
probe kernel.function("*@net/socket.c").call
{
  printf("%s -> %s\n", thread_indent(1), probefunc())
}
probe kernel.function("*@net/socket.c").return
{
  printf("%s <- %s\n", thread_indent(-1), probefunc())
}
类似输出如下:

变量:
变量可以在一个handler中任意使用,声明一个名字如global count,然后就可以从一个函数或者表达式中赋值,在一个表达式中使用它。SystemTap自动识别一个变量的类型是字符串或是整型。默认的变量只能在本地probe使用。要想在probes之间共享变量,可以在probe之外将变量声明为global。
目标变量:
获取代码中可见的变量的值,比如可以使用stap -L 'kernel.function("vfs_read")'来列出vfs_read函数可用的目标变量,输出类似于:
kernel.function("vfs_read@fs/read_write.c:277") $file:struct file* $buf:char* $count:size_t $pos:loff_t* 
每个目标变量由$开头,变量的类型跟随在:后面。
当一个目标变量不是一个本地变量,而是一个全局外部变量或是一个文件中的static变量定义在另一个文件中,它就可以通过"@var("varname@src/file.c")"来引用。而且还可以通过->来进一步引用结构体变量的成员。如:@var("files_stat@fs/file_table.c")->max_files);

条件语句:
if (condition) //非0
  statement1
else 
  statement2
while循环:while (condition) statement
for循环:for (initialization; conditional; increment) statement
条件操作符:==  >=  <=  !=

命令行参数:
使用$或@后面带参数序号(如@1 $1)来从命令行读取参数值,使用$期待参数是一个整型,@则是期待读入一个字符串。

关联数组:
foo["tom"] = 23
foo["dick"] = 24
支持自增操作符
支持foreach
支持in

一些统计函数:
@extractor(variable/array index expression)
@count(reads[execname()])将会返回reads数组中每个唯一的key对应存储着多少values
sum、min、max、avg
 

1. 安装:
1)首先更新内核,以防后续yum安装的kernel-devel不匹配,yum upgrade,其他包管理器类似,安装完毕后重启,然后用uname -r查看内核版本。我的内核版本为2.6.32-504.16.2.el6.x86_64
2))yum install kernel-devel,会安装内核源文件到目录/usr/src/kernels/版本/文件夹下,对比系统内核版本看是否一致,
ls /usr/src/kernels/2.6.32-504.16.2.el6.x86_64/,都是2.6.32-504.16.2,以及modules目录:
 
3)安装systemtap及其依赖包
systemtap以及systemtap-runtime
yum install systemtap 
yum install elfutils elfutils-devel
stap -V
测试systemtap:stap -ve 'probe begin { log("hello world") exit() }'
 
4)安装kernel-debuginfo,这个包并不在yum源中,在CentOS中需要手动添加这个包的repostiry。
创建debuginfo.repo,vim /etc/yum.repos.d/debuginfo.repo
[debuginfo]
name=CentOS-6 - debuginfo
baseurl=http://debuginfo.centos.org/6/x86_64/  #这里根据你的CentOS或者Redhat版本修改
gpgcheck=0
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release #自行修改
然后就可以yum install kernel-debuginfo了,会下载300多M的包,安装后1.8G
 
在RHEL6中,即便是可以安装和内核版本一样的kernel-debuginfo,但是后缀明显多了centos.plus,而且在后续跟踪内核函数或者系统调用时报出找不到debuginfo,所以推断在RHEL6中安装centos源里面的debuginfo是不行的,需要在http://rpm.pbone.net搜索匹配uname -r的kernel-debuginfo如kernel-debuginfo-2.6.32-504.16.2.el6.x86_64.rpm 然后到对应的ftp网站上下载,如ftp://bo.mirror.garr.it/pub/1/slc/updates/slc6X/x86_64/debug 需要下载同样版本的kernel-debuginfo-common和kernel-debuginfo,下载完后rpm -ivh安装,先安装debuginfo-common,再安装debuginfo,如果前面安装有不匹配的debuginfo,先yum erase移除。

2. 使用
简单测试:sudo stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'

如果没装debuginfo,对系统调用的trace可能是不行的,比如简单的检测read()系统调用:
probe syscall.read
{
        printf("%s(%d) read(%s)\n", execname(), pid(), argstr)
}

probe timer.ms(4000) #after 4 seconds
{
        exit()
}

在没有kernel-debuginfo的情况下,运行stap read.stp会报出semantic error: missing x86_64 kernel/module debuginfo的错误。

跟踪某个进程的某个系统调用:
probe syscall.accept {
     if (pid() == target())
          printf( "%s  %s(%d) accept(%s)\n", name, execname(), pid(), argstr)
}
sudo stap accept.stp -x 2170
如果当前pid()是我们的跟踪目标target()也就是传入的-x参数2170,则输出相关的信息,name是当前跟踪的函数,execname()是进程名,pid()是进程ID,argstr是accept函数的参数列表。
 
我们对echoserver程序的read()系统调用进行跟踪,测算stap的额外开销:
有stap情况下:

没有stap跟踪的情况下:

看样子在见到小的任务、小规模负载情况下,stap对系统造成的影响可以忽略。

0 0
原创粉丝点击