前几天凌晨2点,我又被监控报警给吵醒了。服务器CPU飙到90%,但是top看了半天也找不到罪魁祸首。这种时候,就得请出我们运维人员的两大法宝了——strace和ftrace。
说实话,刚开始接触这两个工具的时候,我也是一脸懵逼。什么系统调用、内核跟踪,听起来就很高大上的样子。但是用多了你就会发现,这玩意儿简直就是排查问题的神器!今天就跟大家分享一下我这些年用下来的一些心得。
先说说系统调用这个东西
你可能会问,系统调用到底是个啥?简单来说,就是应用程序想要干点什么事情的时候,必须要通过内核来完成。比如你的程序要读个文件,要发个网络请求,要分配点内存,这些都得通过系统调用来实现。
就像你在家里想吃外卖,你不能直接跑到餐厅后厨去炒菜吧?你得通过外卖平台下单,然后平台帮你处理。系统调用就是这个"外卖平台",应用程序就是你,内核就是后厨。
我记得刚工作那会儿,有个同事总是说"程序跑得慢,肯定是代码写得烂"。后来用strace一看,好家伙,程序每秒钟调用了几万次write系统调用,每次只写一个字节!这不是坑爹吗?
strace:系统调用的"偷窥狂"
strace这个工具,说白了就是个"偷窥狂",专门监视程序都调用了哪些系统调用。
基本用法很简单
最简单的用法就是直接在命令前面加个strace:
strace ls /tmp
然后你就会看到一堆输出,密密麻麻的,第一次看肯定会被吓到。不过别慌,我们慢慢来分析。
如果你想跟踪一个正在运行的进程,用-p参数:
strace -p 1234
这里1234是进程ID,你可以通过ps命令找到。
输出内容怎么看
strace的输出格式基本是这样的:
系统调用名(参数1, 参数2, ...) = 返回值
比如:
open("/etc/passwd", O_RDONLY) = 3 read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1024 close(3) = 0
这三行就是一个完整的文件读取过程:
-
open打开文件,返回文件描述符3
-
read从文件描述符3读取数据,读了1024字节
-
close关闭文件描述符3
有时候你还会看到一些错误,比如:
open("/tmp/nonexistent", O_RDONLY) = -1 ENOENT (No such file or directory)
这就是说文件不存在,open调用失败了。
实用参数组合
我平时用得最多的几个参数组合:
只看文件操作:
strace -e trace=file ls /tmp
只看网络操作:
strace -e trace=network curl baidu.com
统计系统调用次数和耗时:
strace -c ls /tmp
这个-c参数特别有用,能给你一个统计报告,告诉你哪个系统调用用得最多,哪个最耗时。
输出到文件:
strace -o trace.log -p 1234
线上环境建议都输出到文件,不然屏幕刷得你眼花缭乱。
真实案例分享
去年有个Java应用启动特别慢,用户都投诉了。开发说代码没问题,我就用strace跟踪了一下启动过程:
strace -f -o startup.log java -jar myapp.jar
这里-f参数很重要,可以跟踪子进程。结果发现这个应用启动的时候,疯狂地在读取/dev/random,每次读取都要等好几秒。原来是某个加密库的配置有问题,改成/dev/urandom就好了。
ftrace:内核级别的"透视镜"
如果说strace是应用层的监控,那ftrace就是内核层的透视镜了。它能让你看到内核内部发生了什么,包括函数调用关系、执行时间等等。
ftrace的工作原理
ftrace是Linux内核自带的跟踪框架,通过debugfs文件系统来操作。说白了就是内核在关键位置埋了很多"探针",你可以选择性地开启这些探针来收集信息。
基本操作步骤
ftrace的操作都是通过/sys/kernel/debug/tracing目录下的文件来完成的。
首先检查ftrace是否可用:
ls /sys/kernel/debug/tracing/
如果看不到这个目录,可能需要挂载debugfs:
mount -t debugfs debugfs /sys/kernel/debug
查看可用的跟踪器:
cat /sys/kernel/debug/tracing/available_tracers
你会看到function、function_graph、nop等选项。
启用函数跟踪:
echo function > /sys/kernel/debug/tracing/current_tracer echo 1 > /sys/kernel/debug/tracing/tracing_on
查看跟踪结果:
cat /sys/kernel/debug/tracing/trace
停止跟踪:
echo 0 > /sys/kernel/debug/tracing/tracing_on
输出内容解读
ftrace的输出格式大概是这样:
# tracer: function # # entries-in-buffer/entries-written: 140080/250280 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | bash-1994 [000] .... 12345.678901: sys_read <-system_call_fastpath bash-1994 [000] .... 12345.678902: vfs_read <-sys_read
这里面信息量很大:
-
TASK-PID:进程名和进程ID
-
CPU#:在哪个CPU核心上执行
-
TIMESTAMP:时间戳
-
FUNCTION:调用的函数名
那些点和字母表示系统状态,比如中断是否关闭、是否需要重新调度等等。
function_graph跟踪器
我个人比较喜欢用function_graph跟踪器,它能显示函数的调用关系和执行时间:
echo function_graph > /sys/kernel/debug/tracing/current_tracer echo 1 > /sys/kernel/debug/tracing/tracing_on # 运行你要跟踪的程序 echo 0 > /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace
输出会是这样的:
0) | sys_read() { 0) 0.632 us | fget_light(); 0) | vfs_read() { 0) 0.112 us | rw_verify_area(); 0) 0.887 us | ext4_file_read(); 0) 2.221 us | } 0) 3.005 us | }
这样就能清楚地看到函数调用的层次关系和每个函数的执行时间。
过滤和限制
ftrace的输出通常会非常多,你需要学会过滤。比如只跟踪特定的函数:
echo 'sys_read sys_write' > /sys/kernel/debug/tracing/set_ftrace_filter
或者只跟踪特定进程:
echo 1234 > /sys/kernel/debug/tracing/set_ftrace_pid
实战技巧和注意事项
用了这么多年,我总结了一些实战经验。
性能影响要考虑 strace和ftrace都会对性能产生影响,特别是ftrace。线上环境使用的时候一定要小心,最好在业务低峰期操作。我见过有同事在生产环境开启了全量ftrace跟踪,直接把服务器搞宕机了。
日志文件会很大 特别是ftrace,输出量可能非常大。记得设置好日志轮转,或者及时清理。有一次我忘记关闭ftrace,结果/var/log被撑爆了。
结合其他工具使用 strace和ftrace最好配合其他工具一起用。比如先用top、iotop找到可疑进程,再用strace详细分析。或者用perf配合ftrace做性能分析。
学会看关键信息 输出信息太多的时候,要学会抓重点。比如看系统调用的返回值、执行时间、调用频率等。错误信息特别要关注,往往问题就藏在那里。
我记得有次排查一个网络问题,strace显示connect系统调用一直返回ETIMEDOUT,但是开发坚持说网络没问题。后来发现是防火墙规则有问题,特定端口被屏蔽了。
一些高级用法
多进程跟踪
strace -f -p 1234
-f参数可以跟踪子进程,对于多进程应用很有用。
时间戳显示
strace -t -p 1234
-t显示时间戳,-tt显示微秒级时间戳,排查性能问题的时候特别有用。
系统调用统计
strace -c -p 1234
运行一段时间后按Ctrl+C,会显示统计信息,告诉你哪些系统调用最频繁、最耗时。
自定义ftrace脚本 我写了个简单的脚本来自动化ftrace操作:
#!/bin/bash TRACE_DIR="/sys/kernel/debug/tracing" # 清理之前的跟踪 echo 0 > $TRACE_DIR/tracing_on echo > $TRACE_DIR/trace # 设置跟踪器 echo function_graph > $TRACE_DIR/current_tracer # 设置过滤器 echo $1 > $TRACE_DIR/set_ftrace_pid # 开始跟踪 echo 1 > $TRACE_DIR/tracing_on echo "正在跟踪进程 $1,按回车键停止..." read # 停止跟踪 echo 0 > $TRACE_DIR/tracing_on # 显示结果 cat $TRACE_DIR/trace
用法就是 ./trace.sh 1234
,比手动操作方便多了。
踩过的坑和经验教训
说起来都是泪啊。刚开始用这些工具的时候,我踩了不少坑。
有一次用strace跟踪一个数据库进程,结果发现性能下降了50%!原来strace对I/O密集型应用的影响特别大。后来我学会了用-e参数只跟踪特定的系统调用,影响就小多了。
还有一次,我在生产环境用ftrace跟踪内核函数,结果忘记设置过滤器,把所有内核函数调用都记录下来了。几分钟就产生了几个GB的日志,差点把磁盘撑爆。从那以后,我都会先设置好过滤条件再开启跟踪。
最坑的一次是,我用strace跟踪一个多线程程序,但是忘记加-f参数,只看到了主线程的系统调用。折腾了半天才发现问题出在子线程上。
与其他工具的配合
这两个工具虽然强大,但是单独使用有时候还不够。我通常会配合其他工具一起用:
配合lsof查看文件描述符 strace显示程序在操作文件描述符3,但是你不知道这是什么文件,这时候用lsof就能看到:
lsof -p 1234
配合netstat查看网络连接 看到程序在进行网络操作,但不知道连接状态,netstat能帮你:
netstat -anp | grep 1234
配合perf做性能分析 ftrace告诉你哪个函数调用频繁,perf能告诉你CPU时间都花在哪里了:
perf top -p 1234
一些实用的过滤技巧
输出太多的时候,学会过滤很重要。
strace过滤示例:
# 只看文件操作 strace -e trace=file,desc ls # 只看网络操作 strace -e trace=network wget baidu.com # 排除某些系统调用 strace -e trace=!write,read ls
ftrace过滤示例:
# 只跟踪特定函数 echo 'vfs_*' > /sys/kernel/debug/tracing/set_ftrace_filter # 排除某些函数 echo '!schedule*' > /sys/kernel/debug/tracing/set_ftrace_notrace
调试技巧分享
多年的经验告诉我,用这些工具调试问题有一些套路。
先用strace找到异常的系统调用 比如程序卡住了,先看看是不是某个系统调用阻塞了。如果是网络问题,通常会看到connect或者read调用长时间不返回。
再用ftrace深入内核层面 如果strace没找到问题,可能问题在内核层面。这时候用ftrace跟踪相关的内核函数,看看是不是内核哪里出了问题。
结合时间戳分析性能 用-t参数显示时间戳,看看哪些操作耗时比较长。有时候一个简单的文件读取操作耗时几秒钟,那肯定有问题。
我记得有次排查一个程序启动慢的问题,strace显示程序在读取某个配置文件的时候特别慢。进一步分析发现,这个文件在NFS挂载的目录下,而NFS服务器响应很慢。换到本地文件系统后,启动时间从30秒缩短到3秒。
总结
strace和ftrace确实是排查问题的利器,但是需要一定的学习成本。我的建议是:
从strace开始学起,相对简单一些。多在测试环境练习,熟悉各种参数的用法。学会看关键信息,不要被大量的输出吓到。线上使用要谨慎,注意性能影响。最好结合其他工具一起使用,效果会更好。
最重要的是,工具只是手段,关键还是要理解系统的工作原理。当你对Linux系统调用、内核机制有了更深入的理解,这些工具才能真正发挥威力。
说实话,掌握了这两个工具之后,我解决问题的效率提高了不少。以前遇到奇怪的问题,只能靠猜测和试错。现在能直接看到程序在做什么,问题往往很快就能定位到。
当然,学习这些工具也不是一蹴而就的。我用了好几年才算比较熟练,期间也踩了不少坑。但是一旦掌握了,你会发现这些投入都是值得的。
希望这篇文章能帮到大家。如果你在使用过程中遇到什么问题,或者有什么好的经验想分享,欢迎留言讨论!
如果这篇文章对你有帮助,别忘了点个赞👍,转发给更多需要的朋友。想了解更多运维干货和实战经验,记得关注@运维躬行录,我会持续分享更多有价值的技术内容!
公众号:运维躬行录
个人博客:躬行笔记