概述 @
systemtap是一个强大的动态跟踪工具,能够深入Linux内核和用户空间程序,实时监控系统行为和性能特征。与coredump(事后分析)和auditd(偏向安全审计)不同,systemtap提供了一种主动式、低开销的监控方法,可以在不中断服务的情况下,持续监控系统状态,特别适合排查那些难以复现或间歇性出现的崩溃问题。
在复杂的分布式系统中,systemtap能够帮助工程师在生产环境中进行"无损调试",捕获导致服务崩溃的微小异常和竞争条件,为问题排查提供前所未有的深度和灵活性。
安装指南 @
使用systemtap需要确保内核支持必要的调试特性:
# 检查内核是否支持所需特性
cat /boot/config-$(uname -r) | grep -E "CONFIG_DEBUG_INFO|CONFIG_KPROBES|CONFIG_DEBUG_FS|CONFIG_RELAY"
# 所有选项都应该显示为y或m
如果缺少这些配置,需要重新编译内核或安装支持这些特性的内核版本
安装必要的开发包和调试信息:
# CentOS/RHEL系统
yum install gcc kernel-devel-$(uname -r) kernel-debuginfo-$(uname -r) kernel-debuginfo-common-$(uname -m)-$(uname -r) -y
# 或从官方仓库下载对应版本的debuginfo包
# http://debuginfo.centos.org/$(rpm -E %rhel)/x86_64/
# Ubuntu/Debian系统
apt-get install gcc linux-headers-$(uname -r) -y
# 安装调试信息包
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ddebs.list
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01
apt-get update
apt-get install linux-image-$(uname -r)-dbgsym -y
安装systemtap工具:
# CentOS/RHEL系统
yum install systemtap systemtap-runtime -y
# Ubuntu/Debian系统
apt-get install systemtap systemtap-sdt-dev -y
# manjaro
yay -S systemtap
# 验证安装是否成功
stap -v -e 'probe vfs.read { exit() }'
安装完成后,可以通过以下命令验证安装是否成功:
sudo stap -v -e 'probe begin { printf("Systemtap 安装成功!\n"); exit() }'
Systemtap实战脚本 @
以下是几个经过实战验证的、用于服务监控和崩溃排查的实用systemtap脚本,这些脚本可以直接在生产环境中使用或根据具体需求进行定制:
1. 进程IO监控脚本(增强版) @
#!/usr/bin/stap
// 监控进程IO活动,可用于排查IO导致的服务崩溃
global reads, writes, start_time
start_time = gettimeofday_s()
// 监控读操作
probe vfs.read.return {
if (bytes_read > 0) {
reads[execname(), pid(), uid()] += bytes_read
}
}
// 监控写操作
probe vfs.write.return {
if (bytes_written > 0) {
writes[execname(), pid(), uid()] += bytes_written
}
}
// 每5秒打印一次top 10 IO进程
probe timer.s(5) {
now = gettimeofday_s()
elapsed = now - start_time
printf("\n===== IO统计报告 (过去%d秒) =====\n", 5)
printf("%16s\t%8s\t%8s\t%12s\t%12s\t%12s\n", "进程名", "PID", "UID", "读取(KB)", "写入(KB)", "总IO(KB)")
// 计算总IO并排序输出
foreach ([name, pid, uid] in reads-) {
total_read = reads[name, pid, uid]/1024
total_write = writes[name, pid, uid]/1024
total_io = total_read + total_write
if (total_io > 0) {
printf("%16s\t%8d\t%8d\t%12d\t%12d\t%12d\n", name, pid, uid, total_read, total_write, total_io)
}
}
// 清除统计数据,准备下一轮监控
delete reads
delete writes
}
// 捕获SIGINT信号,优雅退出
probe signal.handle(SIGINT) {
printf("\n监控已停止,共运行%d秒\n", gettimeofday_s() - start_time)
exit()
}
使用方法:
stap io_monitor.stp
# 或保存为文件后执行
chmod +x io_monitor.stp
./io_monitor.stp
2. 进程崩溃监控脚本 @
#!/usr/bin/stap
// 监控进程异常终止,记录信号和堆栈信息
global crash_count
// 监控进程收到致命信号
probe signal.send {
if (sig_pid && sig_name != "SIGCHLD") {
// 只关注可能导致崩溃的信号
if (sig_name ~ /SIGSEGV|SIGABRT|SIGFPE|SIGILL|SIGBUS|SIGTRAP/) {
crash_count[execname(), sig_pid, sig_name]++
printf("[%s] 进程 %s (PID: %d) 收到致命信号 %s\n",
ctime(gettimeofday_s()), execname(), sig_pid, sig_name)
printf(" 发送者: %s (PID: %d)\n", cmdline_str(), pid())
printf(" 当前工作目录: %s\n", cwd())
printf(" 进程命令行: %s\n", cmdline_str(sig_pid))
printf("\n")
}
}
}
// 每60秒打印一次统计报告
probe timer.s(60) {
printf("\n===== 进程崩溃统计报告 =====\n")
if (@count(crash_count) > 0) {
printf("%16s\t%8s\t%10s\t%8s\n", "进程名", "PID", "信号", "次数")
foreach ([name, pid, sig] in crash_count+) {
printf("%16s\t%8d\t%10s\t%8d\n", name, pid, sig, crash_count[name, pid, sig])
}
} else {
printf("在过去60秒内没有检测到进程崩溃事件\n")
}
printf("============================\n\n")
}
3. 系统调用监控脚本 @
#!/usr/bin/stap
// 监控特定进程的系统调用,用于排查异常行为
probe begin {
printf("开始监控系统调用,按Ctrl+C停止...\n")
printf("%10s\t%12s\t%10s\t%8s\t%s\n", "时间", "进程名", "PID", "系统调用", "结果")
}
// 监控所有系统调用
probe syscall.* {
// 可以添加过滤条件,比如只监控特定进程
// if (execname() == "nginx" || pid() == target_pid) {
printf("%10d\t%12s\t%10d\t%8s\t",
gettimeofday_s(), execname(), pid(), ppfunc())
// }
}
probe syscall.*.return {
// 只打印执行时间超过100ms的系统调用
if (gettimeofday_us() - @entry(gettimeofday_us()) > 100000) {
printf("%10d\t%12s\t%10d\t%8s\t返回值: %d (耗时: %dms)\n",
gettimeofday_s(), execname(), pid(), ppfunc(),
returnval(), (gettimeofday_us() - @entry(gettimeofday_us()))/1000)
}
}
probe end {
printf("监控已停止\n")
}
4. systemtap排查服务崩溃的实际案例 @
当遇到难以定位的服务崩溃问题时,可以结合多个脚本进行综合分析:
# 场景:Nginx服务间歇性崩溃,但没有coredump文件
# 1. 首先运行崩溃监控脚本
stap crash_monitor.stp &
# 2. 同时运行IO监控脚本,检查是否存在IO问题
stap io_monitor.stp &
# 3. 针对Nginx进程运行系统调用监控
stap -e 'probe syscall.* { if (execname() == "nginx") { printf("%s\t%s\t%d\n", ctime(gettimeofday_s()), ppfunc(), pid()); } }'
# 4. 监控内存分配和释放
stap -e 'global alloc, free
probe process("/usr/sbin/nginx").function("malloc") { alloc[tid()] += $size }
probe process("/usr/sbin/nginx").function("free") { free[tid()] += $ptr }
probe timer.s(10) { printf("当前内存分配: %d bytes\n", @sum(alloc) - @sum(free)) }'
# 5. 当服务再次崩溃时,分析收集到的信息,定位问题根源