限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
本文基于对 Linux 内核 ftrace 文档 翻译整理而成。
ftrace
是 Linux 内核内置的追踪机制,用来帮助系统设计人员了解内核内部当前发生的事情,它可以用于用户空间调试和分析内核延迟和性能问题。虽然 ftrace
通常被认为是 function tracer
,但它实际上已经发展为内核的追踪框架。后文中的所有 ftrace
,指代整个 ftrace 框架
,而非 function tracer
。
ftrace
通过 debugfs
向用户空间导出接口:
# mount
tracefs on /sys/kernel/debug/tracing type tracefs (rw,relatime)
ftrace
目录树默认挂载在 /sys/kernel/debug/tracing
路径下。
内核集成了各种 tracer ,如用来追踪内核函数的 function tracer
等等。通过文件 available_tracers
可以查看当前编译到内核的 tracer 列表:
# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
current_tracer
表示当前启用的 tracer ,默认为 nop
,即不做任何追踪工作:
# cat current_tracer
nop
通过写 current_tracer
可以修改当前 tracer ,available_tracers
文件中列出了可以写入到 current_tracer
的 tracer 名。如想追踪系统内核函数调用,可以向 current_tracer
写入 function
:
# echo function > current_tracer
# cat current_tracer
function
available_tracers
文件中包含的是当前编译到内核的 tracer 列表,也即可以写到 current_tracer
的 tracer 名。
向 tracing_on
写入 1,启用追踪;向 tracing_on
写入 0,停止追踪。ftrace
使用 ring buffer
记录追踪数据。修改 tracing_on
不会影响 ring buffer
当前记录的内容。
# echo 1 > tracing_on 启用追踪过程
# echo 0 > tracing_on 关闭追踪过程
trace
文件包含当前追踪的内容,以人类可读的格式展现。来看一个简单例子:
# echo function > current_tracer
# echo 1 > tracing_on
# cat trace
...
<idle>-0 [001] dNh. 8786.470388: __enqueue_entity <-enqueue_entity
<idle>-0 [001] dNh. 8786.470388: hrtick_update <-enqueue_task_fair
<idle>-0 [001] dNh. 8786.470388: ttwu_do_wakeup <-ttwu_do_activate
<idle>-0 [001] dNh. 8786.470389: check_preempt_curr <-ttwu_do_wakeup
<idle>-0 [001] dNh. 8786.470389: resched_curr <-check_preempt_curr
...
# echo 0 > tracing_on
我们看到,trace
文件包含了内核函数调用记录。
trace_pipe
和 trace
一样,都是记录当前的追踪内容,但它和 trace
不一样的是:
o 对 `trace_pipe` 的读操作会阻塞,直到有新的追踪数据进来为止;
o 当前从`trace_pipe` 读取的内容会被消耗掉,再次读 `trace_pipe` 又会阻塞到新数据进来为止。
trace_options
文件中包含用来控制 ftrace
写入到 trace
和 trace_pipe
中内容的选项列表。trace_options
中列举的每一个选项名,在 options 目录
下都有一个对应文件,对 options 目录
下的文件写 0 或 1 来关闭或启用对应的选项,以达到控制 ftrace
写入到 trace
和 trace_pipe
的内容。后续在对 set_ftrace_pid
文件的说明中,将对此选项做进一步说明。
trace_options
文件中包含的每个选项名,在 options
目录下都会有一个同名文件,可以通过对这些文件写 0 或 1 来关闭或启用对应的选项。
有些 tracer
记录最大延迟。如禁用中断的最长时间(即最大中断延迟)。tracing_max_latency
记录当前的最大延迟时间,单位为 微妙(us)
。在追踪过程中,如果有延迟时间大于 tracing_max_latency
记录的时长,将更新 tracing_max_latency
文件。 trace
文件记录当前的延迟时间。
有些 延迟追踪类 tracer
,只有延迟时间大于某个阈值的时候,才记录追踪数据,此时tracing_thresh
记录这个阈值。tracing_thresh
需要 大于 0
才会激活这种机制,小于等于0的值没有意义。
buffer_size_kb
记录 CPU buffer
的大小,单位为 KB
。per_cpu/cpuX/buffer_size_kb
记录 每个CPU buffer
大小,单位为 KB
。可通过写 buffer_size_kb
来改变 CPU buffer
的大小。当所有的 CPU buffer 有不同大小时,buffer_size_kb
将显示为 X
。对 buffer_size_kb
写入,将重置所有 CPU buffer 的大小(即重置 per_cpu/cpuX/buffer_size_kb
)。
buffer_total_size_kb
记录所有 CPU buffer
的总大小,即所有 CPU buffer 大小总和,如有 128 个 CPU buffer ,每个大小 7KB,则 buffer_total_size_kb
记录的总大小为 128 * 7KB = 896。buffer_total_size_kb
文件是只读的。
TODO:
指定追踪的 CPU buffer 掩码,如追踪所有 128 个 CPU buffer,则:
# cat tracing_cpumask
ffffffff,ffffffff,ffffffff,ffffffff
所有 128 个 CPU buffer 掩码位全为1。
注:这里的 128 个并非对应到 CPU 的个数,CPU buffer 个数 和 CPU 个数不是一一对应的关系。
ftrace
仅追踪文件 set_ftrace_filter
中列举的内核函数。可以写入 set_ftrace_filter
文件的内核函数名列举在 available_filter_functions
中。通常是写内核函数名到 set_ftrace_filter
,但也可以写内核函数在文件 available_filter_functions
中所在的行位置到 set_ftrace_filter
,行位置起始编号为 1 。
set_ftrace_notrace
和 set_ftrace_filter
刚好相反,ftrace
禁用对其中列举函数的追踪。
ftrace
对 set_ftrace_pid
文件中包含的 PID 关联的进程进行追踪。如果开启了 options/function-fork
选项,fork 的子进程的 PID 也会自动加入该文件,同时该选项也会引起 ftrace
自动将已终止进程的 PID 从文件中移除。
set_ftrace_notrace_pid
和 set_ftrace_pid
相反,ftrace
将不对 set_ftrace_notrace_pid
文件中包含的 PID 关联的进程进行追踪。如果开启了 options/function-fork
选项,fork 的子进程的 PID 也会自动加入文件,同时该选项也会引起 ftrace
自动将已终止进程的 PID 从文件中移除。
如果 set_ftrace_pid
和 set_ftrace_notrace_pid
同时包含某个进程 PID ,set_ftrace_notrace_pid
的设置具有更高优先级。
如果对 set_event_pid
文件进行了设置,ftrace
将仅追踪 set_event_pid
中 PID 指定进程的 event 。如果开启了 options/event-fork
选项,fork 的子进程的 PID 也会自动加入文件,同时该选项也会引起 ftrace
自动将已终止进程的 PID 从文件中移除。
ftrace
不追踪 set_event_notrace_pid
中 PID 指定进程的 event 。如果开启了 options/event-fork
选项,fork 的子进程的 PID 也会自动加入文件,同时该选项也会引起 ftrace
自动将已终止进程的 PID 从文件中移除。
function_graph
tracer 将仅追踪文件 set_graph_function
中列举的函数。注意,set_ftrace_filter
和 set_ftrace_notrace
仍会影响 被追踪
或 不被追踪
的函数列表。
set_graph_notrace
和 set_graph_function
相反,包含不被 function_graph
tracer 追踪的函数列表。
可以被追踪的内核函数列表,即可以写到 set_ftrace_filter,set_ftrace_notrace,set_graph_function,set_graph_notrace
文件的函数列表。
用于调试,记录系统中可被追踪的函数总数。
TODO:
如果设置了 function_profile_enabled
(对文件写1),ftrace
将启用对所有 available_filter_functions
列举的内核函数的 function
tracer 追踪,如果启用了 function_graph
tracer ,也将启用对available_filter_functions
列举的内核函数的 function_graph
追踪。同时也会保留调用的函数数量的直方图,还将跟踪在这些函数中花费的时间。直方图内容可以显示在文件中:trace_stat/function[cpu]
。如:
# echo 1 > function_profile_enabled
# cat trace_stat/function0
Function Hit Time Avg s^2
-------- --- ---- --- ---
schedule 4368 334221648 us 76515.94 us 735411.5 us
do_syscall_64 15508 222285003 us 14333.56 us 19887.52 us
schedule_hrtimeout_range 1879 189004501 us 100587.8 us 220126.6 us
schedule_hrtimeout_range_clock 1879 189003100 us 100587.0 us 222184.7 us
...
注意,如果设置了 function_profile_enabled
(对文件写1),tracing_on
即使为 0 ,ftrace
也会启动追踪过程,更新 ring buffer
。
包含各种 ftrace
统计数据的目录,为每个 CPU buffer
建立1个目录:trace_stat/function[cpu]
。
# ls -l trace_stat
total 0
-rw-r--r-- 1 root root 0 4月 12 10:06 function0
-rw-r--r-- 1 root root 0 4月 12 10:06 function1
...
参见 https://www.kernel.org/doc/html/latest/trace/kprobetrace.html 。
参见 添加链接描述 。
function_graph
类别追踪中,显示的函数调用栈的最大深度。
如果某个追踪事件上报到 ring buffer
的数据引用了字符串,那么 ring buffer
只会记录该字符串的地址,这样我们没法知道该地址指向的字符串内容。printk_formats
文件建立了 字符串地址
到 字符串内容
的映射表,这样就可以通过地址来查找到字符串的内容。
# cat printk_formats
0xffffffff950be839 : "CPU_ON"
0xffffffff950be839 : "CPU_ON"
0xffffffff950be840 : "CPU_OFF"
0xffffffff950be840 : "CPU_OFF"
0xffffffff950c6525 : "thaw_processes"
0xffffffff950c6525 : "thaw_processes"
0xffffffff950c66e4 : "suspend_enter"
...
saved_cmdlines
文件记录了 进程 PID
到 进程名
的映射表。除非特别去记录 进程名
,否则 trace event 通常只会记录 进程 PID
。ftrace
维护一个 进程 PID
到 进程名
的映射到 saved_cmdlines
缓存文件,当输出追踪信息时,通过文件 saved_cmdlines
将 进程 PID
映射为 进程名
显示出来,如果当前 saved_cmdlines
没有某个 进程 PID
的映射项,输出显示 进程名
为 <…>
,如:
<...>-4587 [001] .... 19257.259293: vfs_open <-path_openat
如果选项文件 options/record-cmd
设为 0 ,表示在追踪过程中,不缓存 进程 PID
到 进程名
的映射数据到文件 saved_cmdlines
,默认 options/record-cmd
的值为 1。
默认 saved_cmdlines
只缓存 128
个进程名映射项,如果要增加缓存数目,通过修改 saved_cmdlines_size
来完成。
saved_tgids
文件记录 进程 PID
到 进程所在进程组 ID
的映射表。如果选项文件 options/record-tgid
为 1 ,每当发生进程切换时,进程 PID
到 进程所在进程组 ID
的映射将记录到文件 saved_tgids
。默认 options/record-tgid
为 0 。
见后面章节。
见后面章节。
见后面章节。
见后面章节。
ftrace
记录追踪事件到 ring buffer
时,总会记录它的时间戳信息。时间戳来源于指定的时钟,默认情况下,ftrace
使用 CPU 本地时钟(local)
,这个时间变换很快,并且是特定于 CPU 的。
# cat trace_clock
[local] global counter uptime perf mono mono_raw boot x86-tsc
有时候可能有需求,要形成全局的参照,这时候使用 CPU 本地时钟 local ,可能就不合适了。此时可以通过命令修改为全局时钟(global)
,将所有CPU追踪记录的时间戳,统一到同一时间轴上来,这样就可以看出不同CPU事件数据之间发生的先后关系了。
# echo global > trace_clock
# cat trace_clock
local [global] counter uptime perf mono mono_raw boot x86-tsc
修改 trace_clock
文件,将会清空 ring buffer
和 snapshot
缓冲内容。
写到文件 trace_marker
的内容,将会写入到 ftrace
的追踪缓冲。通常的应用是:启用追踪,写一个开始标记;结束追踪前,再写一个结束标记。这样很方便在两个标记之间查找关心的内容。
# echo 1 > tracing_on
# echo "START_MAKER" > trace_marker
# echo "END_MARKER" > trace_marker
# echo 0 > tracing_on
# cat trace | grep "START_MAKER"
bash-2819 [000] .... 11433.485740: tracing_mark_write: START_MAKER
# cat trace | grep "END_MARKER"
bash-2819 [000] .... 11453.265817: tracing_mark_write: END_MARKER
类似于 trace_marker
,但是以二进制方式写入到 trace 缓冲。对 trace 原始数据感兴趣的工具可以从 trace_pipe_raw
中提取标记。
参见 https://www.kernel.org/doc/html/latest/trace/uprobetracer.html 。
参看内核文档 Documentation/trace/uprobetracer.txt
。
见后面章节。
trace event 目录
,包含静态编译到内核的 event tracepoint
。
写文件 set_event
将启用指定的 trace event
。可写入的 trace event
名,查看文件 available_events
。更多细节参考 Event Tracing 和 Event Histograms 。
系统支持的 trace event
列表,可通过写 trace event
名到文件 set_event
启用。
TODO:
包含延迟检查硬件信息文件。
包含追踪系统 每 CPU buffer 包含的信息。
跟踪所有内核函数的 tracer。
类似于 function
tracer ,和 function
tracer 不同的是:function
tracer 只跟踪函数的进入,而 function_graph
tracer 既跟踪函数的进入,又跟踪函数的退出,因此可以绘制类似于C代码的函数调用图。
block tracer,供 blktrace
存储 IO 工具使用。
硬件延迟 tracer ,用来检测是否有硬件产生的延迟。
追踪中断延迟,记录最大中断延迟到 tracing_max_latency
。
追踪禁用抢占的最长时间,记录最长禁用时间到 tracing_max_latency
。
追踪 中断延迟 和 禁用抢占 两者中的最长时间,记录到 tracing_max_latency
。
追踪记录最高优先级任务,从其被唤醒开始到被调度的最大延迟,记录到 tracing_max_latency
。
类似于 wakeup
tracer ,但仅追踪记录 RT 调度类
任务。
类似于 wakeup
tracer ,但仅追踪记录 DL(DeadLine) 调度类
任务。
TODO:
用来追踪系统中 likely()
或 unlikely()
调用,用于观察 likely()
或 unlikely()
分支预测是否正确。
关闭 tracer 。
# echo function > current_tracer
# echo > trace 清空跟踪缓冲
# echo 1 > tracing_on 开启追踪
# cat trace 查看追踪信息
# tracer: function
#
# entries-in-buffer/entries-written: 205087/1383792 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION
# | | | | |||| | |
vmtoolsd-1512 (-----) [000] .... 29892.117923: __inode_wait_for_writeback <-inode_wait_for_writeback
vmtoolsd-1512 (-----) [000] .... 29892.117923: bit_waitqueue <-__inode_wait_for_writeback
...
# echo wakeup > current_tracer
# echo > trace 清空跟踪缓冲
# echo 1 > tracing_on 开启追踪
# cat trace 查看追踪信息
# tracer: wakeup
#
# wakeup latency trace v1.1.5 on 4.15.0-142-generic
# --------------------------------------------------------------------
# latency: 112 us, #146/146, CPU#2 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: kworker/2:1H-313 (uid:0 nice:-20 policy:0 rt_prio:0)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
<idle>-0 2dNs. 1us : 0:120:R + [002] 313:100:R kworker/2:1H
<idle>-0 2dNs. 4us : ttwu_do_activate <-try_to_wake_up
<idle>-0 2dNs. 4us : ttwu_stat <-try_to_wake_up
<idle>-0 2dNs. 5us : _raw_spin_unlock_irqrestore <-try_to_wake_up
<idle>-0 2.Ns. 5us : _raw_spin_lock_irq <-run_timer_softirq
<idle>-0 2.Ns. 6us : _raw_spin_lock_irq <-run_timer_softirq
<idle>-0 2dNs. 6us : __next_timer_interrupt <-run_timer_softirq
<idle>-0 2dNs. 8us : call_timer_fn <-run_timer_softirq
<idle>-0 2dNs. 8us : delayed_work_timer_fn <-call_timer_fn
<idle>-0 2dNs. 8us : __queue_work <-delayed_work_timer_fn
<idle>-0 2dNs. 9us : get_work_pool <-__queue_work
<idle>-0 2dNs. 10us : _raw_spin_lock <-__queue_work
<idle>-0 2dNs. 10us : insert_work <-__queue_work
<idle>-0 2dNs. 11us : get_pwq.isra.20 <-insert_work
<idle>-0 2dNs. 12us : wake_up_process <-insert_work
<idle>-0 2dNs. 13us : try_to_wake_up <-wake_up_process
...
<idle>-0 2d... 302us : __schedule <-schedule_idle
<idle>-0 2d... 302us : 0:120:R ==> [002] 1732: 0:R rtkit-daemon
# cat tracing_max_latency 查看最大延迟为 302us
302
# echo function_graph > current_tracer
# echo > trace 清空跟踪缓冲
# echo 1 > tracing_on 开启追踪
# cat trace 查看追踪信息
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
3) 2.319 us | } /* apparmor_socket_recvmsg */
3) 3.737 us | } /* security_socket_recvmsg */
3) | unix_stream_recvmsg() {
3) | unix_stream_read_generic() {
3) | mutex_lock() {
3) | _cond_resched() {
3) 0.481 us | rcu_all_qs();
3) 1.369 us | }
3) 2.188 us | }
3) 0.479 us | _raw_spin_lock();
3) 0.526 us | mutex_unlock();
3) 0.465 us | put_pid();
3) 6.043 us | }
3) 7.018 us | }
3) + 12.114 us | } /* sock_recvmsg */
...
3) ! 122.290 us | }
3) ! 123.110 us | }
3) ! 125.997 us | }
...
------------------------------------------
3) gnome-t-3300 => <idle>-0
------------------------------------------
3) 0.621 us | finish_task_switch();
3) # 1956.576 us | } /* schedule_idle */
3) * 11216.74 us | } /* do_idle */
function_graph
是很有用的 tracer ,因为它追踪了函数的进入和退出,所以能够评测函数的执行时间。在上面我们看到 #,*,!
等这些奇怪的符号,它们的含义是用来标记值得注意的时间消耗,这些符号的具体含义如下:
‘$’ - greater than 1 second
‘@’ - greater than 100 millisecond
‘*’ - greater than 10 millisecond
‘#’ - greater than 1000 microsecond
‘!’ - greater than 100 microsecond
‘+’ - greater than 10 microsecond
‘ ‘ - less than or equal to 10 microsecond.
# cat trace_options 查看支持的追踪选项
print-parent
nosym-offset
nosym-addr
noverbose
noraw
nohex
nobin
noblock
trace_printk
annotate
...
要禁用
某个选项,通过在选项名前加上 "no"
前缀,然后写入到 trace_options
文件,或者写 0 到 options/选项名
文件;
要启用
某个选项,通过移除选项名前 "no"
前缀,然后写入到 trace_options
文件,或者写 1 到 options/选项名
文件。
function tracer的输出数据中,是否输出调用链中 caller 函数。
# cat trace_options | grep print-parent
print-parent
# echo function > current_tracer
# echo > trace 清空跟踪缓冲
# echo 1 > tracing_on 开启追踪
# cat trace 查看追踪信息
irqbalance-995 [003] .... 32160.569842: seq_put_decimal_ull <-show_stat
irqbalance-995 [003] .... 32160.569843: kstat_irqs_usr <-show_stat
# echo noprint-parent > trace_options
# cat trace_options
noprint-parent
...
# echo > trace 清空跟踪缓冲
# echo 1 > tracing_on 开启追踪
# cat trace
update-notifier-2727 [003] .... 32315.372687: copy_msghdr_from_user
update-notifier-2727 [003] .... 32315.372689: rw_copy_check_uvector
update-notifier-2727 [003] .... 32315.372689: __check_object_size
update-notifier-2727 [003] .... 32315.372690: check_stack_object
update-notifier-2727 [003] .... 32315.372691: sock_recvmsg
看看,后一记录没有了 <-
标记的调用链。
以函数名+偏移形式显示调用链:
# echo sym-offset > trace_options
# cat trace 查看追踪信息
gnome-terminal--3300 [000] .... 33488.523955: sock_poll+0x0/0x90 <-do_sys_poll+0x367/0x600
...
# echo sym-addr > trace_options
# cat trace 查看追踪信息
compiz-2280 [003] .... 33594.617764: sock_poll+0x0/0x90 <ffffffff9484dc30> <-do_sys_poll+0x367/0x600 <ffffffff9429bec7>
...
写 0 禁用 trace_printk()
写入缓冲区。
有些时候,当所有的 CPU buffer 被填满了,其中一个 CPU 的某个 buffer 有很多新近的事件数据,因此其包含的事件数据时间范围更短;而另一个 CPU 的某个 buffer 可能只有几个新近的
事件数据,这使得其包含的事件数据的时间范围较前一个CPU buffer更早。当报告事件数据时,会先显示时间上更靠前的数据,这样输出的报告数据,看起来就像是只有包含时间上更早事件数据的CPU在运行一样,让人感到困惑。当开启 annotate
选项时,每当报告另一个 CPU 的 buffer 数据时,就会插入一个注释行,提示接下来输出的是另一个 CPU 的 buffer 数据。如:
<idle>-0 [001] d... 36111.794219: nohz_balance_enter_idle <-__tick_nohz_idle_enter
<idle>-0 [001] d... 36111.794219: rcu_irq_exit <-irq_exit
<idle>-0 [001] d... 36111.794219: rcu_eqs_enter_common <-rcu_irq_exit
<idle>-0 [001] .... 36111.794221: rcu_idle_exit <-do_idle
<idle>-0 [001] d... 36111.794221: rcu_dynticks_eqs_exit <-rcu_idle_exit
<idle>-0 [001] .... 36111.794221: arch_cpu_idle_exit <-do_idle
<idle>-0 [001] d... 36111.794222: arch_cpu_idle_enter <-do_idle
<idle>-0 [001] d... 36111.794222: tsc_verify_tsc_adjust <-arch_cpu_idle_enter
<idle>-0 [001] d... 36111.794223: local_touch_nmi <-arch_cpu_idle_enter
<idle>-0 [001] d... 36111.794223: tick_check_broadcast_expired <-do_idle
<idle>-0 [001] d... 36111.794224: cpuidle_get_cpu_driver <-do_idle
<idle>-0 [001] d... 36111.794224: rcu_idle_enter <-do_idle
<idle>-0 [001] d... 36111.794224: rcu_eqs_enter_common <-rcu_idle_enter
<idle>-0 [001] d... 36111.794224: cpuidle_not_available <-do_idle
<idle>-0 [001] d... 36111.794225: default_idle_call <-do_idle
<idle>-0 [001] d... 36111.794225: arch_cpu_idle <-default_idle_call
<idle>-0 [001] d... 36111.794225: default_idle <-arch_cpu_idle
##### CPU 2 buffer started ####
compiz-2280 [002] .... 36111.798775: __check_object_size <-do_sys_poll
...
看上面的输出,当从 CPU 1 的 buffer 切换到 CPU 2 的 buffer 时,输出了一个注释行:
##### CPU 2 buffer started ####
这就是 annotate
选项的作用。
在每个 trace event 记录之后,都伴随着一个当前用户空间线程的栈追踪记录。
当 userstacktrace
开启时,开启 sym-userobj
时将输出堆栈中地址所属的 文件/目标/行号
,并且打印地址在所属目标文件内的相对地址。这在启用了 ASLR(Address Space Layout Randomization)
,否则在应用退出后,你将再没有机会将栈中地址解析为对应的 文件/目标/行号
。如:
a.out-1623 [000] 40874.465068: /root/a.out[+0x480] <-/root/a.out[+0
x494] <- /root/a.out[+0x4a8] <- /lib/libc-2.7.so[+0x1e1a6]
控制当 ftrace 追踪 ring buffer 满时,采取的策略。如果为1(默认值),覆写时间上最早的数据;如果为0,则丢弃新数据。
如果启用,延迟追踪类 tracer(如 irqoff 等)
将开启对函数的追踪(function trace)。默认启用。
如果启用,在每个 trace event 记录之后,都会伴随着一个栈追踪记录。
TODO
ftrace 过滤命令有如下通用格式:
echo <function>:<command>:<parameter> > set_ftrace_filter
echo <function>:<command>:<parameter> >> set_ftrace_filter
mod
命令用来控制每 module 的函数过滤。
如:
echo 'write*:mod:ext3' > set_ftrace_filter
其中 mod
命令使能每 module 的函数过滤,write*:mod:ext3
合起来的意思就是值追踪模块 ext3 中的匹配 write* 模式的函数。用下面的命令移除上述过滤器:
echo '!write*:mod:ext3' >> set_ftrace_filter
就是在过滤表示式前加一个 !
号。下面列举了几个常见 mod 操作命令:
echo '!*:mod:!ext3' >> set_ftrace_filter 禁用除了ext3外其它所有模块函数的跟踪
echo '!*:mod:*' >> set_ftrace_filter 禁用对所有模块的追踪
...
在特定的函数被调用时,打开或关闭追踪系统。
echo '__schedule_bug:traceoff:5' > set_ftrace_filter 前5次__schedule_bug()调用关闭trace
echo '!__schedule_bug:traceoff:0' > set_ftrace_filter 移除上一条过滤规则
echo '__schedule_bug:traceoff' > set_ftrace_filter 只要调用__schedule_bug()就关闭trace
echo '!__schedule_bug:traceoff' > set_ftrace_filter 移除上一条过滤规则
echo 'native_flush_tlb_others:snapshot' > set_ftrace_filter 触发snapshot每当native_flush_tlb_others()被调用时
echo '!native_flush_tlb_others:snapshot' > set_ftrace_filter 移除上一条过滤规则
echo 'native_flush_tlb_others:snapshot:1' > set_ftrace_filter 只触发一次snapshot
echo '!native_flush_tlb_others:snapshot:0' > set_ftrace_filter 移除上一条过滤规则
命令格式如下:
echo <function>:enable_event:<system>:<event>[:count] > set_ftrace_filter
echo <function>:disable_event:<system>:<event>[:count] > set_ftrace_filter
如:
# 启用事件 sched:sched_switch 的过滤规则
echo 'try_to_wake_up:enable_event:sched:sched_switch:2' > set_ftrace_filter
# 移除事件 sched:sched_switch 过滤规则的两种方式
echo '!try_to_wake_up:enable_event:sched:sched_switch:0' > set_ftrace_filter
echo '!schedule:disable_event:sched:sched_switch' > set_ftrace_filter
dump: 某 function 触发时,导出 trace 缓冲内容到控制台
cpudump: 某 function 触发时,导出执行 function 的 CPU 的缓冲内容到控制台
stacktrace: 某 function 触发时,记录 stack trace
命令的格式同前面一样:
echo <function>:<command>:<parameter> > set_ftrace_filter
其中的 command
分别是 dump,cpudump,stacktrace
。
如果内核开启了配置项 CONFIG_TRACER_SNAPSHOT
,所有 非延迟类追踪tracer
都可以使用 snapshot
特性。那到底什么是 snapshot
?顾名思义,snapshot
就是当前 ftrace
追踪 ring buffer
内容的一个快照,即保存当前 ftrace
追踪 ring buffer
内容的一个缓冲区。
接下来,看看该怎么使用 snapshot
,下面的表格给出了 snapshot
用法:
-----------------------------------------------------------
| status \ input | 0 | 1 | else |
|----------------|--------------|------------|--------------|
| not allocated | (do nothing) | alloc+swap | (do nothing) |
|----------------|--------------|------------|--------------|
| allocated | free | swap | clear |
-----------------------------------------------------------
以一个例子来说明 snapshot
的用法:
# echo 1 > events/sched/enable 启用对调度事件的追踪
# echo 1 > snapshot 分配一个snapshot缓冲,并且将该缓冲和当前ftrace缓冲互换
# cat snapshot 读取snapshot缓冲,即ftrace缓冲的快照
# tracer: nop
#
# entries-in-buffer/entries-written: 12153/12153 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
systemd-journal-392 [001] d... 1257.482249: sched_stat_runtime: comm=systemd-journal pid=392 runtime=208507 [ns] vruntime=111987337 [ns]
bash-2819 [002] dN.. 1257.482257: sched_stat_runtime: comm=bash pid=2819 runtime=1572127 [ns] vruntime=97226080 [ns]
in:imklog-1090 [002] d... 1257.482357: sched_stat_runtime: comm=in:imklog pid=1090 runtime=274641 [ns] vruntime=27355353 [ns]
kworker/2:0-2818 [002] d... 1257.482367: sched_stat_runtime: comm=kworker/2:0 pid=2818 runtime=10041 [ns] vruntime=103641088200 [ns]
rs:main Q:Reg-1092 [003] d... 1257.482528: sched_stat_runtime: comm=rs:main Q:Reg pid=1092 runtime=197090 [ns] vruntime=52490293 [ns]
...
echo 1 > snapshot
命令的工作细节是:ftrace 分配一个空闲 buffer ,然后将 ftrace 的当前 buffer
和 空闲 buffer
交换,空闲 buffer
变成了 ftrace 的当前 buffer 。cat snapshot
命令读取 和 空闲 buffer
交换的 buffer 的内容,其内容为 ftrace 交换 buffer 时追踪内容的快照。
用完 snapshot 缓冲后,可以通过 echo 0 > snapshot
命令释放它,这里释放的是被交换出来的那个 buffer ,而不是新分配的。
# echo 0 > snapshot 释放缓冲
# cat snapshot 再次查看,内容已经为空
# tracer: nop
#
#
# * Snapshot is freed *
#
# Snapshot commands:
# echo 0 > snapshot : Clears and frees snapshot buffer
# echo 1 > snapshot : Allocates snapshot buffer, if not already allocated.
# Takes a snapshot of the main buffer.
# echo 2 > snapshot : Clears snapshot buffer (but does not allocate or free)
# (Doesn't have to be '2' works with any number that
# is not a '0' or '1')
前述所有章节中,ftrace
所有的追踪,都是使用了唯一的一个全局 ring buffer
,这是系统启动期间 ftrace
创建的。假设现在想用一个不同于全局 ring buffer
的新缓冲,来记录追踪数据,该怎么办?答案非常简单,就是用 mkdir
命令在 instances 目录
下新建一个子目录,ftrace
将为你创建一个新的 ring buffer
来记录追踪数据;如果使用完成后,想移除该缓冲,使用 rmdir
命令删除 instances 目录
下新建的子目录即可。来看一个例子:
# mkdir instances/foo
# ls instances/foo/ -l
-r--r--r-- 1 root root 0 4月 13 12:39 available_tracers
-rw-r--r-- 1 root root 0 4月 13 12:39 buffer_size_kb
-r--r--r-- 1 root root 0 4月 13 12:39 buffer_total_size_kb
-rw-r--r-- 1 root root 0 4月 13 12:39 current_tracer
drwxr-xr-x 85 root root 0 4月 13 12:39 events
--w------- 1 root root 0 4月 13 12:39 free_buffer
drwxr-xr-x 2 root root 0 4月 13 12:39 options
drwxr-xr-x 130 root root 0 4月 13 12:39 per_cpu
-rw-r--r-- 1 root root 0 4月 13 12:39 set_event
-rw-r--r-- 1 root root 0 4月 13 12:39 set_event_pid
-rw-r--r-- 1 root root 0 4月 13 12:39 set_ftrace_filter
-rw-r--r-- 1 root root 0 4月 13 12:39 set_ftrace_notrace
-rw-r--r-- 1 root root 0 4月 13 12:39 set_ftrace_pid
-rw-r--r-- 1 root root 0 4月 13 12:39 snapshot
-rw-r--r-- 1 root root 0 4月 13 12:39 trace
-rw-r--r-- 1 root root 0 4月 13 12:39 trace_clock
--w--w---- 1 root root 0 4月 13 12:39 trace_marker
--w--w---- 1 root root 0 4月 13 12:39 trace_marker_raw
-rw-r--r-- 1 root root 0 4月 13 12:39 trace_options
-r--r--r-- 1 root root 0 4月 13 12:39 trace_pipe
-rw-r--r-- 1 root root 0 4月 13 12:39 tracing_cpumask
-rw-r--r-- 1 root root 0 4月 13 12:39 tracing_max_latency
-rw-r--r-- 1 root root 0 4月 13 12:39 tracing_on
看到了吗?和 ftrace
导出的顶层目录及其相似,用法也一样,对此处的修改,不会影响系统启动期间 ftrace
创建的全局缓冲。用完之后,删除即可:
# rmdir instances/foo
# ls instances/ -l
total 0
要使用 stack trace 功能,首先内核要开启 CONFIG_STACK_TRACER
配置项。另外,stack trace 功能有一个开关,可以在内核命令行添加 stacktrace
开启,或者对文件 /proc/sys/kernel/stack_tracer_enabled
写 1
来开启。
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
# cat stack_max_size
2920
# # cat stack_trace
Depth Size Location (15 entries)
----- ---- --------
0) 2888 64 __msecs_to_jiffies+0x5/0x30
1) 2824 88 update_group_capacity+0x28/0x1f0
2) 2736 40 find_busiest_group+0xfb/0xa20
3) 2696 600 load_balance+0x195/0xa20
4) 2096 120 pick_next_task_fair+0x419/0x580
5) 1976 104 __schedule+0x118/0x8b0
6) 1872 24 schedule+0x36/0x80
7) 1848 144 schedule_hrtimeout_range_clock+0x196/0x1b0
8) 1704 16 schedule_hrtimeout_range+0x13/0x20
9) 1688 32 poll_schedule_timeout+0x45/0x70
10) 1656 928 do_select+0x6eb/0x830
11) 728 416 core_sys_select+0x1f6/0x310
12) 312 104 SyS_select+0xba/0x110
13) 208 32 do_syscall_64+0x73/0x130
14) 176 176 entry_SYSCALL_64_after_hwframe+0x41/0xa6
stack_trace
的内容会随着时间的变化而变化,最大达到 stack_max_size
的深度。