4、第一个ftrace实验:使用available_tracers查看支持的tracer,使用current_tracer切换tracer,查看trace输出
好,咱们现在开始动手。这是你第一次真正接触ftrace,我建议你打开终端,跟我一起敲命令。光看文档是学不会调试的,得亲手试。
4.1 先看看你的内核支持哪些tracer
ftrace不是所有tracer都默认打开的。不同的内核配置,支持的tracer列表不一样。我遇到过好几次,同事说“ftrace怎么没有function_graph”,结果一看,内核编译时压根没开这个选项。
查看方法很简单:
# 挂载tracefs(如果还没挂载)
mount -t tracefs tracefs /sys/kernel/tracing
# 或者老路径
# mount -t debugfs debugfs /sys/kernel/debug
# 然后路径是 /sys/kernel/debug/tracing
cd /sys/kernel/tracing
cat available_tracers
你会看到类似这样的输出:
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
每个tracer的名字用空格隔开。我个人的习惯是,先看一眼有没有function_graph,这个是我最常用的。如果没有,那后面很多实验都做不了。
available_tracers 里常见的 tracer:
| tracer 名称 | 作用 |
|---|---|
| nop | 空跟踪器,不跟踪任何东西。相当于关闭ftrace |
| function | 跟踪内核函数的调用,只记录函数入口 |
| function_graph | 跟踪函数调用图,显示调用层级和耗时 |
| wakeup | 跟踪进程唤醒延迟 |
| wakeup_rt | 跟踪实时进程的唤醒延迟 |
| wakeup_dl | 跟踪deadline调度类的进程唤醒延迟 |
| blk | 块设备I/O跟踪 |
| mmiotrace | 内存映射I/O跟踪 |
| hwlat | 硬件延迟检测器 |
4.2 切换tracer:写入current_tracer
知道了有哪些tracer可用,接下来就是切换。说白了,就是往current_tracer这个文件里写名字。
# 先看看当前用的是哪个
cat current_tracer
# 输出一般是 nop
# 切换到 function tracer
echo function > current_tracer
# 确认切换成功
cat current_tracer
# 输出: function
这里有个坑,我曾经踩过:如果你写了一个不存在的tracer名字,不会报错,但current_tracer会变成nop。所以切换后一定要检查一下。
注意:切换tracer时,如果trace buffer里还有数据,最好先清空一下:
echo > trace
否则新旧数据混在一起,看着容易晕。
4.3 查看trace输出
切换完tracer,怎么看到跟踪结果?两个文件:trace 和 trace_pipe。
- trace:静态文件,cat一下就显示当前buffer里的所有记录
- trace_pipe:管道文件,会阻塞等待新数据,适合实时查看
咱们先试试trace:
# 先清空旧数据
echo > trace
# 随便做点操作,让内核产生一些函数调用
ls /tmp > /dev/null
# 查看跟踪结果
cat trace
你会看到类似这样的输出:
# tracer: function
#
# entries-in-buffer/entries-written: 142/142 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
ls-2345 [003] .... 12345.678901: __x64_sys_newstat <- do_syscall_64
ls-2345 [003] .... 12345.678902: do_stat <- __x64_sys_newstat
ls-2345 [003] .... 12345.678903: user_path_at_empty <- do_stat
...
每一行代表一次函数调用。格式从左到右是:
- 进程名和PID
- 运行在哪个CPU
- 中断状态、是否需要重新调度等标志位
- 时间戳
- 函数名,以及调用者(箭头后面)
嗯,这里要注意:function tracer只记录函数入口,不记录返回。所以你看不到函数执行了多久。想看耗时?后面我们会用function_graph。
4.4 用trace_pipe实时看
trace是一次性显示,trace_pipe是流式的。我调试实时问题时特别喜欢用trace_pipe,因为它会一直输出,直到你按Ctrl+C。
# 先切回function tracer
echo function > current_tracer
# 实时查看
cat trace_pipe
这时候终端会卡住,等着新数据进来。你另开一个终端执行点命令,这边就会刷刷刷地出数据。按Ctrl+C退出。
小技巧:如果trace_pipe输出太快,可以用head限制行数:
cat trace_pipe | head -20
这样只看前20行,不会刷屏。
4.5 完整实验流程
我建议你按这个顺序走一遍,养成习惯:
- 查看可用tracer:
cat available_tracers - 确认当前tracer:
cat current_tracer(应该是nop) - 清空trace buffer:
echo > trace - 切换tracer:
echo function > current_tracer - 验证切换:
cat current_tracer(应该是function) - 触发跟踪:随便执行一个命令,比如
ls - 查看结果:
cat trace或cat trace_pipe - 切回nop:
echo nop > current_tracer
为什么要切回nop?因为ftrace开着会有性能开销。虽然function tracer的开销不大,但生产环境里我建议用完就关掉。我曾经见过有人忘了关,结果线上问题排查时发现ftrace一直在跑,白白浪费CPU。
4.6 常见问题
你可能会遇到这么几个情况:
- available_tracers 里只有 nop:说明内核编译时没开ftrace,或者只开了最基本的功能。需要重新编译内核,打开
CONFIG_FUNCTION_TRACER等选项。 - 写入current_tracer没反应:检查一下文件权限,是不是root。ftrace的接口通常只有root能写。
- trace文件是空的:可能你切换tracer之前没清空,或者切换后没有触发任何内核函数调用。试试执行
echo "hello" > /dev/null这种操作。
好了,第一个实验就到这里。你亲手敲一遍,比看十遍文档都管用。下一节我们会深入function_graph,那才是真正好用的东西。