使用 SystemTap 对系统进行动态追踪

起因

之前跟章亦春(春哥)讨论如何排查系统的长尾问题的时候,春哥举了他之前在 Cloudflare 排查的一个例子,一个请求比较慢,所以需要确定是网络的问题,还是磁盘 IO 的问题,但这几个地方的 99.9% metric 都非常正常,所以推测应该是长尾问题。于是春哥在 TCP,I/O 相关的地方用 SystemTap 加上 probe,然后发现网络能正常的处理,但在一些 I/O 操作的时候有很慢的情况,确定了 I/O 有问题,最后在追查到磁盘有一个地方有损坏。

那时候我就对 SystemTap 印象深刻了。虽然之前也有很多动态追踪的实践,但通常还是对 perf 使用较多,对 SystemTap 的研究比较少,但这次交流之后,让我确定对于一些长尾疑难问题,SystemTap 是一个非常好的方法,值得花时间去研究一下,

介绍

SystemTap 是一个动态追踪工具,它使用 kprobes 组件去帮助用户更深入的学习和监控整个系统(无论是 kernel 还是 application)的状态,而且运行的时候只对系统有极小的开销。虽然很多工具像 netstat,ps,top,iostat 等也能够了解很多信息,但 SystemTap 能做到更多,譬如知道 application 和 operating system 之间的交互,不同进程的交互,不同 kernel subsystem 的交互,以及一些疑难杂症的排查等。

脚本处理

SystemTap 提供了一套脚本语言,让用户非常方便的编写自定义测量以及分析工具去定位问题。

image.png

当用户编写一个脚本 probe 文件之后,首先会经过 parse 生成 AST,然后在 elaborate 阶段,解析相关的符号和引用信息,然后在转成 C 的代码并编译成一个 kernel module。然后在 load module,开始 probe,运行结束之后,将收集的信息高效的传输给用户空间,然后 unload module。

脚本语言

SystemTap 的语法非常简单,如果熟悉 C,AWK 等语言的会非常容易上手,我们以一个非常简单的例子稍作说明:

global bt_stats
global quit

probe begin {
    warn("Start tracing. Wait for 10 sec to complete.\n")
}

probe process("/lib*/libc-2.17.so").function("__memcpy_ssse3_back") {
    if (pid() == target()) {
        if (quit) {
            foreach (bt in bt_stats) {
                print_ustack(bt)
                printf("\t%d\n\n", @count(bt_stats[bt]))
            }

            exit()
        } else {
            bt = ubacktrace()
            bt_stats[bt] <<< 1
        }

    }
}


probe timer.s(1) {
    quit = 1
}

上面是我之前想看 TiKV memcpy 到底在哪里调用的一个简单脚本。

变量

我们定义了两个全局变量,quitbt_stats,在 SystemTap 里面,变量需要提前声明,我们并不需要提前声明变量的类型,SystemTap 能自己判断这个变量的使用类型。上面的 quit 是一个 int 变量,我们在 1s 之后会将其设置为 1,然后程序退出。bt_stats 是一个类似 AWK 关联数组,它的 key 就是对应的堆栈,而 value 则是一个统计变量。

统计变量是 SystemTap 里面用来进行聚合操作的变量,譬如上面的 <<< 1,每次匹配了 memcpy 函数之后,我们就会记录下对应堆栈的次数。然后在结束的时候我们可以用统计函数,譬如 count,得到实际的个数。

Probe

Probe 就是定义了实际的探针 points,当对应的事件触发的时候,就会去调用对应 probe 的 block 代码,譬如上面我们 probe 了 process("/lib*/libc-2.17.so").function("__memcpy_ssse3_back"),我们知道 TiKV 进程会使用 libc 的 memcpy 函数,所以使用 process 从 libc 的 so 里面找到对应的 function。上面我们是知道对应的函数名字,也可以通过 function(name@file:line)等规则来匹配相关的函数。

我们也可以 probe kernel 函数,譬如 probe kernel.funcion(*init*) 来匹配所有 kernel 里面的 init 函数。所以如果要精通 SystemTap 来动态追踪,我们其实需要更深刻的去理解整个 kernel 的软件栈和 API。

SystemTap 也提供了 Tapsets 简化了一些 kernel 的 probe 编写,譬如我们可以通过 probe netdev.transmitprobe netdev.receive 来 probe 网络的包收发。

总结

上面简单的介绍了 SystemTap 的相关知识,可以看到,如果想更多了解,可以看 SystemtTap 自己的相关文档,也可以通过官方 example 深入学习。但要更加熟练的使用 SystemTap 进行诊断,对 Linux 整个软件栈的了解是必不可少的,所以后面看看 kernel 代码是有必要的。

你可能感兴趣的:(使用 SystemTap 对系统进行动态追踪)