3、ftrace核心概念:tracer、trace event、filter、trigger,以及它们之间的关系
说实话,刚接触ftrace的时候,我也被这些概念搞得有点晕。tracer、trace event、filter、trigger,每个词都认识,但放在一起就不知道谁是谁了。今天咱们就把这几个核心概念彻底捋清楚。
3.1 tracer:内核的"监控探头"
tracer,说白了就是ftrace里预置好的"监控模式"。每个tracer都盯着内核的某个特定方面,比如函数调用、调度行为、中断响应等等。
我个人习惯把tracer分成两类:
- function tracer:跟踪函数调用,最常用
- function_graph tracer:带调用关系的函数跟踪,能看到谁调了谁
- irqsoff tracer:跟踪中断关闭时间,排查延迟问题
- preemptoff tracer:跟踪抢占关闭时间
- wakeup tracer:跟踪进程唤醒延迟
- nop tracer:空跟踪器,啥也不干,但可以配合event用
怎么选?我一般这样:
快速定位问题:先用function_graph看调用链,再用irqsoff或preemptoff查延迟。
举个例子,你想看某个驱动函数的调用过程:
# 切换到function_graph tracer
echo function_graph > /sys/kernel/tracing/current_tracer
# 只跟踪你关心的函数
echo 'my_driver_function' > /sys/kernel/tracing/set_graph_function
# 开始跟踪
echo 1 > /sys/kernel/tracing/tracing_on
# ... 执行你的操作 ...
echo 0 > /sys/kernel/tracing/tracing_on
# 查看结果
cat /sys/kernel/tracing/trace
3.2 trace event:内核的"日志点"
trace event跟tracer不一样。tracer是"模式",event是"点"。内核代码里到处都埋了trace event,就像打了一堆日志点。你只需要决定开哪些点、关哪些点。
我记得有一次排查网络丢包问题,就是靠打开net:netif_receive_skb这个event,才看到数据包在哪个环节被丢弃了。
查看所有可用的event:
ls /sys/kernel/tracing/events/
你会看到一大堆分类:
| 分类 | 说明 |
|---|---|
| sched | 调度相关,如进程切换、唤醒 |
| irq | 中断相关,如中断进入/退出 |
| syscalls | 系统调用相关 |
| block | 块设备IO相关 |
| net | 网络协议栈相关 |
| kmem | 内存分配相关 |
启用一个event很简单:
echo 1 > /sys/kernel/tracing/events/sched/sched_switch/enable
或者一次启用整个分类:
echo 1 > /sys/kernel/tracing/events/sched/enable
3.3 filter:只抓你想要的
你想想看,如果不加filter,ftrace会输出海量数据。我见过有人开了所有event,结果一秒生成了几百MB的日志,系统直接卡死。
filter就是用来做"数据裁剪"的。它支持基于字段的表达式过滤。
举个例子,我只想看PID为1234的进程调度事件:
echo 'next_pid == 1234' > /sys/kernel/tracing/events/sched/sched_switch/filter
filter支持的表达式:
==、!=:等于、不等于<、>、<=、>=:比较&&、||:逻辑与、或- 支持通配符:
prev_comm ~ "sshd*"
小技巧:先用cat /sys/kernel/tracing/events/sched/sched_switch/format查看event的字段名,再写filter表达式。
3.4 trigger:让事件"动起来"
trigger是ftrace里最灵活、也最容易让人懵的概念。它允许你在某个event触发时,执行一个动作。
我曾经用trigger解决过一个棘手的性能问题:某个函数被频繁调用,但不知道是谁调用的。用trigger的stacktrace动作,每次调用都打印调用栈,问题一目了然。
常用的trigger动作:
| 动作 | 说明 |
|---|---|
| traceon/traceoff | 开启/停止跟踪 |
| snapshot | 保存当前跟踪快照 |
| stacktrace | 打印内核调用栈 |
| enable_event/disable_event | 启用/禁用另一个event |
举个例子,当sched_switch事件发生时,同时打印调用栈:
echo 'stacktrace if next_pid == 1234' > /sys/kernel/tracing/events/sched/sched_switch/trigger
嗯,这里要注意:trigger的语法是动作 [if 条件]。条件部分是可选的,不加条件就是每次event都触发。
3.5 它们之间的关系
好了,四个概念都讲完了。它们之间到底是什么关系?我画个简单的逻辑图给你看:
tracer 是"监控模式",决定跟踪什么类型的数据。
trace event 是"数据源",提供具体的跟踪数据点。
filter 是"筛子",从数据源里挑出你关心的数据。
trigger 是"触发器",在数据满足条件时执行额外动作。
说白了,tracer和event是"数据生产者",filter是"数据筛选器",trigger是"数据消费者"。它们配合起来,才能完成一次高效的调试。
我常用的组合方式:
- function_graph + filter:只看某个函数的调用链
- event + filter:只抓特定进程的调度事件
- event + trigger(stacktrace):定位谁触发了某个事件
- nop tracer + event + trigger:最灵活的组合,完全自定义
避坑指南:我曾经在线上环境同时开了多个tracer和event,结果系统负载飙升。记住一个原则——只开你需要的,关掉所有不需要的。调试完立刻关掉跟踪。
最后说一句,这些概念刚开始可能觉得抽象,但用几次就熟了。下次遇到内核问题,先想想:我要用哪个tracer?开哪些event?要不要加filter?要不要加trigger?想清楚这四步,调试思路就清晰了。