Ftrace
Ftrace 是一个 Linux 内部的 trace 工具,能够帮助开发者和系统设计者知道内核当前正在进行的工作,从而更好的去分析性能问题。Ftrace 能帮我们分析内核特定的事件,譬如调度,中断等,也能帮我们去追踪动态的内核函数,以及这些函数的调用栈还有栈的使用。它也能帮我们去追踪延迟,譬如中断被屏蔽,抢占被禁止的时间,以及唤醒一个进程之后多久开始执行的时间。
ftrace 原理
Ftrace 最初是在 2.6.27 中出现,那时 SystemTap 已经开始崭露头角,然而 SystemTap 设计比较复杂,不当的使用和 SystemTap 自身的不完善都有可能导致系统崩溃,在真正的产品环境,人们依然无法放心的使用。
Ftrace 的设计目标简单,本质上是一种静态代码插装技术,不需要支持某种编程接口让用户自定义 trace 行为。静态代码插装技术更加可靠,不会因为用户的不当使用而导致内核崩溃。 ftrace 代码量很小,稳定可靠。实际上,即使是 Dtrace,大多数用户也只使用其静态 trace 功能。
- Ftrace 主要由
ftrace framework和 一系列tracer两部分组成,每个 tracer 完成不同的功能,它们统一由framework管理 - ftrace 的 trace 信息保存在 ring buffer 中,由 framework 负责管理。ftrace 有两种主要跟踪机制可以往缓冲区中写数据
- 函数:即动态探针,可以跟踪内核函数的调用栈,包括
function tracer,function graph tracer两个 tracer - 事件:即静态探针,包括其他大多数的 tracer
- 函数:即动态探针,可以跟踪内核函数的调用栈,包括
- Framework 利用 debugfs 系统在 /debugfs 下建立 tracing 目录,对用户空间输出 trace 信息,并提供了一系列的控制文件
- ftrace 的目录设置和 sysfs 类似,都是把目录当作对象,把里面的文件当作这个对象的属性。
debugfs/tracing目录可以理解成一个独立的监控实例 instance,在 tracing 目录或者子目录创建任何目录相当于创建了一个新的 ftrace 实例,ftrace 会为这个 ftrace 实例自动创建 ring buffer 内存缓冲区,并在这个目录下创建 ftrace 实例所需的与 tracing 目录完全相同的文件。
debugfs 在大部分发行版中都 mount 在 /sys/kernel/debug 目录下,而 ftrace 就在这个目录下的 tracing 目录中。如果系统没有 mount 这个文件系统,可以手动 mount。
|
|
ftrace 控制机制
在讲解 ftrace 的 tracer 之前,我们先来看看 tracing 目录下的文件,它们提供了对 ftrace trace 过程的控制。
|
|
tracing 目录下的文件分成了下面四类:
- 提示类:显示当前系统可用的 event,tracer 列表
- 控制类:控制 ftrace 的跟踪参数
- 显示类:显示 trace 信息
- 辅助类:一些不明或者不重要的辅助信息
提示类
| ftrace 文件 | 作用 |
|---|---|
| available_events | 可用事件列表,也可查看 events/ 目录 |
| available_filter_functions | 当前内核导出的可以跟踪的函数 |
| dyn_ftrace_total_info | 显示 available_filter_functins 中跟中函数的数目 |
| available_tracers | 可用的 tracer,不同的 tracer 有不同的功能 |
| events | 1. 查看可用事件列表以及事件参数(事件包含的内核上下文信息) cat events/sched/sched_switch/format 2.设置事件的过滤条件 echo ‘next_comm ~ “cs”‘ > events/sched/sched_switch/filter |
控制类
| 适用 tracer | ftrace 文件 | 作用 |
|---|---|---|
| 通用 | tracing_on | 用于控制跟踪打开或停止,0 停止跟踪,1 继续跟踪 |
| 通用 | tracing_cpumask | 设置允许跟踪特定 CPU |
| 通用 | tracing_max_latency | 记录 Tracer 的最大延时 |
| 通用 | tracing_thresh | 延时记录 Trace 的阈值,当延时超过此值时才开始记录 Trace。单位是 ms,只有非 0 才起作用 |
| 通用 | events | 1. 查看可用事件列表以及事件参数(事件包含的内核上下文信息) cat events/sched/sched_switch/format 2.设置事件的过滤条件 echo ‘next_comm ~ “cs”‘ > events/sched/sched_switch/filter |
| 通用 | set_event | 设置跟踪的 event 事件,与通过 events 目录内的 filter 文件设置一致 |
| 通用 | current_tracer | 1. 设置或者显示当前使用的跟踪器列表 2. 系统缺省为 nop,可以通过写入 nop 重置跟踪器 3. 使用 echo 将跟踪器名字写入即可打开 echo function_graph > current_tracer |
| 通用 | buffer_size_kb | 设置单个 CPU 所使用的跟踪缓存的大小 如果跟踪太多,旧的信息会被新的跟踪信息覆盖掉 不想被覆盖需要先将 current_trace 设置为 nop 才可以 |
| 通用 | buffer_total_size_kb | 显示所有 CPU ring buffer 大小之和 |
| 通用 | trace_options | trace 过程的复杂控制选项 控制 Trace 打印内容或者操作跟踪器 也可通过 options/目录设置 |
| 通用 | options/ | 显示 trace_option 的设置结果 也可以直接设置,作用同 trace_options |
| func | function_profile_enabled | 打开此选项,trace_stat 就会显示 function 的统计信息 echo 0/1 > function_profile_enabled |
| func | set_ftrace_pid | 设置跟踪的 pid |
| func | set_ftrace_filter | 用于显示指定要跟踪的函数 |
| func | set_ftrace_notrace | 用于指定不跟踪的函数,缺省为空 |
| graph | max_graph_depth | 函数嵌套的最大深度 |
| graph | set_graph_function | 设置要清晰显示调用关系的函数 缺省对所有函数都生成调用关系 |
| Stack | stack_max_size | 当使用 stack 跟踪器时,记录产生过的最大 stack size |
| Stack | stack_trace | 显示 stack 的 back trace |
| Stack | stack_trace_filter | 设置 stack tracer 不检查的函数名称 |
输出类
| ftrace 文件 | 作用 |
|---|---|
| printk_formats | 提供给工具读取原始格式 trace 的文件 |
| trace | 查看 ring buffer 内跟踪信息 echo > trace 可以清空当前 RingBuffer |
| trace_pipe | 输出和 trace 一样的内容,但输出 Trace 同时将 RingBuffer 清空 可避免 RingBuffer 的溢出 保存文件内容: cat trace_pipe > trace.txt & |
| snapshot | 是对 trace 的 snapshot echo 0 清空缓存,并释放对应内存 echo 1 进行对当前 trace 进行 snapshot,如没有内存则分配 echo 2 清空缓存,不释放也不分配内存 |
| trace_clock | 显示当前 Trace 的 timestamp 所基于的时钟,默认使用 local 时钟 local:默认时钟;可能无法在不同 CPU 间同步 global:不同 CUP 间同步,但是可能比 local 慢 counter:跨 CPU 计数器,需要分析不同 CPU 间 event 顺序比较有效 |
| trace_marker | 从用户空间写入标记到 trace 中,用于用户空间行为和内核时间同步 |
| trace_stat | 每个 CPU 的 Trace 统计信息 |
| per_cpu/ | trace 等文件的输出是综合所有 CPU 的,如果你关心单个 CPU 可以进入 per_cpu 目录,里面有这些文件的分 CPU 版本 |
| enabled_functions | 显示有回调附着的函数名称 |
| saved_cmdlines | 放 pid 对应的 comm 名称作为 ftrace 的 cache,这样 ftrace 中不光能显示 pid 还能显示 comm |
| saved_cmdlines_size | saved_cmdlines 的数目 |
trace、trace_pipe 和 snapshot 的区别:
- trace 是从 RingBuffer 中取出内容
- trace_pipe 会一直读取 Buffer 流
- snapshot 是 trace 的一个瞬间快照
辅助类
| ftrace 文件 | 作用 | |
|---|---|---|
| free_buffer | 此文件用于在一个进程被关闭后,同时释放 RingBuffer 内存,并将调整大小到最小值 | |
| instances | 空目录,可在此目录创建新的 ftrace 实例 | |
| hwlat_detector | ||
| kprobe_events | ||
| kprobe_profile | ||
| uprobe_events | ||
| uprobe_profile |
ftrace tracer
下面是 ftrace tracer 的不完全列表,每一个 tracer 输出的内容和格式都不一样,对于我们比较常用的是 Function,Graph,Schedule switch,softirq。
| ftrace | 作用 |
|---|---|
| Function | 跟踪函数调用 |
| Function graph tracer | 跟踪函数调用,显示调用关系 |
| Schedule switch | 跟踪进程调度情况 |
| Wakeup | 跟踪进程的调度延迟,即高优先级进程从进入 ready 状态到获得 CPU 的延迟时间。该 tracer 只针对实时进程 |
| Irqsoff | 当中断被禁止时,系统无法相应外部事件,比如键盘和鼠标,时钟也无法产生 tick 中断。这意味着系统响应延迟,irqsoff 这个 tracer 能够跟踪并记录内核中哪些函数禁止了中断,对于其中中断禁止时间最长的,irqsoff 将在 log 文件的第一行标示出来,从而使开发人员可以迅速定位造成响应延迟的罪魁祸首 |
| Preemptoff | 和前一个 tracer 类似,preemptoff tracer 跟踪并记录禁止内核抢占的函数,并清晰地显示出禁止抢占时间最长的内核函数 |
| Preemptirqsoff | 同上,跟踪和记录禁止中断或者禁止抢占的内核函数,以及禁止时间最长的函数 |
| Branch | 跟踪内核程序中的 likely/unlikely 分支预测命中率情况。 Branch tracer 能够记录这些分支语句有多少次预测成功。从而为优化程序提供线索 |
| Hardware branch | 利用处理器的分支跟踪能力,实现硬件级别的指令跳转记录。在 x86 上,主要利用了 BTS 这个特性 |
| Initcall | 记录系统在 boot 阶段所调用的 init call |
| Mmiotrace | 记录 memory map IO 的相关信息 |
| Power | 记录系统电源管理相关的信息 |
| Sysprof | 缺省情况下,sysprof tracer 每隔 1 msec 对内核进行一次采样,记录函数调用和堆栈信息 |
| Kernel memory | 内存 tracer 主要用来跟踪 slab allocator 的分配情况。包括 kfree,kmem_cache_alloc 等 API 的调用情况,用户程序可以根据 tracer 收集到的信息分析内部碎片情况,找出内存分配最频繁的代码片断,等等 |
| Workqueue statistical | 这是一个 statistic tracer,统计系统中所有的 workqueue 的工作情况,比如有多少个 work 被插入 workqueue,多少个已经被执行等。开发人员可以以此来决定具体的 workqueue 实现,比如是使用 single threaded workqueue 还是 per cpu workqueue |
| Event | 跟踪系统事件,比如 timer,系统调用,中断等 |
ftrace 的实战
跟踪函数
下面这个脚本演示了 ftrace 简单实用 ftrace 的方法,它通过操作 tracing 目录下的控制对象,实现对 function 的 tracing :
|
|
一旦脚本运行完成后,我们将看到下列的输出(这里只列出了一个小片断):
|
|
在这里有一个包含下列信息的函数列表:
- 进程标识符(PID)
- 运行这个进程的 CPU(CPU#)
- 进程开始时间(TIMESTAMP)
- 被跟踪函数的名字以及调用它的父级函数;例如,在我们输出的第一行,
rb_simple_write调用了mutex-unlock函数。
追踪系统调用
|
|
输出:第三列是函数执行延迟;最后一列,则是函数调用关系图。
基于 ftrace 使用 kprobe
在 kprobe 这篇文章中,我介绍了通过内核模块使用 kprobe 实现追踪系统调用的方法,如果每次追踪都需要去编写内核模块比较麻烦,ftrace 也可以和 kprobe 来使用,可以对内核进行配置,然后添加探测点、进行探测、查看结果。
Kprobe 配置
打开 "General setup"->"Kprobes",以及 "Kernel hacking"->"Tracers"->"Enable kprobes-based dynamic events"。
|
|
kprobe 事件在 debugfs 中相关目录如下:
|
|
kprobe 事件配置
新增一个 kprobe 事件,通过写 kprobe_events 来设置。
|
|
解释如下:
|
|
执行如下两条命令就会生成目录 /sys/kernel/debug/tracing/events/kprobes/myprobe;第三条命令则可以删除指定 kprobe 事件,如果要全部删除则 echo > /sys/kernel/debug/tracing/kprobe_events。
|
|
参数后面的寄存器是跟架构相关的,%ax、%dx、%cx 表示第 1/2/3 个参数,超出部分使用 $stack 来存储参数。函数返回值保存在 $retval 中。
查看 format,也就是 trace_pipe 的打印格式
|
|
kprobe 使能
对 kprobe 事件的是能通过往对应事件的 enable 写 1 开启探测;写 0 暂停探测。在 enable 专辑后,就可以在 /sys/kernel/debug/tracing/trace 看到结果了。
|
|
在/sys/kernel/debug/tracing/trace 中可以看到结果。
|
|
kprobe 事件过滤
跟踪函数需要通过 filter 进行过滤,可以有效过滤掉冗余信息。
filter 文件用于设置过滤条件,可以减少 trace 中输出的信息,它支持的格式和 c 语言的表达式类似,支持 ==,!=,>,<,>=,<=判断,并且支持与&&,或||,还有()。
|
|
kprobe 和栈配合使用
如果要在显示函数的同时显示其栈信息,可以通过配置 trace_options 来达到。
|
|
kprobe_profile 统计信息
获取一段 kprobe 时间之后,可以在 kprobe_profile 中查看统计信息。
后面两列分别表示命中和未命中的次数。
|
|
Debugfs 抓 kprobe event 实现原理
接下来我们初略的分析下 debugfs 中 kprobe event 的实现过程。
根据代码 kernel/trace/trace_kprobe.c 可知在 kernel 初始化的 fs_initcall 阶段,就调用了 init_kprobe_trace 来实现 kprobe_events,并指定了 kprobe_events_ops 这个 file_operation。当用 echo 操作 kprobe_events 这个节点时,调用 probe_write,注册 kprobe event。
|
|
注册 kprobe events 相关调用顺序如下所示,最终调用的是 __register_trace_kprobe 来实现真正的注册过程。
|
|
__register_trace_kprobe 的实现细节如下所示,其中令人醒目的是调用了 register_kprobe ,这个就到了注册 kprobe 过程,这个可以参看内核目录下的一个样例 kprobe_example.c,看看注册要哪些参数,这里可以猜测到 struct trace_kprobe 结构体中的 rp.kp 指定了探测符号和偏移量,这里就不一一分析了,读者可根据这个看看详细 kprobe events 怎么注册的。
|
|
perf-tools
ftrace 一个比较明显的缺点是没有用户态的跟踪点支持。perf-tools 对 Ftrace 的功能进行了很好的封装和集成,建议大家用 perf-tools 来使用 Ftrace,则效果更佳更简单,关于 perf-tools 的实用可以参考 Github: perf-tools。
参考资料
-
No backlinks found.