===============================》内核新视界文章汇总《===============================
Ftrace是一个内部跟踪程序,用于帮助系统开发者和设计者观察内核内部正在发生的事情。它可以用于调试和分析用户空间之外的延迟和性能问题。
尽管ftrace通常被认为是函数跟踪程序,但他实际上已经是一个由几个分类跟踪实用程序组成的框架。有延时跟踪(latency tracing)去检查在中断禁用与激活之间发生了什么,以及抢占和从任务唤醒到实际任务调度的时间。
Ftrace最常见的用途是Event跟踪。内核有数百个静态事件点,可以通过tracefs文件系统启用这些时间点,以便查看内核的某些部分正在发生什么。详情看"Event trace"章节。
注意:本文主要根据linux-5.12内核文档Document/trace/ftrace.rst
和Document/trace/events.rst
说明。
内核文档的Documentation/trace/ftrace-design.rst
作者以注释设计文档已经过时参考价值不是很大,不过设计实现基础思想不变可以看一下,下面这篇博客也是一个对理解设计思想有价值的文档:
参考博客:https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/04-ftrace_internal
博客中链接附带的pdf文档很详细的说明了设计者的设计思路。
主要代码路径:kernel/trace/
,更多详细信息可以在源代码中获取,kernel/trace/*.c
。
Ftrace使用tracefs文件系统去保存控制文件和显示输出的文件。
当tracefs被配置进内核时(选择任何ftrace选项将完成此操作),目录/sys/kernel/tracing
将会被创建。为了挂在这个目录,你可以在/etc/fstab
文件中添加以下信息::
tracefs /sys/kernel/tracing tracefs defaults 0 0
或者在运行时挂在::
mount -t tracefs nodev /sys/kernel/tracing
为了快速访问这个目录可以创建一个软链接::
ln -s /sys/kernel/tracing /tracing
. .注意::
在4.1版本之前,所有的ftrace跟踪控制文件都在debugfs文件系统中,该文件系统通常位于/sys/kernel/debug/tracing
。为了向后兼容,当挂载debugfs文 件系统时,tracefs文件系统将自动挂载在/sys/kernel/debug/tracing
。
位于tracefs文件系统中的所有文件也将位于debugfs文件系统目录中。
. .注意::
任何选定的ftrace选项也将创建tracefs文件系统。文档中的操作都假定在ftrace的目录中(/sys/kernel/tracing
或者/sys/kernel/debug/tracing
)。
在mount tracefs后,即可访问ftrace的控制和输出文件。以下是一些关键文件的列表:
注意:所有的时间值都以微妙(us)为单位。
current_tracer
这个用于设置和显示已配置的当前tracer。更改当前tracer将会清除ring buffer内容和"snapshot"缓冲区(后面有章节专门介绍"snapshot"用法)。
available_tracers
这个文件保存了已编译到内核中的不同tracer。这里列出的tracer可以通过将其名称echo到current_tracer中来配置。
tracing_on
设置或者回显是否启用写入trace ring buffer。将0 echo到该文件以禁用tracer,或者将1 echo到该文件中以启用tracer。注意,这只是禁止写入到ring buffer,跟踪程序的开销仍然可能会发生。
可以在内核中使用内核函数tracing_off()
来禁用写入ring buffer,这将把该文件设置为"0"。用户空间可以通过在文件echo 1来重新启用tracing。
注意:函数和事件触发器"traceoff"也会将该文件设置为0并停止tracing。也可以由用户空间使用此文件重新启用tracing。
trace
这个文件以用户可读的格式保存了trace输出(如下所述)。使用O_TRUNC标志打开这个文件进行写入将清除ring buffer内容。注意,这个文件不是一个消费者。如果跟踪处于关闭状态(没有tracing运行,或者tracing_on为0),那么每次读取它时会产生相同的输出。当traceing处于开启状态,尝试读取整个缓冲区时而不清除它时,可能会产生不一致的结果。
trace_pipe
输出与“trace”文件相同,但该文件是用于实时跟踪流的。从这个文件读取trace将会阻塞直到有新数据到来。不像"trace"文件,这个文件是一个消费者。这意味着从这个文件读取将导致顺序读取去显示更多当前数据。一旦从这个文件中读取数据,它就会被消费掉,并且不会再被顺序读取出来。"trace"文件是静态的,如果tracing程序没有产生添加更多数据,那么每次读取数据时都会显示相同的信息。
trace_options
该文件允许用户控制在上述(“trace"或者"trace_pipe”)输出文件之一中显示的数据量,还有一些选项可以修改trace程序或事件的工作方式(堆栈跟踪,时间戳等,也可以直接使用options目录设置)。
options/
这个目录为每个可用的trace options(也在trace_options)提供一个文件。也可以通过向带有选项名称的相应文件写"1"或者"0"来设置或清除选项。
tracing_max_latency
有些tracer会记录最大的延迟。
例如,禁用中断(irqsoff tracer)最大的时间。该文件保存最大的时间。最大跟踪也将被存储,并通过"trace"显示。只有当延迟大于此文件中的值(以微妙为单位)时,该文件才会刷新记录新的max time。
tracing_thresh
当延迟大于此文件中的值时,一些延迟tracer才会记录跟踪。
只有在文件包含大于0的数字时激活(以微妙为单位)。
buffer_size_kb
这个文件设置或者显示每个cpu缓冲区保存的千字节数。默认情况下,每个cpu的trace buffer大小相同。显示的数字是单个cpu缓冲区的大小,而不是所有缓冲区的总和。trace buffer以页的形式分配(内核用于分配的内存块,通常的大小是4KB btyes)。可能会分配一些额外的页面来容纳缓冲区管理元数据。如果分配的最后一页的空间大于请求的字节,那么该页面余下部分将有预留,从而使实际分配的大小大于请求的大小和显示的大小(注意,由于缓冲区的元数据管理,大小可能不是页面的倍数)。
每个cpu的缓冲区大小可能会有所不同(参考下面的"per_cpu/cpu0/buffer_size_kb"),如果有这样去为单独某个cpu设置,这个文件将会显示"X"。
buffer_total_size_kb
这个文件显示了所有trace buffer大小的总和。
free_buffer
如果一个进程正在执行tracing,并且在进程完成时ring buffer应该被收缩"freed",即使它将被一个信号杀死,这个文件用于这样的目的。在关闭该文件时,ring buffer将被调整为其最小大小。如果有一个tracing的进程也打开了这个文件,当该进程退出该文件的文件描述符时,该文件的描述符将被关闭,在此过程,ring bufer也将被"freed"。
如果options/disable_on_free选项被设置将会停止tracing。
tracing_cpumask
这个cpumask允许用户只跟踪指定的cpu。格式是cpu的十六进制字符串。
set_ftrace_filter
当在内核配置中启用了"dynamic ftrace",将动态修改代码(code text重写)以禁用函数探测器(mcount)的调用。这使得tracing被配置成实际上几乎没有性能开销。这样做也有一个副作用,即启用或者禁用要tracing的特定函数,将函数名echo到该文件将只会tracing这个文件里的函数。
这将影响tracer “function"和"function_graph”,从而也影响函数分析(参考"function_profile_enabled")。
在"available_filter_functions"文件中列出了所有可以写入该文件的trace函数。
该接口支持commands的使用,更过详细信息,参阅Ftrace的Filter commands章节。
作为一种提速手段,因为处理字符串可能代价昂贵,并且需要检查注册到trace的所有函数,所以可以将索引写入该文件。一个数字(以"1"开头)将选择与"available_filter_functions"文件相同的行位置。
set_ftrace_notrace
这与"set_ftrace_filter"的效果相反。添加到这里的任何函数都不会被跟踪。如果一个函数同时存在于"set_ftrace_filter"和"set_ftrace_notrace"中,则该函数将不会被跟踪,即"set_ftrace_notrace"优先级高于"set_ftrace_filter"。
set_ftrace_pid
让function tracer只跟踪PID在该文件中列出的线程。
如果options/设置了"function-fork"选项,那么当PID列在该文件中的任务被fork时,子任务的PID会自动添加到该文件中,并且该子任务也会被function tracer跟踪。此选项还将导致退出的任务的pid从该文件中删除。
set_ftrace_notrace_pid
让function tracer忽略该文件中列出的PID的线程。
如果options/设置了"function-fork"选项,那么当PID列在该文件中的任务fork时,子任务的PID将自动添加到该文件中,并且该子任务也不会被function tracer跟踪。此选项还将导致退出的任务的PID从该文件中删除。
如果PID同时存在与"set_ftrace_pid"和"set_ftrace_notrace_pid"中,那么这个文件优先级更高,线程将不会被tracing。
set_event_pid
让事件只跟踪该文件列出的具有PID的任务。注意,事件"sched_switch"和"sched_wake_up"也将跟踪该文件中列出的事件。
要在fork上添加该文件中的PID的子任务的PID,请启用options “event-fork”。该选项还将导致在任务退出时从该文件中删除任务的pid。
set_event_notrace_pid
让事件不跟踪具有此文件中列出的PID的任务。注意,事件"sched_switch"和"sched_wakeup"将跟踪该文件中未列出的线程,即使线程的PID在文件中,而事件"sched_switch"和"sched_wakeup"也跟踪应该tracing的线程。
要在fork上添加该文件中的PID的子任务的PID,请启用options “event-fork”。该选项还将导致在任务退出时从该文件中删除任务的pid。
set_graph_function
此文件列出的函数将导致function_graph tracer只跟踪这些函数及其调用的函数(参考"dynamic ftrace"章节获取更多信息)。注意,"set_ftrace_filter"和"set_ftrace_notrace"仍然影响正在tracing的函数。
set_graph_notrace
类似于"set_graph_function",但是当函数被命中时将禁用function_graph tracer,直到它退出函数。这使得忽略由特定函数调用的tracing函数成为可能。
available_filter_functions
它列出了ftrace处理过的和可以跟踪的函数。这些是可以传递给"set_ftrace_filter",“set_ftrace_notrace”,“set_graph_function”,或"set_graph_notrace"的函数名(参考"dynamic ftrace"章节获取更多信息)。
dyn_ftrace_total_info
此文件用于调试目的。已转换为nops并可被跟踪的函数的数量。
enabled_functions
这个文件更多的是用于调试ftrace,但也可以用于查看是否有任何函数附加了回调。不仅ftrace框架使用ftrace函数tracing,其他子系统也可能使用。该文件显示所有附加回调的函数,以及附加回调的数量。注意,一个回调也可以调用多个函数,这些函数不会在这个计数中列出。
如果回调被一个带有"save regs"属性的函数注册tracing(这样开销更大),一个’R’将显示在与返回寄存器的函数的同一行上。
如果回调被一个带有"ip modify"属性的函数注册tracing(这样regs->ip就可以被修改),'I’将显示在可以被覆盖的函数的同一行上。
如果体系架构支持,它还将显示函数直接调用的回调。如果计数大于1,则很可能是ftrace_ops_list_func()。
如果函数的回调跳转到特定于回调而不是标准的"跳转点"的跳转点,它的地址将和跳转点调用的函数一起打印。
function_profile_enabled
当我们设置时所有function都被激活,要么是function tracer,要么是配置后的function graph tracer。它将保存被调用函数数量的直方图,如果配置了function graph tracer,它还将跟踪在这些函数中花费的时间。直方图的内容在文件中显示:
trace_stat/function
trace_stat/
保存不同tracing统计信息的目录。
kprobe_events
激活 dynamic trace opoints。看内核文档Documentation/trace/kprobetrace.rst
。
kprobe_profile
Dynamic trace points 统计信息。 看内核文档Documentation/trace/kprobetrace.rst
。
max_graph_depth
被用于function_graph tracer。这是tracing一个函数的最大深度。将其设置为1将只显示从用户空间调用的第一个内核函数。
printk_formats
这是用于读取原始格式文件的工具。如果缓冲区中的事件引用了字符串,则只记录指向该字符串的指针到缓冲区中,而不记录字符串本身。这将阻止工具知道该字符串是什么。该文件显示字符串和字符串地址,允许工具将指针映射到字符串的内容。
saved_cmdlines
只有任务的pid被记录在跟踪事件中,除非该事件还特别保存了任务comm。Ftrace缓存了comms的pid映射,以尝试显示事件的comms。如果未列出comm的pid则在输出中显示"<…>"。
如果record-cmd选项为0,则记录过程中不保存任务的comm。缺省情况下,启用该功能。
saved_cmdlines_size
默认可以保存128个comms(见上面saved_cmdlines)。要增加或减少缓存的通信数量,echo数量到这个文件。
saved_tgids
如果设置了"record-tgid"选项,在每次调度上下文切换时,任务的Task Group ID会保存在一个表中,将线程的PID映射到其TGID。默认情况下,”record-tgid“选项是禁用的。
snapshot
这个文件显示"snapshot”缓冲区,并允许用户获取当前运行tracing的"snapshot"(快照)。
更多细节看下面"snapshot"部分。
stack_max_size
当stack tracer被激活时,这将显示它遇到的最大堆栈大小。参考下面的"Stack Trace"部分。
stack_trace
这个文件显示激活stack tracer时遇到的最大堆栈的堆栈反向跟踪。参考下面的"Stack Trace"部分。
stack_trace_filter
这个类似于“set_ftrace_filter”,但它限制了堆栈跟踪程序将检查的函数。
trace_clock
每当一个事件被记录到ring buffer时,就会添加一个"时间戳"。这个戳来自一个特定的时钟。默认情况下,ftrace使用"local"时钟。这个时钟非常快,并且严格按照cpu计算,但在一些系统上,它可能与其他cpu相比并不单调。也就是说,"local"时钟与其他cpu上的"local"时钟不同步。
用于跟踪的常用时钟::
# cat trace_clock
[local] global counter uptime perf mono mono_raw boot
有方括号的时钟是有效时钟。
local
默认时钟,但可能不会在cpu之间同步
global
这个时钟与所有cpu同步,但可能比本地时钟慢一点。
counter
不是时钟,是一个原子counter计数器。逐个计数,但与所有cpu同步。当需要确切地知道事件在不同cpu上发生地顺序时,很有用。
uptime
使用jiffies计数器,时间戳是相对于启动后的时间。
perf
使得ftrace和perf使用相同的时钟。最终perf将能够读取ftrace缓冲区,有助于交织数据。
x86-tsc
架构自定义时钟,例如,x86在这里使用它自己的TSC循环时钟。
ppc-tb
架构自定义,powerpc是基寄存器值。这是在cpu之间同步的,如果tb_offset是已知的,还可以用于跨hypervisor/guest关联事件。
mono
它使用快速单调时钟(CLOCK_MONOTONIC),该时钟是单调的,并受NTP速率调整的影响。
mono_raw
这是原始单调时钟(clock_monoic_raw),它是单调的,但不受任何速率调整和节拍的影响,其速率与硬件时钟源相同。
boot
这是引导时钟(CLOCK_BOOTTIME),它基于快速单调时钟,但也考虑了挂起所花费的时间。由于时钟访问被设计用于在挂起路径中跟踪,如果在快速单点时钟更新之前计算挂起时间之后访问时钟,可能会产生一些副作用。在这种情况下,时钟更新似乎比正常情况下发生的稍早。同样在32位系统上,64位的引导偏移量可能会出现部分更新。这些效果是罕见的,后处理应该能够处理它们。有关更多信息,请参阅ktime_get_boot_fast_ns()函数中的注释。
要设置这些时钟echo时钟名字到这个文件::
# echo global > trace_clock
设置一个时钟清除ring buffer和"snapshot"缓冲区内容。
trace_marker
这是一个实用文件,用于将用户空间与内核空间中发生的事件同步。将字符串写入该文件将被写入ftrace缓冲区。
在应用程序中,应用程序开始打开这个文件并引用文件描述符::
void trace_write(const char *fmt, ...)
{
va_list ap;
char buf[256];
int n;
if (trace_fd < 0)
return;
va_start(ap, fmt);
n = vsnprintf(buf, 256, fmt, ap);
va_end(ap);
write(trace_fd, buf, n);
}
开始::
trace_fd = open("trace_marker", WR_ONLY);
注意:写入trace_marker文件也可以触发写入/sys/kernel/tracing/events/ftrace/print/trigger
的触发器。详细看内核文档Documentation/trace/histogram.rst (Section 3.)
。
trace_marker_raw
这类似于上面的trace_marker,但这意味着要向它写入二进制数据,其中可以使用一个工具来解析来自trace_pipe_raw的数据。
uprobe_events
在应用程序中添加dynamic tracepoints。详细看内核文档Documentation/trace/uprobetracer.rst
。
uprobe_profile
uprobe的统计数据。详细看内核文档Documentation/trace/uprobetrace.txt
。
instances/
这是创建多个trace buffer的方法,可以在不同的缓冲区中记录不同的事件。参见下面的"Instances"一节。
events/
这是events trace目录。他保存已编译到内核中的事件跟踪点(也是静态跟踪点)。它显示了存在哪些事件跟踪点,以及如何按子系统分组。不同级别的"enable"文件可以向跟踪点写入"1"时启用跟踪点。详细看Event trace章节。
set_event
可以通过将事件echo到该文件从而启用该事件。详细可看Event trace章节。
available_events
可在tracing中启用的事件列表。详细可看Event trace章节。
timestamp_mode
某些tracer可能会更改在将事件记录到事件缓冲区时所使用的事件戳模式。具有不同模式的事件可以在缓冲区内共存,但记录事件时有效的模式决定了该事件使用哪种模式的时间戳模式。默认的时间戳模式是"delta"。
用于跟踪的常用时间戳模式:
# cat timestamp_mode
[delta] absolute
带方括号的时间戳模式是有效的。
delta
默认的时间戳模式 - timestamp是针对每个缓冲区的时间戳的增量。
absolute
时间戳是一个完整的时间戳,而不是针对其他值的增加量。因此,它占用更多空间和更低的效率。
hwlat_detector/
硬件延迟检测器的目录。请看下面"Hardware Latency Detector"章节。
per_cpu/
这个目录包含了per_cpu的信息。
per_cpu/cpu0/buffer_size_kb
ftrace缓冲区被定义为per_cpu。也就是每个CPU都有一个独立的缓冲区,以允许自动地完成写操作,并且不受缓冲反弹的影响。这些缓冲区可能有不同大小。该文件类似于"buffer_size_kb"文件,但它只显示或设置特定cpu地缓冲区大小(这里是cpu0)。
per_cpu/cpu0/trace
类似于"trace"文件,但它只显示特定于CPU的数据。如果写它,它只清除特定的CPU缓冲区。
per_cpu/cpu0/trace_pipe
这类似于“trace_pipe”文件,并且是一个消耗型的读取,它只显示(并消耗)特定的CPU的数据。
per_cpu/cpu0/trace_pipe_raw
对于能够解析ftrace ring buffer二进制格式的工具,可以使用"trace_pipe_raw"文件直接从ring buffer提取数据。通过使用splice()系统调用,可以将缓冲区数据快速传输到一个文件或一个正在收集数据服务的网络。
与"trace_pipe"一样,这是一个消耗大量数据的读取器,多次读取总是会产生不同的数据。
per_cpu/cpu0/snapshot
这类似于主“快照”文件,但只会快照当前的CPU(如果支持)。它只显示给定CPU的快照内容,如果写入,只清除该CPU缓冲区。
per_cpu/cpu0/snapshot_raw
类似于"trace_pipe_raw",但将从给定CPU的快照缓冲区读取二进制格式。
per_cpu/cpu0/stats
这显示了ring buffer的一些统计信息:
entries
仍在缓冲区中的事件数。
overrun
缓冲区满时由于重写而丢失的事件数。
commit overrun
应该总是0。如果一个嵌套事件中发生了如此多的事件(ring buffer是可重入的),那么它将填充缓冲区并开始删除事件。
bytes
实际读取的字节(没有覆盖)。
oldest event ts
缓冲区中最旧的时间戳。
now ts
当前时间戳。
dropped events
由于overwrite选项关闭而丢失的事件数量。
read events
读取的事件数。
下面是可能配置的当前tracers的列表:
function
函数调用跟踪程序来跟踪所有内核函数。
function_graph
与函数跟踪程序相似,不同之处在于函数跟踪程序在函数的入口探测函数,而函数图跟踪程序在函数的入口和出口都进行跟踪。然后,它提供了绘制函数调用图的能力,类似于C源代码。
blk
the block tracer。blktrace应用程序使用的跟踪程序。
hwlat
硬件延迟跟踪器用于检测硬件是否产生任何延迟。请参阅下面的“Hardware Latency Detector”一节。
irqsoff
跟踪禁用中断的区域,并以最长的最大延迟保存跟踪。
看"tracing_max_latency"文件。当记录新的max时,它将替换旧的跟踪值。最好在启用"latency-format"选项的情况下查看此跟踪,这在选择跟踪程序时自动设置。
preemptoff
类似于irqsoff,但是跟踪和记录抢占被禁用的时间量。
preemptirqsoff
类似于irqsoff和preemptoff,但是跟踪和记录irqs和/或抢占被禁用的最大时间。
wakeup
跟踪和记录在最高优先级任务被唤醒后实际调度它所需要的最大延迟。按照一般开发人员的预期跟踪所有任务。
wakeup_rt
跟踪和记录RT任务所需要的最大延迟。这对于那些对RT任务的唤醒时间感兴趣的人很有用。
wakeup_dl
跟踪和记录唤醒SCHED_DEADLINE任务所需的最大延迟(与“wakeup”和“wakeup_rt”一样)。
mmiotrace
一种用于跟踪二进制模块的特殊跟踪程序。它将跟踪一个模块对硬件的所有调用。它也从I/O中写入和读取所有内容。
branch
这个跟踪程序可以在跟踪内核中可likely/unlikley的调用时配置。它将跟踪何时命中可能和不可能的分支,以及它的预测是否正确。
nop
这是“无跟踪”跟踪器。要从跟踪中删除所有的跟踪程序,只需将“nop” echo到"current_tracer"中。
对于大多数ftrace命令,故障模式是显而易见的,并且使用标准返回码进行通信。
对于其他更复杂的命令,扩展的错误信息可以通过tracing/error_log文件获得。对于支持它的命令,在错误之后读取tracing/error_log文件将显示关于出错的更详细的信息(如果信息可用的话)。trace /error_log文件是一个循环的错误日志,它为最后(8)个失败的命令显示少量的ftrace错误(目前为8)。
扩展的错误信息和用法采用如下示例所示的形式::
# echo xxx > /sys/kernel/debug/tracing/events/sched/sched_wakeup/trigger
echo: write error: Invalid argument
# cat /sys/kernel/debug/tracing/error_log
[ 5348.887237] location: error: Couldn't yyy: zzz
Command: xxx
^
[ 7517.023364] location: error: Bad rrr: sss
Command: ppp qqq
要清除 error log::
# echo > /sys/kernel/debug/tracing/error_log
注意,在新的内核上面大多数错误信息可以直接显示出来,而不需要查看error_log文件。
下面是使用tracer的典型示例,它们只使用"tracers"接口来控制跟踪程序(不使用任何用户使用的实用程序)。
下面是文件“trace”的输出格式示例::
# tracer: function
#
# entries-in-buffer/entries-written: 165223/272378 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
sh-147 [002] d..2 17598.798559: gic_handle_irq <-el1_irq
sh-147 [002] d..2 17598.798883: __handle_domain_irq <-gic_handle_irq
sh-147 [002] d..2 17598.798888: irq_enter <-__handle_domain_irq
sh-147 [002] d..2 17598.798891: irq_enter_rcu <-irq_enter
sh-147 [002] d..2 17598.798892: preempt_count_add <-irq_enter_rcu
sh-147 [002] d.h2 17598.798895: irqtime_account_irq <-irq_enter_rcu
sh-147 [002] d.h2 17598.798898: irq_find_mapping <-__handle_domain_irq
sh-147 [002] d.h2 17598.798900: generic_handle_irq <-__handle_domain_irq
sh-147 [002] d.h2 17598.798904: handle_percpu_devid_irq <-generic_handle_irq
sh-147 [002] d.h2 17598.798906: arch_timer_handler_virt <-handle_percpu_devid_irq
sh-147 [002] d.h2 17598.798911: hrtimer_interrupt <-arch_timer_handler_virt
sh-147 [002] d.h2 17598.798913: _raw_spin_lock_irqsave <-hrtimer_interrupt
sh-147 [002] d.h2 17598.798914: preempt_count_add <-_raw_spin_lock_irqsave
sh-147 [002] d.h3 17598.798917: ktime_get_update_offsets_now <-hrtimer_interrupt
sh-147 [002] d.h3 17598.798920: arch_counter_read <-ktime_get_update_offsets_now
sh-147 [002] d.h3 17598.798923: __hrtimer_run_queues <-hrtimer_interrupt
sh-147 [002] d.h3 17598.798925: __remove_hrtimer <-__hrtimer_run_queues
sh-147 [002] d.h3 17598.798929: _raw_spin_unlock_irqrestore <-__hrtimer_run_queues
....
头部是使用的tracer名字打印。在这种情况下,tracer是"function"。然后显示缓冲区中事件的数量以及写入的条目的总数。差异是由于缓冲区填充而丢失的条目数量(272378 - 165223 = 107155事件丢失)。
头部解释事件内容。任务名"sh",任务pid"147",运行在CPU 002上,延迟格式(latency-format下文解释),时间戳格式为
当我们使用"latency-format"选项时,或者设置了一个延迟tracer,"trace"文件提供了更多信息,以便了解延迟发生的原因。下面是一个典型的trace:
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 5.12.0
# --------------------------------------------------------------------
# latency: 259 us, #4/4, CPU#2 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:8)
# -----------------
# | task: ps-6143 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: __lock_task_sighand
# => ended at: _raw_spin_unlock_irqrestore
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
ps-6143 2d... 0us!: trace_hardirqs_off <-__lock_task_sighand
ps-6143 2d..1 259us+: trace_hardirqs_on <-_raw_spin_unlock_irqrestore
ps-6143 2d..1 263us+: time_hardirqs_on <-_raw_spin_unlock_irqrestore
ps-6143 2d..1 306us :
=> trace_hardirqs_on_caller
=> trace_hardirqs_on
=> _raw_spin_unlock_irqrestore
=> do_task_stat
=> proc_tgid_stat
=> proc_single_show
=> seq_read
=> vfs_read
=> sys_read
=> system_call_fastpath
这个"irqsoff" tracer展示了跟踪中断被禁用的时间。首先给出了trace版本(永不改变)和跟踪的内核版本(5.12)。然后显示最大延迟259us,单位为微秒。显示的跟踪条目的数量和总数(都是4个:#4/4)。P、KP、SP和HP始终为零,保留为以后使用。#P是在线cpu的数量(#P:8)。
该任务是发生延迟时正在运行的进程。(ps pid: 6143)。
导致延迟的启动和停止(分别禁用和启用中断的函数):
– __lock_task_sighand是中断被禁用的地方。
--_raw_spin_unlock_irqrestore是它们再次被启用的位置。
头文件之后的下几行是跟踪本身。标题解释了哪个是哪个。
cmd
跟踪中的进程名。
pid
跟踪中的进程pid。
CPU#
程序运行的CPU。
irqs-off
‘d’表示中断被禁用,相反是’.‘,如果架构不支持则是’X’。
need-resched
- ‘N’ TIF_NEED_RESCHED和PREEMPT_NEED_RESCHED都被设置了,
- ‘n’ 仅设置了TIF_NEED_RESCHED,
- ‘p’ 仅设置了PREEMPT_NEED_RESCHED,
- ‘.’ 都不是。
hardirq/softirq
- ‘Z’ - NMI正在硬件内部发生
- ‘z’ - NMI正在运行
- ‘H’ - 硬中断请求发生在软中断请求内
- ‘h’ - 硬irq正在运行
- ‘s’ - 软irq正在运行
- ‘.’ - 普通上下文
preempt-depth
preempt_disable调用级别。(preempt_disable调用一次加一,preempt_enable调用一次减一)
下面这些信息对内核开发人员有意义:
time
当启用"latency-format"选项时,"trace"文件输出包含一个相对于跟踪开始的时间戳。这与禁用"latency-format"选项输出不同,后者是一个绝对时间戳。
delay
这只是为了更好地吸引你的眼球。并且需要被固定,只相对于相同的CPU。标记由当前跟踪和下一个跟踪之间的差异决定。
- ‘$’ - 大于1秒
- ‘@’ - 大于100毫秒
- ‘*’ - 大于10毫秒
- ‘#’ - 大于1000微秒
- ’!‘ - 超过100微秒
- ‘+’ - 大于10微秒
- ’ ’ - 小于或等于10微秒。
其余部分与“trace”文件相同。
注意,延迟tracer通常会以反向跟踪结束,以便轻松找到延迟发生的位置。
trace_options文件(或options目录)用于控制在跟踪输出中打印的内容,或操作跟踪程序。要查看当前选项是如何配置的,只需cat文件::
# cat trace_options
print-parent
nosym-offset
nosym-addr
noverbose
noraw
nohex
nobin
noblock
trace_printk
annotate
nouserstacktrace
nosym-userobj
noprintk-msg-only
context-info
nolatency-format
record-cmd
norecord-tgid
overwrite
nodisable_on_free
irq-info
markers
noevent-fork
function-trace
nofunction-fork
nodisplay-graph
nostacktrace
nobranch
若要禁用其中一个选项,请回写前面的选项并跟上“no”字符串:
# echo noprint-parent > trace_options
激活选项则不要"no":
echo sym-offset > trace_options
这是一些可用的options:
print-parent
在function tracing中,显示调用(父)函数以及正在跟踪的函数::
print-parent:
bash-4000 [01] 1477.606694: simple_strtoul <-kstrtoul
noprint-parent:
bash-4000 [01] 1477.606694: simple_strtoul
sym-offset
不仅显示函数名,还显示函数中的偏移量。例如,将看到“ktime_get+0xb/0x20”,而不是仅仅看到“ktime_get”::
sym-offset:
bash-4000 [01] 1477.606694: simple_strtoul+0x6/0xa0
sym-addr
这将显示函数地址和函数名::
sym-addr:
bash-4000 [01] 1477.606694: simple_strtoul
verbose
当"latency-format"选项也激活时,它按照下面原始信息显示"trace"文件::
bash 4000 1 0 00000000 00010a95 [58127d26] 1720.415ms \
(+0.000ms): simple_strtoul (kstrtoul)
raw
这将显示原始数字。这个选项最适合于那些比在内核中更好地转换原始数字的用户应用程序::
0 4 87666084422608 ffff80001014b5bc ffff80001014ba44
59 5 87666084424800 type: 254
0 4 87666084425504 ffff80001014b6c4 ffff80001014ba58
hex
类似于raw选项,但数字将以十六进制格式输出。
bin
这将以原始二进制格式输出。
block
当设置时,在轮询时读取trace_pipe不会被阻塞。
trace_printk
可以禁用trace_printk()写入缓冲区。(内核中使用trace_printk()向function_graph tracer的"trace"文件函数内部添加注释信息,后面介绍)
annotate
当CPU缓冲区满了,并且一个CPU缓冲区最近又有很多事件产生,因此在一个较短的时间帧里面,另一个CPU可能只有几个很旧的事件,有时会令人混淆的。当报告跟踪时,它首先显示最旧的事件,看起来可能只有一个CPU在运行(具有最旧事件的那个)。设置了annotate选项后,当一个新的CPU缓冲区启动时,它将显示一个下面这样的注释::
-0 [001] dNs4 21169.031481: wake_up_idle_cpu <-add_timer_on
-0 [001] dNs4 21169.031482: _raw_spin_unlock_irqrestore <-add_timer_on
-0 [001] .Ns4 21169.031484: sub_preempt_count <-_raw_spin_unlock_irqrestore
##### CPU 2 buffer started ####
-0 [002] .N.1 21169.031484: rcu_idle_exit <-cpu_idle
-0 [001] .Ns3 21169.031484: _raw_spin_unlock <-clocksource_watchdog
-0 [001] .Ns3 21169.031485: sub_preempt_count <-_raw_spin_unlock
userstacktrace
此选项将更改跟踪。它在每个跟踪事件之后记录当前用户空间线程的堆栈跟踪。
sym-userobj
当启用user stacktrace事件时,查找地址属于哪个对象,并打印一个相对地址。这在ASLR开启时特别有用,否则在应用程序不再运行后,你没有机会解析到对象/文件/行的地址。
在读取"trace","trace_pipe"时执行查找。例如::
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]
printk-msg-only
设置后,trace_printk()将只显示格式,而不显示它们的参数(如果trace_bprintk()或trace_bputs()用于保存trace_printk())。
context-info
只显示事件数据。隐藏comm、PID、时间戳、CPU和其他有用的数据。
latency-format
此选项将更改跟踪输出。当它被启用时,跟踪将显示关于延迟的附加信息,如“Latency trace format”中所述。
pause-on-trace
设置后,打开"trace"文件进行读操作,将暂停对循环缓冲区的写操作(就像tracing_on设置为0一样)。这模拟了跟踪文件的原始行为。当文件关闭时,跟踪将再次启用。
hash-ptr
设置后,事件printk格式中的"%p"将显示散列后的指针值,而不是实际地址。如果您想要找出哪个散列值对应于跟踪日志中的实际值,这将非常有用。
record-cmd
当启用任何事件或跟踪程序时,在sched_switch跟踪点中启用一个钩子,以使用映射的pid和comm填充comm缓存。但是这可能会造成一些开销,而且如果您只关心pid而不关心任务的名称,禁用此选项可以降低跟踪的影响。看到“saved_cmdlines”。
record-tgid
当启用任何事件或跟踪程序时,在sched_switch跟踪点中启用一个钩子,以填充映射到pid的线程组id (TGID)映射的缓存。看到“saved_tgids”。
overwrite
这将控制跟踪缓冲区满时发生的情况。如果“1”(默认值),则丢弃和覆盖最老的事件。如果“0”,则丢弃最新的事件。(请参阅per_cpu/cpu0/stats了解溢出和丢弃)
disable_on_free
当free_buffer关闭时,跟踪将停止(tracing_on设置为0)。
irq-info
显示中断,抢占计数,需要重新排序的数据。禁用时,跟踪看起来像::
# tracer: function
#
# entries-in-buffer/entries-written: 144405/9452052 #P:4
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
-0 [002] 23636.756054: ttwu_do_activate.constprop.89 <-try_to_wake_up
-0 [002] 23636.756054: activate_task <-ttwu_do_activate.constprop.89
-0 [002] 23636.756055: enqueue_task <-activate_task
markers
当设置时,trace_marker是可写的(仅由根用户)。
当禁用时,trace_marker将在写入时出错,并带有EINVAL。
event-fork
当设置时,在"set_event_pid"中列出的任务将在这些任务fork时将其子任务的pid添加到"set_event_pid"中。此外,当具有"set_event_pid"中的pid的任务退出时,它们的pid将从文件中删除。
这也会影响"set_event_notrace_pid"中列出的pid。
function-trace
如果启用此选项(默认是),延迟类tracer将启用函数跟踪。当它被禁用时,延迟跟踪器不会跟踪函数。这将降低执行延迟测试时tracer的开销。
function-fork
当设置时,在"set_ftrace_pid"中列出的pid的任务将在这些任务分叉时将其子任务的pid添加到"set_ftrace_pid"中。同样,当具有"set_ftrace_pid"中的pid的任务退出时,它们的pid将从文件中删除。
这也会影响"set_ftrace_notrace_pid"中的pid。
display-graph
当设置后,延迟类tracer(irqsoff、wakeup等)将使用函数图跟踪而不是函数跟踪。
stacktrace
当设置时,堆栈跟踪将在记录任何跟踪事件之后记录。
branch
tracer启用分支tracing。这将支持branch tracer以及当前设置的tracer。使用“nop”tracer启用此功能与启用branch tracer相同。
. .提示:有些tracer有自己的选择。它们只在tracer激活时自动地在"trace_options"文件中启用这个选项。
下面是一些per tracer选项:
function tracer 选项
func_stack_trace
当设置时,堆栈跟踪将在记录的每个函数之后记录。注意,使用“set_ftrace_filter”限制在启用前记录的函数,否则系统性能将严重降低。记得在清除函数过滤器之前禁用此选项。
function_graph tracer 选项
由于function_graph tracer的输出略有不同,所以它有自己的选项来控制显示的内容。
funcgraph-overrun
当设置时,在tracing每个函数之后,图形堆栈的“overrun”将显示出来。溢出是指调用的堆栈深度大于为每个任务保留的堆栈深度。每个任务在调用图中都有一个固定的函数数组来跟踪。如果调用的深度超过该值,则不会跟踪函数。溢出是由于超过这个数组而错过的函数数。
funcgraph-cpu
设置后,会显示跟踪发生的CPU编号。
funcgraph-overhead
当设置时,如果函数花费的时间超过一定的数量,则显示一个延迟标记。参见上面标题描述下的“delay”。
funcgraph-proc
与其他tracer不同,进程的命令行在默认情况下不显示,而是只在上下文切换期间跟踪任务时显示。启用此选项将在每行显示每个进程的命令。
funcgraph-duration
在每个函数(返回)结束时,函数中时间量的持续时间以微秒为单位显示。
funcgraph-abstime
设置后,时间戳将显示在每一行。
funcgraph-irqs
当禁用时,在中断中发生的函数将不会被跟踪。
funcgraph-tail
设置后,返回事件将包含它所代表的函数。默认情况下这是关闭的,并且在返回函数时只显示一个右花括号"}"。
sleep-time
在运行函数图跟踪程序时,将任务计划的时间包含在其函数中。当启用时,它将计算任务作为函数调用的一部分被调度出去的时间。
graph-time
当用函数图跟踪器运行函数分析器时,要包括调用嵌套函数的时间。如果没有设置此参数,则报告的函数时间将只包括函数本身执行的时间,而不包括调用函数的时间。
blk tracer 选项
blk_classic
显示出更简约的输出。
当中断被禁用时,CPU不能响应任何其他外部事件(除了NMIs和SMIs)。这可以防止计时器中断触发或鼠标中断让内核知道新的鼠标事件。结果是响应时间的延迟。
irqsoff tracer跟踪中断被禁用的时间。当遇到新的最大延迟时,tracer将保存指向该延迟点的跟踪,以便每次达到新的最大延迟时,将丢弃旧的保存的跟踪,并保存新的跟踪。
要重置最大值,echo 0到tracing_max_latency。下面是一个例子:
# echo 0 > options/function-trace
# echo irqsoff > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# ls -ltr
[...]
# echo 0 > tracing_on
# cat trace
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 5.12.0
# --------------------------------------------------------------------
# latency: 16 us, #4/4, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: swapper/0-0 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: run_timer_softirq
# => ended at: run_timer_softirq
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
-0 0d.s2 0us+: _raw_spin_lock_irq <-run_timer_softirq
-0 0dNs3 17us : _raw_spin_unlock_irq <-run_timer_softirq
-0 0dNs3 17us+: trace_hardirqs_on <-run_timer_softirq
-0 0dNs3 25us :
=> _raw_spin_unlock_irq
=> run_timer_softirq
=> __do_softirq
=> call_softirq
=> do_softirq
=> irq_exit
=> smp_apic_timer_interrupt
=> apic_timer_interrupt
=> rcu_idle_exit
=> cpu_idle
=> rest_init
=> start_kernel
=> x86_64_start_reservations
=> x86_64_start_kernel
这里我们看到我们有16微秒的延迟(这是非常好的)。run_timer_softirq中的_raw_spin_lock_irq禁用中断。16和显示的时间戳25us之间的差异是因为时钟在记录最大延迟和记录有延迟的函数之间的时间增加了。
注意,上面的示例没有设置函数跟踪。如果我们设置function-trace,我们会得到一个更大的输出::
with echo 1 > options/function-trace
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 71 us, #168/168, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: bash-2042 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: ata_scsi_queuecmd
# => ended at: ata_scsi_queuecmd
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
bash-2042 3d... 0us : _raw_spin_lock_irqsave <-ata_scsi_queuecmd
bash-2042 3d... 0us : add_preempt_count <-_raw_spin_lock_irqsave
bash-2042 3d..1 1us : ata_scsi_find_dev <-ata_scsi_queuecmd
bash-2042 3d..1 1us : __ata_scsi_find_dev <-ata_scsi_find_dev
bash-2042 3d..1 2us : ata_find_dev.part.14 <-__ata_scsi_find_dev
bash-2042 3d..1 2us : ata_qc_new_init <-__ata_scsi_queuecmd
bash-2042 3d..1 3us : ata_sg_init <-__ata_scsi_queuecmd
bash-2042 3d..1 4us : ata_scsi_rw_xlat <-__ata_scsi_queuecmd
bash-2042 3d..1 4us : ata_build_rw_tf <-ata_scsi_rw_xlat
[...]
bash-2042 3d..1 67us : delay_tsc <-__delay
bash-2042 3d..1 67us : add_preempt_count <-delay_tsc
bash-2042 3d..2 67us : sub_preempt_count <-delay_tsc
bash-2042 3d..1 67us : add_preempt_count <-delay_tsc
bash-2042 3d..2 68us : sub_preempt_count <-delay_tsc
bash-2042 3d..1 68us+: ata_bmdma_start <-ata_bmdma_qc_issue
bash-2042 3d..1 71us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd
bash-2042 3d..1 71us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd
bash-2042 3d..1 72us+: trace_hardirqs_on <-ata_scsi_queuecmd
bash-2042 3d..1 120us :
=> _raw_spin_unlock_irqrestore
=> ata_scsi_queuecmd
=> scsi_dispatch_cmd
=> scsi_request_fn
=> __blk_run_queue_uncond
=> __blk_run_queue
=> blk_queue_bio
=> submit_bio_noacct
=> submit_bio
=> submit_bh
=> __ext3_get_inode_loc
=> ext3_iget
=> ext3_lookup
=> lookup_real
=> __lookup_hash
=> walk_component
=> lookup_last
=> path_lookupat
=> filename_lookup
=> user_path_at_empty
=> user_path_at
=> vfs_fstatat
=> vfs_stat
=> sys_newstat
=> system_call_fastpath
这里我们追踪了71微秒的延迟。但我们也看到了那段时间调用的所有函数。注意,通过启用函数跟踪,我们引入了额外的开销。这种开销可能会延长延迟时间。但是,这个跟踪提供了一些非常有用的调试信息。
如果我们更喜欢函数图形输出而不是函数,我们可以设置显示图形选项::
with echo 1 > options/display-graph
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 4.20.0-rc6+
# --------------------------------------------------------------------
# latency: 3751 us, #274/274, CPU#0 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: bash-1507 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: free_debug_processing
# => ended at: return_to_handler
#
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| /
# REL TIME CPU TASK/PID |||| DURATION FUNCTION CALLS
# | | | | |||| | | | | | |
0 us | 0) bash-1507 | d... | 0.000 us | _raw_spin_lock_irqsave();
0 us | 0) bash-1507 | d..1 | 0.378 us | do_raw_spin_trylock();
1 us | 0) bash-1507 | d..2 | | set_track() {
2 us | 0) bash-1507 | d..2 | | save_stack_trace() {
2 us | 0) bash-1507 | d..2 | | __save_stack_trace() {
3 us | 0) bash-1507 | d..2 | | __unwind_start() {
3 us | 0) bash-1507 | d..2 | | get_stack_info() {
3 us | 0) bash-1507 | d..2 | 0.351 us | in_task_stack();
4 us | 0) bash-1507 | d..2 | 1.107 us | }
[...]
3750 us | 0) bash-1507 | d..1 | 0.516 us | do_raw_spin_unlock();
3750 us | 0) bash-1507 | d..1 | 0.000 us | _raw_spin_unlock_irqrestore();
3764 us | 0) bash-1507 | d..1 | 0.000 us | tracer_hardirqs_on();
bash-1507 0d..1 3792us :
=> free_debug_processing
=> __slab_free
=> kmem_cache_free
=> vm_area_free
=> remove_vma
=> exit_mmap
=> mmput
=> begin_new_exec
=> load_elf_binary
=> search_binary_handler
=> __do_execve_file.isra.32
=> __x64_sys_execve
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
当抢占被禁用时,我们可以接收到中断,但是任务不能被抢占,一个高优先级的任务必须等待抢占再次被启用,然后它才能抢占一个低优先级的任务。
preemptoff tracer跟踪禁用抢占的地方。与irqsoff tracer一样,它记录禁用抢占时的最大延迟::
# echo 0 > options/function-trace
# echo preemptoff > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# ls -ltr
[...]
# echo 0 > tracing_on
# cat trace
# tracer: preemptoff
#
# preemptoff latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 46 us, #4/4, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: sshd-1991 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: do_IRQ
# => ended at: do_IRQ
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
sshd-1991 1d.h. 0us+: irq_enter <-do_IRQ
sshd-1991 1d..1 46us : irq_exit <-do_IRQ
sshd-1991 1d..1 47us+: trace_preempt_on <-do_IRQ
sshd-1991 1d..1 52us :
=> sub_preempt_count
=> irq_exit
=> do_IRQ
=> ret_from_intr
这里有更多的变化。抢占在中断出现时被禁用(注意’h’),在退出时被启用。但我们也看到,当进入抢占off部分并离开它(‘d’)时,中断已经被禁用。我们不知道中断是在此期间启用的,还是在此结束后不久启用的::
# tracer: preemptoff
#
# preemptoff latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 83 us, #241/241, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: bash-1994 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: wake_up_new_task
# => ended at: task_rq_unlock
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
bash-1994 1d..1 0us : _raw_spin_lock_irqsave <-wake_up_new_task
bash-1994 1d..1 0us : select_task_rq_fair <-select_task_rq
bash-1994 1d..1 1us : __rcu_read_lock <-select_task_rq_fair
bash-1994 1d..1 1us : source_load <-select_task_rq_fair
bash-1994 1d..1 1us : source_load <-select_task_rq_fair
[...]
bash-1994 1d..1 12us : irq_enter <-smp_apic_timer_interrupt
bash-1994 1d..1 12us : rcu_irq_enter <-irq_enter
bash-1994 1d..1 13us : add_preempt_count <-irq_enter
bash-1994 1d.h1 13us : exit_idle <-smp_apic_timer_interrupt
bash-1994 1d.h1 13us : hrtimer_interrupt <-smp_apic_timer_interrupt
bash-1994 1d.h1 13us : _raw_spin_lock <-hrtimer_interrupt
bash-1994 1d.h1 14us : add_preempt_count <-_raw_spin_lock
bash-1994 1d.h2 14us : ktime_get_update_offsets <-hrtimer_interrupt
[...]
bash-1994 1d.h1 35us : lapic_next_event <-clockevents_program_event
bash-1994 1d.h1 35us : irq_exit <-smp_apic_timer_interrupt
bash-1994 1d.h1 36us : sub_preempt_count <-irq_exit
bash-1994 1d..2 36us : do_softirq <-irq_exit
bash-1994 1d..2 36us : __do_softirq <-call_softirq
bash-1994 1d..2 36us : __local_bh_disable <-__do_softirq
bash-1994 1d.s2 37us : add_preempt_count <-_raw_spin_lock_irq
bash-1994 1d.s3 38us : _raw_spin_unlock <-run_timer_softirq
bash-1994 1d.s3 39us : sub_preempt_count <-_raw_spin_unlock
bash-1994 1d.s2 39us : call_timer_fn <-run_timer_softirq
[...]
bash-1994 1dNs2 81us : cpu_needs_another_gp <-rcu_process_callbacks
bash-1994 1dNs2 82us : __local_bh_enable <-__do_softirq
bash-1994 1dNs2 82us : sub_preempt_count <-__local_bh_enable
bash-1994 1dN.2 82us : idle_cpu <-irq_exit
bash-1994 1dN.2 83us : rcu_irq_exit <-irq_exit
bash-1994 1dN.2 83us : sub_preempt_count <-irq_exit
bash-1994 1.N.1 84us : _raw_spin_unlock_irqrestore <-task_rq_unlock
bash-1994 1.N.1 84us+: trace_preempt_on <-task_rq_unlock
bash-1994 1.N.1 104us :
=> sub_preempt_count
=> _raw_spin_unlock_irqrestore
=> task_rq_unlock
=> wake_up_new_task
=> do_fork
=> sys_clone
=> stub_clone
上面是一个设置了函数跟踪的抢占跟踪示例。这里我们看到中断在整个时间内都没有被禁用。irq_enter代码让我们知道我们进入了一个中断’h’。在此之前,被跟踪的函数仍然显示它不是在中断中,但我们可以从函数本身看到情况并非如此。
了解禁用中断或禁用抢占时间最长的位置是有必要的。但有时我们想知道抢占和/或中断在什么时候被禁用。
思考以下代码::
local_irq_disable();
call_function_with_irqs_off();
preempt_disable();
call_function_with_irqs_and_preemption_off();
local_irq_enable();
call_function_with_preemption_off();
preempt_enable();
irqsoff tracer将记录call_function_with_irqs_off()和call_function_with_irqs_and_preemption_off()的总长度。
preemptoff tracer将记录call_function_with_irqs_and_preemption_off()和call_function_with_preemption_off()的总长度。
但两者都无法跟踪中断和/或禁用抢占的时间。这总时间是我们无法安排的时间。要记录这一次,使用preemptirqsoff tracer。
同样,使用这个跟踪很像irqsoff tracer和preemptoff tracer::
# echo 0 > options/function-trace
# echo preemptirqsoff > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# ls -ltr
[...]
# echo 0 > tracing_on
# cat trace
# tracer: preemptirqsoff
#
# preemptirqsoff latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 100 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: ls-2230 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: ata_scsi_queuecmd
# => ended at: ata_scsi_queuecmd
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
ls-2230 3d... 0us+: _raw_spin_lock_irqsave <-ata_scsi_queuecmd
ls-2230 3...1 100us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd
ls-2230 3...1 101us+: trace_preempt_on <-ata_scsi_queuecmd
ls-2230 3...1 111us :
=> sub_preempt_count
=> _raw_spin_unlock_irqrestore
=> ata_scsi_queuecmd
=> scsi_dispatch_cmd
=> scsi_request_fn
=> __blk_run_queue_uncond
=> __blk_run_queue
=> blk_queue_bio
=> submit_bio_noacct
=> submit_bio
=> submit_bh
=> ext3_bread
=> ext3_dir_bread
=> htree_dirblock_to_tree
=> ext3_htree_fill_tree
=> ext3_readdir
=> vfs_readdir
=> sys_getdents
=> system_call_fastpath
当在汇编代码中禁用中断时,在x86上的汇编中调用trace_hardirqs_off_thunk。如果没有函数跟踪,我们就不知道在抢占点中是否启用了中断。我们确实看到它开始时启用了抢占。
下面是函数跟踪集的跟踪::
# tracer: preemptirqsoff
#
# preemptirqsoff latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 161 us, #339/339, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: ls-2269 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: schedule
# => ended at: mutex_unlock
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
kworker/-59 3...1 0us : __schedule <-schedule
kworker/-59 3d..1 0us : rcu_preempt_qs <-rcu_note_context_switch
kworker/-59 3d..1 1us : add_preempt_count <-_raw_spin_lock_irq
kworker/-59 3d..2 1us : deactivate_task <-__schedule
kworker/-59 3d..2 1us : dequeue_task <-deactivate_task
kworker/-59 3d..2 2us : update_rq_clock <-dequeue_task
kworker/-59 3d..2 2us : dequeue_task_fair <-dequeue_task
kworker/-59 3d..2 2us : update_curr <-dequeue_task_fair
kworker/-59 3d..2 2us : update_min_vruntime <-update_curr
kworker/-59 3d..2 3us : cpuacct_charge <-update_curr
kworker/-59 3d..2 3us : __rcu_read_lock <-cpuacct_charge
kworker/-59 3d..2 3us : __rcu_read_unlock <-cpuacct_charge
kworker/-59 3d..2 3us : update_cfs_rq_blocked_load <-dequeue_task_fair
kworker/-59 3d..2 4us : clear_buddies <-dequeue_task_fair
kworker/-59 3d..2 4us : account_entity_dequeue <-dequeue_task_fair
kworker/-59 3d..2 4us : update_min_vruntime <-dequeue_task_fair
kworker/-59 3d..2 4us : update_cfs_shares <-dequeue_task_fair
kworker/-59 3d..2 5us : hrtick_update <-dequeue_task_fair
kworker/-59 3d..2 5us : wq_worker_sleeping <-__schedule
kworker/-59 3d..2 5us : kthread_data <-wq_worker_sleeping
kworker/-59 3d..2 5us : put_prev_task_fair <-__schedule
kworker/-59 3d..2 6us : pick_next_task_fair <-pick_next_task
kworker/-59 3d..2 6us : clear_buddies <-pick_next_task_fair
kworker/-59 3d..2 6us : set_next_entity <-pick_next_task_fair
kworker/-59 3d..2 6us : update_stats_wait_end <-set_next_entity
ls-2269 3d..2 7us : finish_task_switch <-__schedule
ls-2269 3d..2 7us : _raw_spin_unlock_irq <-finish_task_switch
ls-2269 3d..2 8us : do_IRQ <-ret_from_intr
ls-2269 3d..2 8us : irq_enter <-do_IRQ
ls-2269 3d..2 8us : rcu_irq_enter <-irq_enter
ls-2269 3d..2 9us : add_preempt_count <-irq_enter
ls-2269 3d.h2 9us : exit_idle <-do_IRQ
[...]
ls-2269 3d.h3 20us : sub_preempt_count <-_raw_spin_unlock
ls-2269 3d.h2 20us : irq_exit <-do_IRQ
ls-2269 3d.h2 21us : sub_preempt_count <-irq_exit
ls-2269 3d..3 21us : do_softirq <-irq_exit
ls-2269 3d..3 21us : __do_softirq <-call_softirq
ls-2269 3d..3 21us+: __local_bh_disable <-__do_softirq
ls-2269 3d.s4 29us : sub_preempt_count <-_local_bh_enable_ip
ls-2269 3d.s5 29us : sub_preempt_count <-_local_bh_enable_ip
ls-2269 3d.s5 31us : do_IRQ <-ret_from_intr
ls-2269 3d.s5 31us : irq_enter <-do_IRQ
ls-2269 3d.s5 31us : rcu_irq_enter <-irq_enter
[...]
ls-2269 3d.s5 31us : rcu_irq_enter <-irq_enter
ls-2269 3d.s5 32us : add_preempt_count <-irq_enter
ls-2269 3d.H5 32us : exit_idle <-do_IRQ
ls-2269 3d.H5 32us : handle_irq <-do_IRQ
ls-2269 3d.H5 32us : irq_to_desc <-handle_irq
ls-2269 3d.H5 33us : handle_fasteoi_irq <-handle_irq
[...]
ls-2269 3d.s5 158us : _raw_spin_unlock_irqrestore <-rtl8139_poll
ls-2269 3d.s3 158us : net_rps_action_and_irq_enable.isra.65 <-net_rx_action
ls-2269 3d.s3 159us : __local_bh_enable <-__do_softirq
ls-2269 3d.s3 159us : sub_preempt_count <-__local_bh_enable
ls-2269 3d..3 159us : idle_cpu <-irq_exit
ls-2269 3d..3 159us : rcu_irq_exit <-irq_exit
ls-2269 3d..3 160us : sub_preempt_count <-irq_exit
ls-2269 3d... 161us : __mutex_unlock_slowpath <-mutex_unlock
ls-2269 3d... 162us+: trace_hardirqs_on <-mutex_unlock
ls-2269 3d... 186us :
=> __mutex_unlock_slowpath
=> mutex_unlock
=> process_output
=> n_tty_write
=> tty_write
=> vfs_write
=> sys_write
=> system_call_fastpath
这是一个有趣的轨迹。它开始于kworker运行和调度,然后ls接管。但是一旦ls释放了rq锁并启用了中断(但不是抢占),就会触发中断。当中断结束时,它开始运行软中断。但是当软中断运行时,另一个中断被触发。当一个中断在软中断中运行时,注释是’H’。
人们感兴趣的一个常见情况是跟踪一个被唤醒的任务实际醒来所花费的时间。对于非Real-Time任务,这可以是任意的。但追踪它仍然是有趣的。
没有function跟踪::
# echo 0 > options/function-trace
# echo wakeup > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# chrt -f 5 sleep 1
# echo 0 > tracing_on
# cat trace
# tracer: wakeup
#
# wakeup latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 15 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: kworker/3:1H-312 (uid:0 nice:-20 policy:0 rt_prio:0)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
-0 3dNs7 0us : 0:120:R + [003] 312:100:R kworker/3:1H
-0 3dNs7 1us+: ttwu_do_activate.constprop.87 <-try_to_wake_up
-0 3d..3 15us : __schedule <-schedule
-0 3d..3 15us : 0:120:R ==> [003] 312:100:R kworker/3:1H
tracer只跟踪系统中优先级最高的任务,避免跟踪正常情况。这里我们看到kworker的优先级是-20(不是很好),从它醒来到它运行的时间只花了15微秒。
非实时任务不是那么有趣。更有趣的跟踪是只关注实时任务。
在实时环境中,了解被唤醒的最高优先级任务到它执行的时间所花费的唤醒时间非常重要。这也被称为“调度延迟”。我强调一点,这是关于RT任务的。了解非rt任务的调度延迟也很重要,但是平均调度延迟对于非rt任务更好。像LatencyTop这样的工具更适合于这种测量。
实时环境对最坏情况下的延迟感兴趣。这是某事发生所需要的最长延迟,而不是平均值。我们可以有一个非常快的调度程序,它可能只在一段时间内有一个很大的延迟,但这并不适合实时任务。wakeup_rt跟踪程序被设计用来记录RT任务的最坏情况唤醒。不记录非RT任务,因为跟踪程序只记录一个最坏的情况,而跟踪不可预测的非RT任务将覆盖RT任务的最坏情况延迟(只需运行一段时间的正常唤醒跟踪程序,看看效果)。
由于此跟踪程序只处理RT任务,因此我们将以与之前的跟踪程序略有不同的方式运行此跟踪程序。我们不再执行’ls’,而是在’chrt’下运行’sleep 1’,这会改变任务的优先级::
# echo 0 > options/function-trace
# echo wakeup_rt > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# chrt -f 5 sleep 1
# echo 0 > tracing_on
# cat trace
# tracer: wakeup
#
# tracer: wakeup_rt
#
# wakeup_rt latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 5 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: sleep-2389 (uid:0 nice:0 policy:1 rt_prio:5)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
-0 3d.h4 0us : 0:120:R + [003] 2389: 94:R sleep
-0 3d.h4 1us+: ttwu_do_activate.constprop.87 <-try_to_wake_up
-0 3d..3 5us : __schedule <-schedule
-0 3d..3 5us : 0:120:R ==> [003] 2389: 94:R sleep
在空闲系统上运行它,我们看到执行任务切换只花了5微秒。注意,由于调度中的跟踪点在实际的“切换”之前,所以当记录的任务即将调度进来时,我们停止跟踪。如果我们在调度程序的末尾添加一个新的标记,这可能会改变。
注意,记录的任务是“sleep”,PID为2389,rt_prio为5。这个优先级是用户空间优先级,而不是内部内核优先级。SCHED_FIFO策略为1,SCHED_RR策略为2。
注意,跟踪数据显示了内部优先级(99 - rtprio)::
-0 3d..3 5us : 0:120:R ==> [003] 2389: 94:R sleep
0:120:R表示空闲正在以良好的优先级0(120 - 120)运行,并处于运行状态’R’。睡眠任务被安排在2389:94:R。优先级是内核rtprio(99 - 5 = 94),它也处于运行状态。
对chrt -r 5和函数跟踪集执行相同的操作::
echo 1 > options/function-trace
# tracer: wakeup_rt
#
# wakeup_rt latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 29 us, #85/85, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: sleep-2448 (uid:0 nice:0 policy:1 rt_prio:5)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
-0 3d.h4 1us+: 0:120:R + [003] 2448: 94:R sleep
-0 3d.h4 2us : ttwu_do_activate.constprop.87 <-try_to_wake_up
-0 3d.h3 3us : check_preempt_curr <-ttwu_do_wakeup
-0 3d.h3 3us : resched_curr <-check_preempt_curr
-0 3dNh3 4us : task_woken_rt <-ttwu_do_wakeup
-0 3dNh3 4us : _raw_spin_unlock <-try_to_wake_up
-0 3dNh3 4us : sub_preempt_count <-_raw_spin_unlock
-0 3dNh2 5us : ttwu_stat <-try_to_wake_up
-0 3dNh2 5us : _raw_spin_unlock_irqrestore <-try_to_wake_up
-0 3dNh2 6us : sub_preempt_count <-_raw_spin_unlock_irqrestore
-0 3dNh1 6us : _raw_spin_lock <-__run_hrtimer
-0 3dNh1 6us : add_preempt_count <-_raw_spin_lock
-0 3dNh2 7us : _raw_spin_unlock <-hrtimer_interrupt
-0 3dNh2 7us : sub_preempt_count <-_raw_spin_unlock
-0 3dNh1 7us : tick_program_event <-hrtimer_interrupt
-0 3dNh1 7us : clockevents_program_event <-tick_program_event
-0 3dNh1 8us : ktime_get <-clockevents_program_event
-0 3dNh1 8us : lapic_next_event <-clockevents_program_event
-0 3dNh1 8us : irq_exit <-smp_apic_timer_interrupt
-0 3dNh1 9us : sub_preempt_count <-irq_exit
-0 3dN.2 9us : idle_cpu <-irq_exit
-0 3dN.2 9us : rcu_irq_exit <-irq_exit
-0 3dN.2 10us : rcu_eqs_enter_common.isra.45 <-rcu_irq_exit
-0 3dN.2 10us : sub_preempt_count <-irq_exit
-0 3.N.1 11us : rcu_idle_exit <-cpu_idle
-0 3dN.1 11us : rcu_eqs_exit_common.isra.43 <-rcu_idle_exit
-0 3.N.1 11us : tick_nohz_idle_exit <-cpu_idle
-0 3dN.1 12us : menu_hrtimer_cancel <-tick_nohz_idle_exit
-0 3dN.1 12us : ktime_get <-tick_nohz_idle_exit
-0 3dN.1 12us : tick_do_update_jiffies64 <-tick_nohz_idle_exit
-0 3dN.1 13us : cpu_load_update_nohz <-tick_nohz_idle_exit
-0 3dN.1 13us : _raw_spin_lock <-cpu_load_update_nohz
-0 3dN.1 13us : add_preempt_count <-_raw_spin_lock
-0 3dN.2 13us : __cpu_load_update <-cpu_load_update_nohz
-0 3dN.2 14us : sched_avg_update <-__cpu_load_update
-0 3dN.2 14us : _raw_spin_unlock <-cpu_load_update_nohz
-0 3dN.2 14us : sub_preempt_count <-_raw_spin_unlock
-0 3dN.1 15us : calc_load_nohz_stop <-tick_nohz_idle_exit
-0 3dN.1 15us : touch_softlockup_watchdog <-tick_nohz_idle_exit
-0 3dN.1 15us : hrtimer_cancel <-tick_nohz_idle_exit
-0 3dN.1 15us : hrtimer_try_to_cancel <-hrtimer_cancel
-0 3dN.1 16us : lock_hrtimer_base.isra.18 <-hrtimer_try_to_cancel
-0 3dN.1 16us : _raw_spin_lock_irqsave <-lock_hrtimer_base.isra.18
-0 3dN.1 16us : add_preempt_count <-_raw_spin_lock_irqsave
-0 3dN.2 17us : __remove_hrtimer <-remove_hrtimer.part.16
-0 3dN.2 17us : hrtimer_force_reprogram <-__remove_hrtimer
-0 3dN.2 17us : tick_program_event <-hrtimer_force_reprogram
-0 3dN.2 18us : clockevents_program_event <-tick_program_event
-0 3dN.2 18us : ktime_get <-clockevents_program_event
-0 3dN.2 18us : lapic_next_event <-clockevents_program_event
-0 3dN.2 19us : _raw_spin_unlock_irqrestore <-hrtimer_try_to_cancel
-0 3dN.2 19us : sub_preempt_count <-_raw_spin_unlock_irqrestore
-0 3dN.1 19us : hrtimer_forward <-tick_nohz_idle_exit
-0 3dN.1 20us : ktime_add_safe <-hrtimer_forward
-0 3dN.1 20us : ktime_add_safe <-hrtimer_forward
-0 3dN.1 20us : hrtimer_start_range_ns <-hrtimer_start_expires.constprop.11
-0 3dN.1 20us : __hrtimer_start_range_ns <-hrtimer_start_range_ns
-0 3dN.1 21us : lock_hrtimer_base.isra.18 <-__hrtimer_start_range_ns
-0 3dN.1 21us : _raw_spin_lock_irqsave <-lock_hrtimer_base.isra.18
-0 3dN.1 21us : add_preempt_count <-_raw_spin_lock_irqsave
-0 3dN.2 22us : ktime_add_safe <-__hrtimer_start_range_ns
-0 3dN.2 22us : enqueue_hrtimer <-__hrtimer_start_range_ns
-0 3dN.2 22us : tick_program_event <-__hrtimer_start_range_ns
-0 3dN.2 23us : clockevents_program_event <-tick_program_event
-0 3dN.2 23us : ktime_get <-clockevents_program_event
-0 3dN.2 23us : lapic_next_event <-clockevents_program_event
-0 3dN.2 24us : _raw_spin_unlock_irqrestore <-__hrtimer_start_range_ns
-0 3dN.2 24us : sub_preempt_count <-_raw_spin_unlock_irqrestore
-0 3dN.1 24us : account_idle_ticks <-tick_nohz_idle_exit
-0 3dN.1 24us : account_idle_time <-account_idle_ticks
-0 3.N.1 25us : sub_preempt_count <-cpu_idle
-0 3.N.. 25us : schedule <-cpu_idle
-0 3.N.. 25us : __schedule <-preempt_schedule
-0 3.N.. 26us : add_preempt_count <-__schedule
-0 3.N.1 26us : rcu_note_context_switch <-__schedule
-0 3.N.1 26us : rcu_sched_qs <-rcu_note_context_switch
-0 3dN.1 27us : rcu_preempt_qs <-rcu_note_context_switch
-0 3.N.1 27us : _raw_spin_lock_irq <-__schedule
-0 3dN.1 27us : add_preempt_count <-_raw_spin_lock_irq
-0 3dN.2 28us : put_prev_task_idle <-__schedule
-0 3dN.2 28us : pick_next_task_stop <-pick_next_task
-0 3dN.2 28us : pick_next_task_rt <-pick_next_task
-0 3dN.2 29us : dequeue_pushable_task <-pick_next_task_rt
-0 3d..3 29us : __schedule <-preempt_schedule
-0 3d..3 30us : 0:120:R ==> [003] 2448: 94:R sleep
这并不是一个很大的跟踪,即使启用了函数跟踪,所以这里包含了整个跟踪。
当系统处于空闲状态时中断停止。在调用task_woken_rt()之前,设置了NEED_RESCHED标志,这由第一次出现’N’标志表示。
由于function tracing可能导致更大的延迟,但是如果不看到延迟中发生了什么,就很难知道是什么导致了延迟。有一个中间地带,那就是启用事件::
# echo 0 > options/function-trace
# echo wakeup_rt > current_tracer
# echo 1 > events/enable
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# chrt -f 5 sleep 1
# echo 0 > tracing_on
# cat trace
# tracer: wakeup_rt
#
# wakeup_rt latency trace v1.1.5 on 3.8.0-test+
# --------------------------------------------------------------------
# latency: 6 us, #12/12, CPU#2 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: sleep-5882 (uid:0 nice:0 policy:1 rt_prio:5)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
-0 2d.h4 0us : 0:120:R + [002] 5882: 94:R sleep
-0 2d.h4 0us : ttwu_do_activate.constprop.87 <-try_to_wake_up
-0 2d.h4 1us : sched_wakeup: comm=sleep pid=5882 prio=94 success=1 target_cpu=002
-0 2dNh2 1us : hrtimer_expire_exit: hrtimer=ffff88007796feb8
-0 2.N.2 2us : power_end: cpu_id=2
-0 2.N.2 3us : cpu_idle: state=4294967295 cpu_id=2
-0 2dN.3 4us : hrtimer_cancel: hrtimer=ffff88007d50d5e0
-0 2dN.3 4us : hrtimer_start: hrtimer=ffff88007d50d5e0 function=tick_sched_timer expires=34311211000000 softexpires=34311211000000
-0 2.N.2 5us : rcu_utilization: Start context switch
-0 2.N.2 5us : rcu_utilization: End context switch
-0 2d..3 6us : __schedule <-schedule
-0 2d..3 6us : 0:120:R ==> [002] 5882: 94:R sleep
通过启用hwlat tracer来执行硬件延迟检测器。
注意,这个tracer会影响系统的性能,因为它会周期性地使CPU在关闭中断的情况下持续忙碌::
# echo hwlat > current_tracer
# sleep 100
# cat trace
# tracer: hwlat
#
# entries-in-buffer/entries-written: 13/13 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<...>-1729 [001] d... 678.473449: #1 inner/outer(us): 11/12 ts:1581527483.343962693 count:6
<...>-1729 [004] d... 689.556542: #2 inner/outer(us): 16/9 ts:1581527494.889008092 count:1
<...>-1729 [005] d... 714.756290: #3 inner/outer(us): 16/16 ts:1581527519.678961629 count:5
<...>-1729 [001] d... 718.788247: #4 inner/outer(us): 9/17 ts:1581527523.889012713 count:1
<...>-1729 [002] d... 719.796341: #5 inner/outer(us): 13/9 ts:1581527524.912872606 count:1
<...>-1729 [006] d... 844.787091: #6 inner/outer(us): 9/12 ts:1581527649.889048502 count:2
<...>-1729 [003] d... 849.827033: #7 inner/outer(us): 18/9 ts:1581527654.889013793 count:1
<...>-1729 [007] d... 853.859002: #8 inner/outer(us): 9/12 ts:1581527658.889065736 count:1
<...>-1729 [001] d... 855.874978: #9 inner/outer(us): 9/11 ts:1581527660.861991877 count:1
<...>-1729 [001] d... 863.938932: #10 inner/outer(us): 9/11 ts:1581527668.970010500 count:1 nmi-total:7 nmi-count:1
<...>-1729 [007] d... 878.050780: #11 inner/outer(us): 9/12 ts:1581527683.385002600 count:1 nmi-total:5 nmi-count:1
<...>-1729 [007] d... 886.114702: #12 inner/outer(us): 9/12 ts:1581527691.385001600 count:1
上面的输出在头文件中有些相同。所有事件都将被禁用中断’d’。在函数标题下有:
#1
这是大于"tracing_thresh" 文件保存的最小延迟的事件记录计数(参见下面)。
inner/outer(us): 11/11
这显示了两个数字:“inner延迟”和“outer延迟”。测试在一个循环中运行,检查两次时间戳。在两个时间戳中检测到的延迟是“内部延迟”,在循环中前一个时间戳和下一个时间戳之后检测到的延迟是“外部延迟”。
ts:1581527483.343962693
在窗口中记录第一次延迟的绝对时间戳。
count:6
在窗口期间检测到延迟的次数。
nmi-total:7 nmi-count:1
在支持它的架构上,如果在测试期间出现一个NMI,花费在NMI上的时间以“nmi-total”(以微秒为单位)报告。
如果在测试期间出现NMI,那么所有具有NMI的架构都将显示“NMI计数”。
tracing_threshold
这将被自动设置为“10”来表示10微秒。这是在记录跟踪之前需要检测的延迟阈值。
注意,当helat tracer完成时(另一个tracer写入"current_tracer"),tracing_thresh的原始值将会被放回这个文件中。
hwlat_detector/width
禁用中断时测试运行的时间长度。(循环中关中断的时间 off)
hwlat_detector/window
测试运行的窗口时间的长度,意思是将以每个窗口微妙的宽度运行(总的时间,off + on,其中on包括sleep的时间)。
tracing_cpumask
当测试开始时。创建一个内核线程来运行测试。这个线程将在每个周期(一个“window”)之间在tracing_cpumask中列出的cpu之间交替。要将测试限制为特定的cpu,请将此文件中的掩码设置为只运行测试的cpu。
这个tracer是函数跟踪器。可以在debugfs中启用function tracer。确保设置了ftrace_enabled;否则这个tracer是一个nop tracer。看下面"ftrace_enable"一节::
# sysctl kernel.ftrace_enabled=1
# echo function > current_tracer
# echo 1 > tracing_on
# usleep 1
# echo 0 > tracing_on
# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 24799/24799 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
bash-1994 [002] .... 3082.063030: mutex_unlock <-rb_simple_write
bash-1994 [002] .... 3082.063031: __mutex_unlock_slowpath <-mutex_unlock
bash-1994 [002] .... 3082.063031: __fsnotify_parent <-fsnotify_modify
bash-1994 [002] .... 3082.063032: fsnotify <-fsnotify_modify
bash-1994 [002] .... 3082.063032: __srcu_read_lock <-fsnotify
bash-1994 [002] .... 3082.063032: add_preempt_count <-__srcu_read_lock
bash-1994 [002] ...1 3082.063032: sub_preempt_count <-__srcu_read_lock
bash-1994 [002] .... 3082.063033: __srcu_read_unlock <-fsnotify
[...]
注意:function tracer使用ring buffer来存储上述条目。最新的数据可能会覆盖旧数据。有时候使用echo停止跟踪是不够的,因为tracing可能已经覆盖了要记录的数据。出于这个原因,有时最好从程序中直接禁用tracing。这允许在到达感兴趣部分停止跟踪。要直接从C程序禁用跟踪,可以使用如下代码片段::
int trace_fd;
[...]
int main(int argc, char *argv[]) {
[...]
trace_fd = open(tracing_file("tracing_on"), O_WRONLY);
[...]
if (condition_hit()) {
write(trace_fd, "0", 1);
}
[...]
}
通过写入set_ftrace_pid,可以跟踪单个线程。例如::
# cat set_ftrace_pid
no pid
# echo 3111 > set_ftrace_pid
# cat set_ftrace_pid
3111
# echo function > current_tracer
# cat trace | head
# tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
yum-updatesd-3111 [003] 1637.254676: finish_task_switch <-thread_return
yum-updatesd-3111 [003] 1637.254681: hrtimer_cancel <-schedule_hrtimeout_range
yum-updatesd-3111 [003] 1637.254682: hrtimer_try_to_cancel <-hrtimer_cancel
yum-updatesd-3111 [003] 1637.254683: lock_hrtimer_base <-hrtimer_try_to_cancel
yum-updatesd-3111 [003] 1637.254685: fget_light <-do_sys_poll
yum-updatesd-3111 [003] 1637.254686: pipe_poll <-do_sys_poll
# echo > set_ftrace_pid
# cat trace |head
# tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
##### CPU 3 buffer started ####
yum-updatesd-3111 [003] 1701.957688: free_poll_entry <-poll_freewait
yum-updatesd-3111 [003] 1701.957689: remove_wait_queue <-free_poll_entry
yum-updatesd-3111 [003] 1701.957691: fput <-free_poll_entry
yum-updatesd-3111 [003] 1701.957692: audit_syscall_exit <-sysret_audit
yum-updatesd-3111 [003] 1701.957693: path_put <-audit_syscall_exit
如果想在执行时跟踪一个函数,您可以使用类似于这个简单程序的东西::
#include
#include
#include
#include
#include
#include
#include
#define _STR(x) #x
#define STR(x) _STR(x)
#define MAX_PATH 256
const char *find_tracefs(void)
{
static char tracefs[MAX_PATH+1];
static int tracefs_found;
char type[100];
FILE *fp;
if (tracefs_found)
return tracefs;
if ((fp = fopen("/proc/mounts","r")) == NULL) {
perror("/proc/mounts");
return NULL;
}
while (fscanf(fp, "%*s %"
STR(MAX_PATH)
"s %99s %*s %*d %*d\n",
tracefs, type) == 2) {
if (strcmp(type, "tracefs") == 0)
break;
}
fclose(fp);
if (strcmp(type, "tracefs") != 0) {
fprintf(stderr, "tracefs not mounted");
return NULL;
}
strcat(tracefs, "/tracing/");
tracefs_found = 1;
return tracefs;
}
const char *tracing_file(const char *file_name)
{
static char trace_file[MAX_PATH+1];
snprintf(trace_file, MAX_PATH, "%s/%s", find_tracefs(), file_name);
return trace_file;
}
int main (int argc, char **argv)
{
if (argc < 1)
exit(-1);
if (fork() > 0) {
int fd, ffd;
char line[64];
int s;
ffd = open(tracing_file("current_tracer"), O_WRONLY);
if (ffd < 0)
exit(-1);
write(ffd, "nop", 3);
fd = open(tracing_file("set_ftrace_pid"), O_WRONLY);
s = sprintf(line, "%d\n", getpid());
write(fd, line, s);
write(ffd, "function", 8);
close(fd);
close(ffd);
execvp(argv[1], argv+1);
}
return 0;
}
或者是这样简单的脚本::
#!/bin/bash
tracefs=`sed -ne 's/^tracefs \(.*\) tracefs.*/\1/p' /proc/mounts`
echo nop > $tracefs/tracing/current_tracer
echo 0 > $tracefs/tracing/tracing_on
echo $$ > $tracefs/tracing/set_ftrace_pid
echo function > $tracefs/tracing/current_tracer
echo 1 > $tracefs/tracing/tracing_on
exec "$@"
function_graph tracer与function tracer类似,不同之处在于它探测函数的入口和出口。这是通过在每个task_struct中使用动态分配的返回地址堆栈实现的。在函数项上,tracer覆盖跟踪的每个函数的返回地址以设置一个自定义探针。因此原始返回地址存储在task_struct中的返回地址堆栈中。
在函数的两端进行探测会导致一些特殊的特性,例如:
这个tracer在以下几种情况是很有用的:
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) | sys_open() {
0) | do_sys_open() {
0) | getname() {
0) | kmem_cache_alloc() {
0) 1.382 us | __might_sleep();
0) 2.478 us | }
0) | strncpy_from_user() {
0) | might_fault() {
0) 1.389 us | __might_sleep();
0) 2.553 us | }
0) 3.807 us | }
0) 7.876 us | }
0) | alloc_fd() {
0) 0.668 us | _spin_lock();
0) 0.570 us | expand_files();
0) 0.586 us | _spin_unlock();
有几列可以被动态地启用和禁用。可以根据自己的需要,使用任何想要的选项组合:
执行该功能的cpu编号默认为启用状态。有时候只跟踪一个cpu更好(参见"tracing_cpu_mask"文件),或者有时在cpu跟踪开关时可能会看到无序的函数调用。
echo nofuncgraph-cpu > trace_options
echo funcgraph-cpu > trace_options
持续时间(函数的执行时间)显示在函数的右括号行上,如果是第一个叶节点,则显示在与当前函数相同的行上。默认是启用的。
echo nofuncgraph-duration > trace_options
echo funcgraph-duration > trace_options
当达到持续时间阈值时,开销字段在持续时间字段之前。
echo nofuncgraph-overhead > trace_options
echo funcgraph-overhead > trace_options
funcgraph-duration
ie::
3) # 1837.709 us | } /* __switch_to */
3) | finish_task_switch() {
3) 0.313 us | _raw_spin_unlock_irq();
3) 3.177 us | }
3) # 1889.063 us | } /* __schedule */
3) ! 140.417 us | } /* __schedule */
3) # 2034.948 us | } /* schedule */
3) * 33998.59 us | } /* schedule_preempt_disabled */
[...]
1) 0.260 us | msecs_to_jiffies();
1) 0.313 us | __rcu_read_unlock();
1) + 61.770 us | }
1) + 64.479 us | }
1) 0.313 us | rcu_bh_qs();
1) 0.313 us | __local_bh_enable();
1) ! 217.240 us | }
1) 0.365 us | idle_cpu();
1) | rcu_irq_exit() {
1) 0.417 us | rcu_eqs_enter_common.isra.47();
1) 3.125 us | }
1) ! 227.812 us | }
1) ! 457.395 us | }
1) @ 119760.2 us | }
[...]
2) | handle_IPI() {
1) 6.979 us | }
2) 0.417 us | scheduler_ipi();
1) 9.791 us | }
1) + 12.917 us | }
2) 3.490 us | }
1) + 15.729 us | }
1) + 18.542 us | }
2) $ 3594274 us | }
flags::
‘+’ 表示函数超过10个usec。
‘!’ 表示函数超过100个usec。
‘#’ 表示函数超过1000个usec。
‘*’ 表示函数超过10毫秒。
‘@’ 表示函数超过100毫秒。
‘$’ 表示函数超过1秒。
task/pid字段显示执行该函数的线程cmdline和pid。默认是禁用的。
echo nofuncgraph-proc > trace_options
echo funcgraph-proc > trace_options
ie::
# tracer: function_graph
#
# CPU TASK/PID DURATION FUNCTION CALLS
# | | | | | | | | |
0) sh-4802 | | d_free() {
0) sh-4802 | | call_rcu() {
0) sh-4802 | | __call_rcu() {
0) sh-4802 | 0.616 us | rcu_process_gp_end();
0) sh-4802 | 0.586 us | check_for_new_grace_period();
0) sh-4802 | 2.899 us | }
0) sh-4802 | 4.040 us | }
0) sh-4802 | 5.151 us | }
0) sh-4802 | + 49.370 us | }
绝对时间字段是系统时钟自启动以来给出的绝对时间戳。在函数的每次进入/退出时都会给出这个时间的快照。
echo nofuncgraph-abstime > trace_options
echo funcgraph-abstime > trace_options
ie::
#
# TIME CPU DURATION FUNCTION CALLS
# | | | | | | | |
360.774522 | 1) 0.541 us | }
360.774522 | 1) 4.663 us | }
360.774523 | 1) 0.541 us | __wake_up_bit();
360.774524 | 1) 6.796 us | }
360.774524 | 1) 7.952 us | }
360.774525 | 1) 9.063 us | }
360.774525 | 1) 0.615 us | journal_mark_dirty();
360.774527 | 1) 0.578 us | __brelse();
360.774528 | 1) | reiserfs_prepare_for_journal() {
360.774528 | 1) | unlock_buffer() {
360.774529 | 1) | wake_up_bit() {
360.774529 | 1) | bit_waitqueue() {
360.774530 | 1) 0.594 us | __phys_addr();
如果函数的开头不在trace buffer中,则函数名总是显示在函数的右括号之后。
对于开头在跟踪缓冲区中的函数,可以启用在结束括号后显示函数名,以便使用grep更容易地搜索函数持续时间。默认是禁用的。
隐藏:echo nofuncgraph-tail > trace_options
显示:echo funcgraph-tail > trace_options
使用nofuncgraph-tail(默认)的示例::
0) | putname() {
0) | kmem_cache_free() {
0) 0.518 us | __phys_addr();
0) 1.757 us | }
0) 2.861 us | }
示例funcgraph-tail::
0) | putname() {
0) | kmem_cache_free() {
0) 0.518 us | __phys_addr();
0) 1.757 us | } /* kmem_cache_free() */
0) 2.861 us | } /* putname() */
例如,如果你想在__might_sleep()函数中添加注释,你只需要包含
trace_printk("I'm a comment!\n")
将会产生::
1) | __might_sleep() {
1) | /* I'm a comment! */
1) 1.449 us | }
在下面的“dynamic ftrace”一节中会发现此跟踪程序的其他有用特性,比如只跟踪特定的函数或任务。
如果设置了CONFIG_DYNAMIC_FTRACE,当function tracing被禁用时,系统几乎没有开销的运行。它的工作方式是调用mcount函数(放在每个内核函数的开始处,由gcc中-pg开关产生)开始指向一个简单的返回指令。(启用FTRACE将在编译内核时包含-pg开关。)
在编译时,每个C文件对象都通过recordmcount程序(位于scripts目录中)运行。这个程序将解析C对象中的ELF头,以找到.text段中调用mcount的所有位置。从gcc 4.6版本开始,“-mentry"已经被添加到x86中,它调用”_fentry_“而不是"mcount”。在创建堆栈框架之前调用。
创建了一个名为“__mcount_loc”的段,它保存了对.text段中所有mcount/fentry调用站点的引用。recordmcount程序将此部分重新链接到原始对象中。内核的最后一个链接阶段将把所有这些引用添加到一个表中。
在启动时,在初始化SMP之前,动态ftrace代码扫描该表并将所有位置更新为nops。它还记录位置,这些位置被添加到available_filter_functions列表中。模块在加载时和执行之前被处理。当一个模块被卸载时,它也会从ftrace函数列表中删除它的函数。这在模块卸载代码中是自动的,模块作者不需要担心它。
当启用tracing时,修改功能跟踪点的过程取决于体系结构。旧的方法是使用kstop_machine来防止与执行被修改代码的CPU之间的竞争(这会导致CPU做一些不希望做的事情,特别是当修改的代码跨越缓存(或页面)边界时),并将nops修补回调用。但这一次,它们不调用mcount(它只是一个函数存根)。他们现在调用ftrace基础设施。
修改tracepoints函数的新方法是在要修改的位置放置一个断点,同步所有的cpu,修改断点未覆盖的指令的其余部分。再次同步所有的cpu,然后在完成后移除断点并保存到ftrace调用点。
有些架构甚至不需要在同步上瞎捣鼓,只要在旧代码之上添加新代码,其他cpu同时执行它也不会产生任何问题。
记录正在跟踪的函数的一个特殊副作用是,现在我们可以有选择地选择希望跟踪的函数,以及希望保留mcount调用为nops的函数。
使用了两个文件,一个用于启用,一个用于禁用指定函数的跟踪。它们是:
set_ftrace_filter
和set_ftrace_notrace
可以添加到这些文件的可用函数列表在available_filter_functions
::
# cat available_filter_functions
put_prev_task_idle
kmem_cache_create
pick_next_task_rt
get_online_cpus
pick_next_task_fair
mutex_lock
[...]
如果我们仅对sys_nanosleep和hrtimer_interrupt感兴趣::
# echo sys_nanosleep hrtimer_interrupt > set_ftrace_filter
# echo function > current_tracer
# echo 1 > tracing_on
# usleep 1
# echo 0 > tracing_on
# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 5/5 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
usleep-2665 [001] .... 4186.475355: sys_nanosleep <-system_call_fastpath
-0 [001] d.h1 4186.475409: hrtimer_interrupt <-smp_apic_timer_interrupt
usleep-2665 [001] d.h1 4186.475426: hrtimer_interrupt <-smp_apic_timer_interrupt
-0 [003] d.h1 4186.475426: hrtimer_interrupt <-smp_apic_timer_interrupt
-0 [002] d.h1 4186.475427: hrtimer_interrupt <-smp_apic_timer_interrupt
要查看哪些函数正在被跟踪,可以cat这个文件::
# cat set_ftrace_filter
hrtimer_interrupt
sys_nanosleep
也许这还不够,filter还允许glob(7)匹配。
``
`` 将从
的开始开始匹配函数。 ``*
`` 将从
的尾部开始匹配函数。 ``*
*`` 将从
的中间往前后开始匹配函数。 ``
* `` 将从前后往中间开始匹配函数。
. .注意::
最好使用引号将通配符括起来,否则shell可能会将参数展开为本地目录中的文件名。
# echo 'hrtimer_*' > set_ftrace_filter
Produces::
# tracer: function
#
# entries-in-buffer/entries-written: 897/897 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
-0 [003] dN.1 4228.547803: hrtimer_cancel <-tick_nohz_idle_exit
-0 [003] dN.1 4228.547804: hrtimer_try_to_cancel <-hrtimer_cancel
-0 [003] dN.2 4228.547805: hrtimer_force_reprogram <-__remove_hrtimer
-0 [003] dN.1 4228.547805: hrtimer_forward <-tick_nohz_idle_exit
-0 [003] dN.1 4228.547805: hrtimer_start_range_ns <-hrtimer_start_expires.constprop.11
-0 [003] d..1 4228.547858: hrtimer_get_next_event <-get_next_timer_interrupt
-0 [003] d..1 4228.547859: hrtimer_start <-__tick_nohz_idle_enter
-0 [003] d..2 4228.547860: hrtimer_force_reprogram <-__rem
注意,我们丢失了sys_nanosleep::
# cat set_ftrace_filter
hrtimer_run_queues
hrtimer_run_pending
hrtimer_init
hrtimer_cancel
hrtimer_try_to_cancel
hrtimer_forward
hrtimer_start
hrtimer_reprogram
hrtimer_force_reprogram
hrtimer_get_next_event
hrtimer_interrupt
hrtimer_nanosleep
hrtimer_wakeup
hrtimer_get_remaining
hrtimer_get_res
hrtimer_init_sleeper
‘>‘和’>>‘是适用于ftrace的,要重写filter使用’>’,附加到filter使用’>>'。
清除一个filter,以便所有功能将被再次记录::
# echo > set_ftrace_filter
# cat set_ftrace_filter
#
再一次,现在试试附加::
# echo sys_nanosleep > set_ftrace_filter
# cat set_ftrace_filter
sys_nanosleep
# echo 'hrtimer_*' >> set_ftrace_filter
# cat set_ftrace_filter
hrtimer_run_queues
hrtimer_run_pending
hrtimer_init
hrtimer_cancel
hrtimer_try_to_cancel
hrtimer_forward
hrtimer_start
hrtimer_reprogram
hrtimer_force_reprogram
hrtimer_get_next_event
hrtimer_interrupt
sys_nanosleep
hrtimer_nanosleep
hrtimer_wakeup
hrtimer_get_remaining
hrtimer_get_res
hrtimer_init_sleeper
"set_ftrace_notrace"文件阻止这些函数被跟踪::
# echo '*preempt*' '*lock*' > set_ftrace_notrace
Produces::
# tracer: function
#
# entries-in-buffer/entries-written: 39608/39608 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
bash-1994 [000] .... 4342.324896: file_ra_state_init <-do_dentry_open
bash-1994 [000] .... 4342.324897: open_check_o_direct <-do_last
bash-1994 [000] .... 4342.324897: ima_file_check <-do_last
bash-1994 [000] .... 4342.324898: process_measurement <-ima_file_check
bash-1994 [000] .... 4342.324898: ima_get_action <-process_measurement
bash-1994 [000] .... 4342.324898: ima_match_policy <-ima_get_action
bash-1994 [000] .... 4342.324899: do_truncate <-do_last
bash-1994 [000] .... 4342.324899: should_remove_suid <-do_truncate
bash-1994 [000] .... 4342.324899: notify_change <-do_truncate
bash-1994 [000] .... 4342.324900: current_fs_time <-notify_change
bash-1994 [000] .... 4342.324900: current_kernel_time <-current_fs_time
bash-1994 [000] .... 4342.324900: timespec_trunc <-current_fs_time
可以看到没有更多的锁或抢占跟踪。
因为处理字符串的开销很大(在与传入的字符串进行比较之前,需要查找函数的地址),所以也可以使用索引来启用函数。在一次设置数千个特定函数的情况下,这很有用。通过传入一个数字列表,不会发生任何字符串处理。相反,将选择内部数组中特定位置的函数(对应于“available_filter_functions”文件中的函数)。
# echo 1 > set_ftrace_filter
将选择“available_filter_functions”中列出的第一个函数::
# head -1 available_filter_functions
trace_initcall_finish_cb
# cat set_ftrace_filter
trace_initcall_finish_cb
# head -50 available_filter_functions | tail -1
x86_pmu_commit_txn
# echo 1 50 > set_ftrace_filter
# cat set_ftrace_filter
trace_initcall_finish_cb
x86_pmu_commit_txn
虽然上面已经解释了function tracer和function_graph tracer,但是有一些特殊的特性只在function_graph tracer中可用。
如果你只想跟踪一个函数及其所有子函数,你只需要将它的名字回传给set_graph_function::
# echo __do_fault > set_graph_function
将产生以下__do_fault()函数的“扩展”跟踪::
0) | __do_fault() {
0) | filemap_fault() {
0) | find_lock_page() {
0) 0.804 us | find_get_page();
0) | __might_sleep() {
0) 1.329 us | }
0) 3.904 us | }
0) 4.979 us | }
0) 0.653 us | _spin_lock();
0) 0.578 us | page_add_file_rmap();
0) 0.525 us | native_set_pte_at();
0) 0.585 us | _spin_unlock();
0) | unlock_page() {
0) 0.541 us | page_waitqueue();
0) 0.639 us | __wake_up_bit();
0) 2.786 us | }
0) + 14.237 us | }
0) | __do_fault() {
0) | filemap_fault() {
0) | find_lock_page() {
0) 0.698 us | find_get_page();
0) | __might_sleep() {
0) 1.412 us | }
0) 3.950 us | }
0) 5.098 us | }
0) 0.631 us | _spin_lock();
0) 0.571 us | page_add_file_rmap();
0) 0.526 us | native_set_pte_at();
0) 0.586 us | _spin_unlock();
0) | unlock_page() {
0) 0.533 us | page_waitqueue();
0) 0.638 us | __wake_up_bit();
0) 2.793 us | }
0) + 14.012 us | }
您还可以同时扩展多个函数::
# echo sys_open > set_graph_function
# echo sys_close >> set_graph_function
现在,如果你想返回跟踪所有函数,只需要清除这个特殊的过滤器::
# echo > set_graph_function
注意,proc sysctl ftrace_enable是函数跟踪程序的一个很大的开/关开关。默认情况下,它是启用的(当函数跟踪在内核中启用时)。如果禁用,则禁用所有功能跟踪。这不仅包括ftrace的函数跟踪器,还包括用于任何其他用途的函数跟踪器(perf、kprobes、堆栈跟踪、profiling,etc)。如果有一个注册了FTRACE_OPS_FL_PERMANENT设置的回调,则不能禁用它。
请小心禁用此功能。
可以这样禁用(启用)::
# sysctl kernel.ftrace_enabled=0
# sysctl kernel.ftrace_enabled=1
或者::
# echo 0 > /proc/sys/kernel/ftrace_enabled
# echo 1 > /proc/sys/kernel/ftrace_enabled
"set_ftrace_filter"接口支持一些命令。
跟踪命令的格式::
。
下面的commands被支持:
mod
该命令开启每个模块的功能过滤。参数定义模块。例如,如果只需要ext3模块中的write*函数,则运行:
# echo 'write*:mod:ext3' > set_ftrace_filter
这个命令与过滤器交互的方式与基于函数名的过滤相同。因此,通过向过滤器文件追加(>>),可以在不同模块中添加更多的函数。通过在前面添加"!"来移除特定模块::
# echo '!writeback*:mod:ext3' >> set_ftrace_filter
模块命令支持模块通配符。禁用跟踪所有功能,除了特定模块::
# echo '!*:mod:!ext3' >> set_ftrace_filter
禁用所有模块的跟踪,但仍然跟踪内核::
# echo '!*:mod:*' >> set_ftrace_filter
只在内核中启用过滤器::
# echo '*write*:mod:!*' >> set_ftrace_filter
为模块通配符启用过滤器::
# echo '*write*:mod:*snd*' >> set_ftrace_filter
traceon/traceoff
这些命令在命中指定的函数时打开或关闭跟踪。该参数确定跟踪系统被打开和关闭的次数。如果没有具体说明,则没有限制。例如,要禁用跟踪时,调度错误击中前5次,运行::
# echo '__schedule_bug:traceoff:5' > set_ftrace_filter
在命中__schedule_bug时总是关闭tracing::
# echo '__schedule_bug:traceoff' > set_ftrace_filter
无论是否将这些命令追加到set_ftrace_filter,这些命令都是累积的。要删除一个命令,在前面加上’!’然后删除参数::
echo '!__schedule_bug:traceoff:0' > set_ftrace_filter
上面的代码删除了具有计数器的__schedule_bug的traceoff命令。删除不带计数器的命令::
# echo '!__schedule_bug:traceoff' > set_ftrace_filter
snapshot
当函数命中时,将导致一个snapshot被触发::
# echo 'native_flush_tlb_others:snapshot' > set_ftrace_filter
仅触发一次:
# echo 'native_flush_tlb_others:snapshot:1' > set_ftrace_filter
为了移除上面的commands::
# echo '!native_flush_tlb_others:snapshot' > set_ftrace_filter
# echo '!native_flush_tlb_others:snapshot:0' > set_ftrace_filter
enable_event/disable_event
这些命令可以启用/禁用跟踪事件。注意,因为function tracing是敏感的,当这些命令被注册,跟踪点被激活,但是在"soft"模式下是禁用的。意思是,跟踪点会被调用,但是不会被跟踪。只要有命令触发事件跟踪点,该事件跟踪点就会保持这种模式::
# echo 'try_to_wake_up:enable_event:sched:sched_switch:2' > \
set_ftrace_filter
The format is::
为了移除事件命令::
# 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
当函数被命中时,它将把ftrace ring buffer的内容转储到控制台。如果您需要调试某些内容,并希望在遇到某个函数时转储跟踪,这将非常有用。也许它是一个在三重错误发生之前调用的函数,并且不允许获得常规转储。
cpudump
当函数被命中时,它将把当前CPU的ftrace环缓冲区的内容转储到控制台。与“dump”命令不同,它只打印出执行触发转储的函数的CPU的环形缓冲区的内容。
stacktrace
当函数被命中时,将记录一个堆栈跟踪。
"trace_pipe"输出与"trace"文件相同的内容,但是对跟踪的影响不同。每次从"trace_pipe"读取都会被消耗。这意味着后续的读取将有所不同。跟踪是动态的::
# echo function > current_tracer
# cat trace_pipe > /tmp/trace.out &
[1] 4153
# echo 1 > tracing_on
# usleep 1
# echo 0 > tracing_on
# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 0/0 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
#
# cat /tmp/trace.out
bash-1994 [000] .... 5281.568961: mutex_unlock <-rb_simple_write
bash-1994 [000] .... 5281.568963: __mutex_unlock_slowpath <-mutex_unlock
bash-1994 [000] .... 5281.568963: __fsnotify_parent <-fsnotify_modify
bash-1994 [000] .... 5281.568964: fsnotify <-fsnotify_modify
bash-1994 [000] .... 5281.568964: __srcu_read_lock <-fsnotify
bash-1994 [000] .... 5281.568964: add_preempt_count <-__srcu_read_lock
bash-1994 [000] ...1 5281.568965: sub_preempt_count <-__srcu_read_lock
bash-1994 [000] .... 5281.568965: __srcu_read_unlock <-fsnotify
bash-1994 [000] .... 5281.568967: sys_dup2 <-system_call_fastpath
注意,读取"trace_pipe"文件将会阻塞,直到添加更多输入。这与"trace"文件相反。如果任何进程打开"trace"文件进行读取,它实际上将禁用tracing并阻止添加新条目。"trace_pipe"文件没有这个限制。
在诊断内核中的问题时,数据太多或不足都会带来麻烦。文件buffer_size_kb用于修改内部跟踪缓冲区的大小。列出的数字是每个CPU可以记录的条目数。要知道完整的大小,可以将可能的cpu数量与条目数量相乘::
# cat buffer_size_kb
1408 (units kilobytes)
或者简单的读取"buffer_total_size_kb"::
# cat buffer_total_size_kb
5632
为了修改buffer大小,echo数值到文件里(以1024字节段为单位)::
# echo 10000 > buffer_size_kb
# cat buffer_size_kb
10000 (units kilobytes)
它会尽可能多地分配。如果分配太多,可能会导致内存不足::
# echo 1000000000000 > buffer_size_kb
-bash: echo: write error: Cannot allocate memory
# cat buffer_size_kb
85
per_cpu缓冲区也可以单独更改:
# echo 10000 > per_cpu/cpu0/buffer_size_kb
# echo 100 > per_cpu/cpu1/buffer_size_kb
当per_cpu缓冲区不相同时,顶层的buffer_size_kb将只显示一个X::
# cat buffer_size_kb
X
这就是buffer_total_size_kb有用的地方::
# cat buffer_total_size_kb
12916
写入顶层buffer_size_kb将重置所有缓冲区,使其再次保持不变。
CONFIG_TRACER_SNAPSHOT为所有非延迟tracer提供了一个通用快照特性。(记录最大延迟的延迟tracer,如“irqsoff”或“wakeup”,不能使用这个特性,因为它们已经在内部使用快照机制。)
快照在特定时间点保留当前跟踪缓冲区,而不停止跟踪。Ftrace将当前缓冲区与一个备用缓冲区交换,并在新的当前(=以前的备用)缓冲区中继续跟踪。
“tracing”中的以下跟踪文件与此特性相关:
snapshot
这用于获取快照并读取快照的输出。Echo 1到该文件中以分配一个空闲缓冲区并获取一个快照(交换),然后以与“trace”相同的格式从该文件中读取快照(在“文件系统”一节中描述)。快照读取和跟踪都是可并行执行的。当分配空闲缓冲区时,echo 0释放它,而echo else(正)值则清除快照内容。更多的细节在下表中显示。
±-------------±-----------±-----------±-----------+
|status\input | 0 | 1 | else |
++++==+
|not allocated |(do nothing)| alloc+swap |(do nothing)|
±-------------±-----------±-----------±-----------+
|allocated | free | swap | clear |
±-------------±-----------±-----------±-----------+
下面是一个使用快照特性的示例::
# echo 1 > events/sched/enable
# echo 1 > snapshot
# cat snapshot
# tracer: nop
#
# entries-in-buffer/entries-written: 71/71 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
-0 [005] d... 2440.603828: sched_switch: prev_comm=swapper/5 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2242 next_prio=120
sleep-2242 [005] d... 2440.603846: sched_switch: prev_comm=snapshot-test-2 prev_pid=2242 prev_prio=120 prev_state=R ==> next_comm=kworker/5:1 next_pid=60 next_prio=120
[...]
-0 [002] d... 2440.707230: sched_switch: prev_comm=swapper/2 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2229 next_prio=120
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 77/77 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
-0 [007] d... 2440.707395: sched_switch: prev_comm=swapper/7 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2243 next_prio=120
snapshot-test-2-2229 [002] d... 2440.707438: sched_switch: prev_comm=snapshot-test-2 prev_pid=2229 prev_prio=120 prev_state=S ==> next_comm=swapper/2 next_pid=0 next_prio=120
[...]
如果当前tracer是延迟tracer之一时尝试使用此快照特性,将得到以下结果::
# echo wakeup > current_tracer
# echo 1 > snapshot
bash: echo: write error: Device or resource busy
# cat snapshot
cat: snapshot: Device or resource busy
在tracefs跟踪目录中有一个名为“instances”的目录。
使用mkdir可以在这个目录中创建新的目录,使用rmdir可以删除目录。在这个目录中使用mkdir创建的目录在创建之后已经包含了文件和其他目录::
# mkdir instances/foo
# ls instances/foo
buffer_size_kb buffer_total_size_kb events free_buffer per_cpu
set_event snapshot trace trace_clock trace_marker trace_options
trace_pipe tracing_on
如你所见,新目录看起来类似于tracing/目录本身。事实上,它非常相似,除了缓冲区和事件对于主目录或其他创建的实例是不可知的。
新目录中的文件与跟踪目录中同名的文件的工作方式相同,只是所使用的缓冲区是一个单独的新缓冲区。这些文件会影响该缓冲区,但不会影响主缓冲区(trace_options除外)。目前,trace_options对所有实例和顶层缓冲区的影响是一样的,但在未来的版本中可能会有所改变。也就是说,选项可能特定于它们所在的实例。
注意,这里没有function tracer文件,current_tracer和available_tracers也没有。这是因为缓冲区目前只能为其启用事件(注意,在目前测试的5.12新内核已经可以使用function tracer等等)::
# mkdir instances/foo
# mkdir instances/bar
# mkdir instances/zoot
# echo 100000 > buffer_size_kb
# echo 1000 > instances/foo/buffer_size_kb
# echo 5000 > instances/bar/per_cpu/cpu1/buffer_size_kb
# echo function > current_trace
# echo 1 > instances/foo/events/sched/sched_wakeup/enable
# echo 1 > instances/foo/events/sched/sched_wakeup_new/enable
# echo 1 > instances/foo/events/sched/sched_switch/enable
# echo 1 > instances/bar/events/irq/enable
# echo 1 > instances/zoot/events/syscalls/enable
# cat trace_pipe
CPU:2 [LOST 11745 EVENTS]
bash-2044 [002] .... 10594.481032: _raw_spin_lock_irqsave <-get_page_from_freelist
bash-2044 [002] d... 10594.481032: add_preempt_count <-_raw_spin_lock_irqsave
bash-2044 [002] d..1 10594.481032: __rmqueue <-get_page_from_freelist
bash-2044 [002] d..1 10594.481033: _raw_spin_unlock <-get_page_from_freelist
bash-2044 [002] d..1 10594.481033: sub_preempt_count <-_raw_spin_unlock
bash-2044 [002] d... 10594.481033: get_pageblock_flags_group <-get_pageblock_migratetype
bash-2044 [002] d... 10594.481034: __mod_zone_page_state <-get_page_from_freelist
bash-2044 [002] d... 10594.481034: zone_statistics <-get_page_from_freelist
bash-2044 [002] d... 10594.481034: __inc_zone_state <-zone_statistics
bash-2044 [002] d... 10594.481034: __inc_zone_state <-zone_statistics
bash-2044 [002] .... 10594.481035: arch_dup_task_struct <-copy_process
[...]
# cat instances/foo/trace_pipe
bash-1998 [000] d..4 136.676759: sched_wakeup: comm=kworker/0:1 pid=59 prio=120 success=1 target_cpu=000
bash-1998 [000] dN.4 136.676760: sched_wakeup: comm=bash pid=1998 prio=120 success=1 target_cpu=000
-0 [003] d.h3 136.676906: sched_wakeup: comm=rcu_preempt pid=9 prio=120 success=1 target_cpu=003
-0 [003] d..3 136.676909: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=rcu_preempt next_pid=9 next_prio=120
rcu_preempt-9 [003] d..3 136.676916: sched_switch: prev_comm=rcu_preempt prev_pid=9 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120
bash-1998 [000] d..4 136.677014: sched_wakeup: comm=kworker/0:1 pid=59 prio=120 success=1 target_cpu=000
bash-1998 [000] dN.4 136.677016: sched_wakeup: comm=bash pid=1998 prio=120 success=1 target_cpu=000
bash-1998 [000] d..3 136.677018: sched_switch: prev_comm=bash prev_pid=1998 prev_prio=120 prev_state=R+ ==> next_comm=kworker/0:1 next_pid=59 next_prio=120
kworker/0:1-59 [000] d..4 136.677022: sched_wakeup: comm=sshd pid=1995 prio=120 success=1 target_cpu=001
kworker/0:1-59 [000] d..3 136.677025: sched_switch: prev_comm=kworker/0:1 prev_pid=59 prev_prio=120 prev_state=S ==> next_comm=bash next_pid=1998 next_prio=120
[...]
# cat instances/bar/trace_pipe
migration/1-14 [001] d.h3 138.732674: softirq_raise: vec=3 [action=NET_RX]
-0 [001] dNh3 138.732725: softirq_raise: vec=3 [action=NET_RX]
bash-1998 [000] d.h1 138.733101: softirq_raise: vec=1 [action=TIMER]
bash-1998 [000] d.h1 138.733102: softirq_raise: vec=9 [action=RCU]
bash-1998 [000] ..s2 138.733105: softirq_entry: vec=1 [action=TIMER]
bash-1998 [000] ..s2 138.733106: softirq_exit: vec=1 [action=TIMER]
bash-1998 [000] ..s2 138.733106: softirq_entry: vec=9 [action=RCU]
bash-1998 [000] ..s2 138.733109: softirq_exit: vec=9 [action=RCU]
sshd-1995 [001] d.h1 138.733278: irq_handler_entry: irq=21 name=uhci_hcd:usb4
sshd-1995 [001] d.h1 138.733280: irq_handler_exit: irq=21 ret=unhandled
sshd-1995 [001] d.h1 138.733281: irq_handler_entry: irq=21 name=eth0
sshd-1995 [001] d.h1 138.733283: irq_handler_exit: irq=21 ret=handled
[...]
# cat instances/zoot/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 18996/18996 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
bash-1998 [000] d... 140.733501: sys_write -> 0x2
bash-1998 [000] d... 140.733504: sys_dup2(oldfd: a, newfd: 1)
bash-1998 [000] d... 140.733506: sys_dup2 -> 0x1
bash-1998 [000] d... 140.733508: sys_fcntl(fd: a, cmd: 1, arg: 0)
bash-1998 [000] d... 140.733509: sys_fcntl -> 0x1
bash-1998 [000] d... 140.733510: sys_close(fd: a)
bash-1998 [000] d... 140.733510: sys_close -> 0x0
bash-1998 [000] d... 140.733514: sys_rt_sigprocmask(how: 0, nset: 0, oset: 6e2768, sigsetsize: 8)
bash-1998 [000] d... 140.733515: sys_rt_sigprocmask -> 0x0
bash-1998 [000] d... 140.733516: sys_rt_sigaction(sig: 2, act: 7fff718846f0, oact: 7fff71884650, sigsetsize: 8)
bash-1998 [000] d... 140.733516: sys_rt_sigaction -> 0x0
可以看到最顶部trace buffer的跟踪只显示了函数跟踪。foo实例显示唤醒和任务切换。
移除这些实例::
# rmdir instances/foo
# rmdir instances/bar
# rmdir instances/zoot
注意,如果一个进程在一个实例目录中打开了一个跟踪文件,那么rmdir将返回-EBUSY失败。
因为内核有固定大小的堆栈,所以重要的是不要把它浪费在函数中。内核开发人员必须意识到他们在堆栈上分配了什么。如果它们添加太多,系统可能会面临堆栈溢出的危险,并将发生损坏,通常会导致系统panic。
有一些工具可以进行检查,通常使用中断定期检查使用情况。但如果你能在每个函数调用中执行检查,那将变得非常有用。由于ftrace提供了一个function tracer,因此它可以方便地在每次函数调用时检查堆栈大小。这是通过stack tracer启用的。
CONFIG_STACK_TRACER启用ftrace堆栈跟踪功能。
要启用它,在/proc/sys/kernel/stack_tracer_enabled中写入一个’1’::
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
还可以通过在内核命令行参数中添加“stacktrace”来启用它,以便在启动期间跟踪内核的堆栈大小。
运行几分钟后,输出如下所示::
# cat stack_max_size
2928
# cat stack_trace
Depth Size Location (18 entries)
----- ---- --------
0) 2928 224 update_sd_lb_stats+0xbc/0x4ac
1) 2704 160 find_busiest_group+0x31/0x1f1
2) 2544 256 load_balance+0xd9/0x662
3) 2288 80 idle_balance+0xbb/0x130
4) 2208 128 __schedule+0x26e/0x5b9
5) 2080 16 schedule+0x64/0x66
6) 2064 128 schedule_timeout+0x34/0xe0
7) 1936 112 wait_for_common+0x97/0xf1
8) 1824 16 wait_for_completion+0x1d/0x1f
9) 1808 128 flush_work+0xfe/0x119
10) 1680 16 tty_flush_to_ldisc+0x1e/0x20
11) 1664 48 input_available_p+0x1d/0x5c
12) 1616 48 n_tty_poll+0x6d/0x134
13) 1568 64 tty_poll+0x64/0x7f
14) 1504 880 do_select+0x31e/0x511
15) 624 400 core_sys_select+0x177/0x216
16) 224 96 sys_select+0x91/0xb9
17) 128 128 system_call_fastpath+0x16/0x1b
注意,如果gcc使用-mentry,函数在建立堆栈frame之前就会被跟踪。这意味着在使用-mentry时,stack tracer不会测试到顶端叶的函数。
目前,-mfentry只在x86上的gcc 4.6.0及以上版本中使用。
set_event
设置事件接口可在/sys/kernel/debug/tracing/available_events
查看可设置事件。
为了激活sched_wakeup事件,For example:
# echo sched_wakeup >> /sys/kernel/debug/tracing/set_event
. . Note:: '>>'是有必要的,否则它首先禁用所有事件。
为了禁用事件可在事件前加!:
# echo '!sched_wakeup' >> /sys/kernel/debug/tracing/set_event
为了禁用所有事件,向set_event写空:
# echo > /sys/kernel/debug/tracing/set_event
为了激活所有事件,echo *:*或者 、*: 到set_event文件:
# echo *:* > /sys/kernel/debug/tracing/set_event
事件被有序的组织到subsystems里,比如,ext4,irq,sched,etc.等,所有的事件名都像这种结构:
子系统的名字是可选的,但是在available_events中是被列出来了的。所有子系统事件可用subsystem:*
这样的语法,比如这条命令:
# echo 'irq:*' > /sys/kernel/debug/tracing/set_event
所有可用事件也可以通过/sys/kernel/debug/tracing/events/
这个文件夹查看
为了enable事件’sched_wakeup’::
# echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
为了disable这个事件::
# echo 0 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
为了在sched子系统中enable所有事件::
# echo 1 > /sys/kernel/debug/tracing/events/sched/enable
为了enable所有事件::
# echo 1 > /sys/kernel/debug/tracing/events/enable
当我们读这些enable文件时,可能有下面4种值:
为了能便捷的尽早boot debug,使用boot选项:
trace_event=[event_list]
event_list以逗号分隔。
TODO
可以查看内核samples/trace_events
每个跟踪事件都有一个关联的formats文件,该文件包含记录了事件中每个字段的描述。这些信息可以用解析二进制跟踪流,并且也是查找可以在事件filters中使用的字段名的地方。也在文本模式下展示被用于打印事件的格式字符串,以及用于分析事件名和ID。
每个事件都有一组与之相关的’common’字段,字段前缀为common_
,其他字段每个事件各不相同,他们对应于该事件的TRACE_EVENT。
每个字段格式如下::
field:field-type field-name; offset:N; size:N;
其中offset是跟踪记录字段的偏移量,size是数据项的大小(以字节为单位)
如下展示sched_wakeup事件的formats::
# cat /sys/kernel/debug/tracing/events/sched/sched_wakeup/formats
name: sched_wakeup
ID: 256
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:char comm[16]; offset:8; size:16; signed:0;
field:pid_t pid; offset:24; size:4; signed:1;
field:int prio; offset:28; size:4; signed:1;
field:int success; offset:32; size:4; signed:1;
field:int target_cpu; offset:36; size:4; signed:1;
print fmt: "comm=%s pid=%d prio=%d target_cpu=%03d", REC->comm, REC->pid, REC->prio, REC->target_cpu
事件包含十个字段,前五个为common字段,后五个为事件特有字段。
事件可以在kernel中被过滤,通过boolean类型的’filter expressions’,一旦事件被记录到了trace buffer中,那就会根据该事件关联的filter筛选表达式来检查字段。
一个筛选表达式由一个或多个`判断`组成,可以用使用逻辑运算符号"&&“和”||"的组合。
它将记录事件中包含的字段值和常量进行比较,根据字段值是否匹配返回,不匹配(0),匹配(1)。
filed-name relational-operator value
圆括号可用于提供逻辑分组,双引号可用于防止shell将操作符解释为shell元字符。
filed-name
可在formats
中找到。
The relational-operators取决于被测试字段的类型。
这些 relational-operators可以用与数值类型字段:
==, !=, <, <=, >, >=, &
这些relational-operators可以用于字符串类型字段:
==, !=, ~
通配符~可以接受通配符字符(\*,?)和字符类([):
prev_comm ~ "*sh"
prev_comm ~ "sh*"
prev_comm ~ "*sh*"
prev_comm ~ "ba*sh"
单个事件的筛选器是通过筛选器表达式写入给定事件的filters文件来设置的。
比如:
# cd /sys/kernel/debug/tracing/events/sched/sched_wakeup
# echo "common_preempt_count > 4" > filter
一个稍微复杂一点的例子:
# cd /sys/kernel/debug/tracing/events/signal/signal_generate
# echo "((sig >= 10 && sig < 15) || sig == 17) && comm != bash" > filter
如果表达式有错误,你会得到’Invalid argument’的错误,错误的字符串和错误消息可以通过查看filter看到,比如:
# cd /sys/kernel/debug/tracing/events/signal/signal_generate
# echo "((sig >= 10 && sig < 15) || dsig == 17) && comm != bash" > filter
-bash: echo: write error: Invalid argument
# cat filter
((sig >= 10 && sig < 15) || dsig == 17) && comm != bash
^
parse_error: Field not found
为了清除一个事件的filters,向事件的filter文件写0。
为了清除一个子系统的所有filters,向subsystem的filter文件写0
为了方便,可以将一个过滤器表达式写入子系统的filter文件,从而将子系统中每个事件的filter作为一组设置或者清除。但是要注意,如果子系统中用于任何事件的过滤器缺少子系统过滤器中指定的字段,或者如果由于任何其他原因不能应用该过滤器,该事件的过滤器将保留原先已有的设置。这有可能导致意外的过滤器混用输出 ‘‘混乱’’ 跟踪信息(用户可能认为每个事件的过滤器的设置都是有效的)。只有仅引用公共字段"common_"的筛选器才能保证成功传播到所有事件。
下面是一些子系统过滤器例子,也说明了上述几点:
在sched subsystem中清除所有事件的filters::
# cd /sys/kernel/debug/tracing/events/sched
# echo 0 > filter
# cat sched_switch/filter
none
# cat sched_wakeup/filter
none
在sched subsystem中为所有事件设置过滤common字段::
# cd /sys/kernel/debug/tracing/events/sched
# echo common_pid == 0 > filter
# cat sched_switch/filter
common_pid == 0
# cat sched_wakeup/filter
common_pid == 0
尝试为sched subsystem中的所有事件设置一个非common的过滤器字段(除了那些带有prev_pid字段的事件外的所有事件保留他们原来的过滤器表达式)::
# cd /sys/kernel/debug/tracing/events/sched
# echo prev_pid == 0 > filter
# cat sched_switch/filter
prev_pid == 0
# cat sched_wakeup/filter
common_pid == 0
在顶层目录存在一个set_event_pid文件,用于过滤任务事件(这将仅跟踪当前任务事件)::
# cd /sys/kernel/debug/tracing
# echo $$ > set_event_pid
# echo 1 > events/enable
为了在不丢失已存在的PID的情况下添加更多PIDs,使用">>"。
# echo 123 244 1 >> set_event_pid
跟踪事件可以有条件的调用触发器“命令”,这些命令可以采取各种形式,下面将详细描述;例如,在跟踪事件发生时启用或者禁用其他跟踪事件,或调用堆栈跟踪。每当调用带有附加触发器的跟踪事件时,就会调用与该事件关联的触发器命令集。任何给定的触发器还可以与事件筛选器关联,即事件只有通过了关联的筛选器才会调用该命令。如果没有与之关联的筛选器,他总是被调用的。
通过将触发器表达式写入给定事件的"trigger"文件,可以将触发器添加到特定事件中,也可以从特定事件中删除触发器。
一个给定的事件可以有任意数量的触发器与之关联,这取决于每条命令在这方面的限制。
事件触发器是在“soft”模式上实现的,这意味着每当跟踪事件有一个或者多个与之关联的触发器时,该事件即使没有实际启用,也会被激活,但会在“soft”模式下禁用,也就是说,跟踪点将被调用,但是不会被跟踪。
事件触发的语法大致是基于set_ftrace_filter,但有些是不同的,需要注意。
. .Note::
写入trace_marker
也可以启用写入/sys/kernel/tracing/events/ftrace/print/trigger
的触发器。
触发器是通过echo命令到"trigger"文件来添加的::
# echo 'command[:count] [if filter]' > trigger
通过在命令前加!来移除触发命令::
# echo '!command[:count] [if filter]' > trigger
当移除时[if filter]
不会在匹配命令时使用,所以可以在!中省略它。
[if filter]
的语法和过滤器表达式语法是相同的。
为了方便使用,使用>
写入trigger文件目前只是添加或删除单个触发器,并没有显示的支持>>
(>
的实际行为类似于>>
)或者截断支持来删除所有触发器(对于每添加一个你必须使用!
)
下面这些commands被支持:
enable_event/disable_event
这些命令可以在触发事件发生时启用或者禁用另一个跟踪事件。当这些命令被注册时,其他跟踪事件被激活,但是在“soft”模式下是被禁用的。也就是说跟踪点被调用,但是不会被跟踪。只要有一个有效的触发器可以触发事件跟踪点,该事件跟踪带你就会保持在此模式中。
例如,下面的触发器在进入read系统调用时跟踪kmalloc事件,末尾的:1
指定此启用只发生一次::
echo 'enable_event:kmalloc:1' > \
/sys/kernel/debug/tracing/events/syscalls/sys_enter_read/trigger
当一个read系统调用退出时,下面的触发器将导致kmalloc事件停止跟踪。这种禁用发生在每次read系统调用退出时::
# echo 'disable_event:kmem:kmalloc' > \
/sys/kernel/debug/tracing/events/syscalls/sys_exit_read/trigger
The format is::
enable_event::[:count]
disable_event::[:count]
为了去移除上面的commands::
# echo '!enable_event:kmem:kmalloc:1' > \
/sys/kernel/debug/tracing/events/syscalls/sys_enter_read/trigger
# echo '!disable_event:kmem:kmalloc' > \
/sys/kernel/debug/tracing/events/syscalls/sys_exit_read/trigger
注意,每个触发事件可以有任意数量的enable/disable_event触发器,但每个触发事件只能有一个触发器。例如,sys_enter_read可以有触发器同时启用kmem:kmalloc和sched:sched_switch,但不能有两个kmem:kmalloc版本,例如kmem:kmalloc和kmem:kmalloc:1或’kmem:kmalloc if bytes_req == 256’和’kmem:kmalloc if bytes_alloc == 256’(不过它们可以合并为kmem:kmalloc if bytes_alloc == 256’)。
stacktrace
每当触发事件发生时,此命令将堆栈跟踪转储到trace buffer中。
例如,下面的触发器在每次命中kmalloc跟踪点时转储堆栈跟踪::
# echo 'stacktrace' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
下面的触发器在大小为 size >= SZ_64K的kmalloc请求发生前5次时转储堆栈跟踪::
# echo 'stacktrace:5 if bytes_req >= 65536' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
The format is::
stacktrace[:count]
为了移除上面的commands::
# echo '!stacktrace' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
# echo '!stacktrace:5 if bytes_req >= 65536' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
后者也可以更简单的移除::
# echo '!stacktrace:5' > \
/sys/kernel/debug/tracing/events/kmem/kmalloc/trigger
注意:每个触发事件只能有一个堆栈跟踪触发器。
snapshot
当触发事件发生时,该命令将触发快照。
下面的命令在每次depth > 1时拨出一个块请求队列时创建一个快照。如果当时正在跟踪一组事件或函数,快照跟踪缓冲区将在触发事件发生时捕获这些事件::
# echo 'snapshot if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
仅触发一次快照::
# echo 'snapshot:1 if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
移除上面的commands::
# echo '!snapshot if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
# echo '!snapshot:1 if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
注意:每个触发事件只能有一个快照触发器。
traceon/traceoff
当特定事件触发时这些命令将会开启或者关闭tracing。参数决定了tracing系统的开启或关闭次数,如果没有指定,则无限制。
下面的命令在depth > 1首次拨出块请求时关闭tracing。如果当时正在跟踪一组事件或者函数,那么可以检查trace buffer,以查看导致触发事件的事件序列::
# echo 'traceoff:1 if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
当nr_rq > 1时总是关闭tracing::
# echo 'traceoff if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
移除上面的commands::
# echo '!traceoff:1 if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
# echo '!traceoff if nr_rq > 1' > \
/sys/kernel/debug/tracing/events/block/block_unplug/trigger
注意:每个触发事件只能有一个taceon/off。
hist
TODO
可以查看内核文档Documentation/trace/histogram.rst
TODO
博客:https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/04-ftrace_internal
文档:linux-5.12 Document/trace/ftrace.rst
和Document/trace/events.rst
。
PDF:ftrace-kernel-hooks-2014.pdf