linux systemtap, stap++使用

说明

介绍systemtap, stap++的一些使用方式,以及总结了一些遇到的坑点

systemtap介绍

systemtap跟perf一样也是一个linux下的性能分析工具,但它提供自定义脚本编写,所以功能会比perf更强大。

快速了解systemptab功能

最简单的systemtab脚本

1
2
3
4
5
probe begin
{
print("hello world\n")
exit()
}

执行结果

1
2
xuchen03@onlinegame-53-51:~/systab$ sudo stap hello-world.stp
hello world

probe begin指定了一个begin的追踪点,脚本实际上做的就是定义一些列追踪点。systemtap将脚本翻译成C,并将其编译成一个内核的模块加载到内核。一旦被加载则脚本中所有指定的追踪点会在运行的时候被回调。下面列举了一些systemtap提供的追踪点:

  • begin:此次检测开始被回调
  • end: 此次检测结束被回调
  • kernel.function(“sys_open”): 内核函数sys_open进入时候被回调
  • syscall.close.return: 系统调用close返回时候被回调
  • timer.ms(200): 定时器,每200ms回调一次
  • timer.profile: 定时器,每个system tick回调一次
  • perf.hw.cache_misses: cache miss的时候回调

另一个例子

1
2
3
4
5
6
7
8
probe syscall.open
{
printf("%s"(%d) open (%s)\n", execname(), pid(), argstr)
}
probe time.ms(4000)
{
exit()
}

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xuchen03@onlinegame-53-51:~/systab$ vi strace-open.stp
xuchen03@onlinegame-53-51:~/systab$ sudo stap ./strace-open.stp
redis-server(24453) open ("/proc/24453/stat", O_RDONLY)
bot(28026) open ("/proc/self/stat", O_RDONLY)
bot(28026) open ("/proc/stat", O_RDONLY)
redis-server(24453) open ("/proc/24453/stat", O_RDONLY)
redis-server(24453) open ("/proc/24453/stat", O_RDONLY)
tail(3649) open ("logs/zqc/gameapp.3.INFO", O_RDONLY|O_NONBLOCK)
tail(3651) open ("logs/zqc/gameapp.5.INFO", O_RDONLY|O_NONBLOCK)
mgrapp(3845) open ("/proc/self/stat", O_RDONLY)
mgrapp(3845) open ("/proc/stat", O_RDONLY)
tail(3652) open ("logs/zqc/gateapp.2.INFO", O_RDONLY|O_NONBLOCK)
tail(3650) open ("logs/zqc/gameapp.4.INFO", O_RDONLY|O_NONBLOCK)
mgrapp(11899) open ("/proc/self/stat", O_RDONLY)
mgrapp(11899) open ("/proc/stat", O_RDONLY)
gateapp(3849) open ("/proc/self/stat", O_RDONLY)
gateapp(3849) open ("/proc/stat", O_RDONLY)
gateapp(11903) open ("/proc/self/stat", O_RDONLY)
gateapp(11903) open ("/proc/stat", O_RDONLY)
loginapp(3847) open ("/proc/self/stat", O_RDONLY)
loginapp(3847) open ("/proc/stat", O_RDONLY)
redis-server(24453) open ("/proc/24453/stat", O_RDONLY)
gameapp(3855) open ("/proc/self/stat", O_RDONLY)
...

printf是一个格式化打印函数,可以打印自己脚本定义的变量,或者systemtap提供的获取信息的函数或变量:

  • tid(): 当前线程id
  • pid(): 当前进程id
  • uid(): 当前用户id
  • execname(): 进程的名字
  • cpu(): 当前cpu number
  • gettimeofday_s(): 当前时间戳
  • pp(): 描述当前追踪点的字符串
  • ppfunc(): 当前追踪点被触发的函数,如果存在的话
  • print_backtrace(): 直接输出内核态堆栈
  • print_ubacktrace(): 直接输出用户态堆栈

systemtap脚本语法

普通语法和C语言差不多,这里介绍下不太一样的地方,其他具体可以查看官方教程,或者这个简化版的教程

数组

数组非常方便,内部使用hash table实现,数组大小除非显示指定,否则都定义为一个预先定义好的大小。多维可以直接用’,’分割即可,例如:

1
2
3
4
5
6
7
8
9
10
11
names[400]       // 大小位400的数组
...
foreach (name in names) {
// do something
}

foo[1, "200"] = 1
foo[2, "200"] = 2
foreach ([id, str] in foo) {
// do something
}

这里有个方便的地方当数组元素不存在的时候返回的是0,所以计数的时候就非常方便,可以直接使用bts[ubacktrace()]++计数

stap++介绍

stap++是一个stap扩展,提供一些类似于宏的功能,最后将宏替换后生成的systemtap脚本,并调用systemtap执行。
stap++使得systemtap的脚本可以得到进一步扩展。

sample-bt.sxx(perf record)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env stap++

/*
* Copyright (C) Yichun Zhang (agentzh)
*/

global bts;
global quit = 0

probe timer.profile {
if ($^pid_ok) { // pid是否相等
%( "$^arg_execname :default()" != "" %?
if (execname() == "$^arg_execname") { // 进程名是否相等,如果存在的话
%)

if (!quit) {
bts[ubacktrace()] <<< 1 // 计数,这里有个大坑,如果采样时间长会导致消耗巨量内存,可以改用bts[ubacktrace()]++优化

} else {

foreach (usr in bts- limit $^arg_limit :default(1000)) {
print_ustack(usr)
printf("\t%d\n", @count(bts[usr])) // 打印堆栈和数量,如果上面改为bts[ubacktrace()]++,这里可以直接输出bts[usr]的值
}

exit()
}

%( "$^arg_execname :default()" != "" %?
}
%)
}
}

probe timer.s($^arg_time) {
nstacks = 0
foreach (bt in bts limit 1) {
nstacks++
}

if (nstacks == 0) {
warn("No backtraces found. Quitting now...\n")
exit()

} else {
warn("Time's up. Quitting now...(it may take a while)\n")
quit = 1
}
}

probe begin {
warn(sprintf("Start tracing process $^target ($^exec_path)...\n"))
}

这脚本实现了perf record功能,统计了每次采样的堆栈。
命令执行方式,其中PID是需要进行采样的进程id

1
sudo ./stap++ samples/sample-bt.sxx -x PID -D MAXMAPENTRIES=102400 -D MAXBACKTRACE=100 -D MAXSTRINGLEN=4096 -D MAXACTION=100000 -D STP_OVERLOAD_THRESHOLD=5000000000 -D MAXSKIPPED=100000 --arg time=60 > ~/cpp.bt

采样之后可以使用命令cat ~/cpp.bt|c++filt|~/FlameGraph/stackcollapse-stap.pl|~/sysperf/FlameGraph/flamegraph.pl > ~/cpp.svg生成火焰图