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,怎么看到跟踪结果?两个文件:tracetrace_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 完整实验流程

我建议你按这个顺序走一遍,养成习惯:

  1. 查看可用tracercat available_tracers
  2. 确认当前tracercat current_tracer(应该是nop)
  3. 清空trace bufferecho > trace
  4. 切换tracerecho function > current_tracer
  5. 验证切换cat current_tracer(应该是function)
  6. 触发跟踪:随便执行一个命令,比如ls
  7. 查看结果cat tracecat trace_pipe
  8. 切回nopecho 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,那才是真正好用的东西。