标签 ftrace 下的文章

通过 ftrace 来了解 Linux 内核内部工作方式是一个好方法。

 title=

操作系统的内核是最难以理解的软件之一。自从你的系统启动后,它会一直在后台运行。尽管每个用户都不与内核直接交互,但他们在内核的帮助下完成自己的计算任务。与内核的交互发生在调用系统调用或者用户日常使用的各种库或应用间接调用了系统调用。

在之前的文章里我介绍了如何使用 strace 来追踪系统调用。然而,使用 strace 时你的视野是有限的。它允许你查看特定参数的系统调用。并在工作完成后,看到其返回值或状态,以表明是成功还是失败。但是你无法知道内核在这段时间内发生了什么。除了系统调用外,还有很多其他活动内核中发生,而你却视而不见。

ftrace 介绍

本文的旨在通过使用一个名为 ftrace 的机制来阐明追踪内核函数的一些情况。它使得任何 Linux 用户可以轻松地追踪内核,并且了解更多关于 Linux 内核内部如何工作。

ftrace 默认产生的输出往往是巨大的,因为内核总是忙碌的。为了节省空间,很多情况下我会通过截断来给出尽量小的输出。

我使用 Fedora 来演示下面的例子,但是它们应该在其他最新的 Linux 发行版上同样可以运行。

启用 ftrace

ftrace 现在已经是内核中的一部分了,你不再需要事先安装它了。也就是说,如果你在使用最近的 Linux 系统,那么 ftrace 是已经启用了的。为了验证 ftrace 是否可用,运行 mount 命令并查找 tracefs。如果你看到类似下面的输出,表示 ftrace 已经启用,你可以轻松地尝试本文中下面的例子。下面有些命令需要在 root 用户下使用(用 sudo 执行是不够的)。

# mount | grep tracefs
none on /sys/kernel/tracing type tracefs (rw,relatime,seclabel)

要想使用 ftrace,你首先需要进入上面 mount 命令中找到的特定目录中,在那个目录下运行文章中的其他命令。

# cd /sys/kernel/tracing

一般的工作流程

首先,你需要理解捕捉踪迹和获取输出的一般流程。如果你直接运行 ftrace,不会运行任何特定的 ftrace 命令。相反的,基本操作是通过标准 Linux 命令来写入或读取一些文件。

一般的步骤如下:

  1. 通过写入一些特定文件来启用/结束追踪
  2. 通过写入一些特定文件来设置/取消追踪时的过滤规则
  3. 从文件中读取基于第 1 和 2 步的追踪输出
  4. 从文件中清除早期输出或缓冲区
  5. 缩小到你的特定用例(你要追踪的内核函数),重复 1、2、3、4 步

可用的追踪器类型

有多种不同的追踪器可供使用。之前提到,在运行任何命令前,你需要进入一个特定的目录下,因为需要的文件在这些目录下。我在我的例子中使用了相对路径(而不是绝对路径)。

你可以查看 available_tracers 文件内容来查看所有可用的追踪器类型。你可以看下面列出了几个。不需要担心这些:

$ pwd
/sys/kernel/tracing

$ sudo cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

在所有输出的追踪器中,我会聚焦于下面三个特殊的:启用追踪的 functionfunction_graph,以及停止追踪的 nop

确认当前的追踪器

通常情况默认的追踪器设定为 nop。即在特殊文件中 current_tracer 中的 “无操作”,这意味着追踪目前是关闭的:

$ pwd
/sys/kernel/tracing

$ sudo cat current_tracer
nop

查看追踪输出

在启用任何追踪功能之前,请你看一下保存追踪输出的文件。你可以用 cat 命令查看名为 trace 的文件的内容:

# cat trace

# tracer: nop
#
# entries-in-buffer/entries-written: 0/0   #P:8
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |

启用 function 追踪器

你可以通过向 current_tracer 文件写入 function 来启用第一个追踪器 function(文件原本内容为 nop,意味着追踪是关闭的)。把这个操作看成是启用追踪的一种方式:

$ pwd
/sys/kernel/tracing

$ sudo cat current_tracer
nop
$ echo function > current_tracer
$
$ cat current_tracer
function

查看 function 追踪器的更新追踪输出

现在你已启用追踪,是时候查看输出了。如果你查看 trace 文件内容,你将会看到许多被连续写入的内容。我通过管道只展示了文件内容的前 20 行。根据左边输出的标题,你可以看到在某个 CPU 上运行的任务和进程 ID。根据右边输出的内容,你可以看到具体的内核函数和其父函数。中间显示了时间戳信息:

# sudo cat trace | head -20

# tracer: function
#
# entries-in-buffer/entries-written: 409936/4276216   #P:8
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |
          <idle>-0       [000] d...  2088.841739: tsc_verify_tsc_adjust <-arch_cpu_idle_enter
          <idle>-0       [000] d...  2088.841739: local_touch_nmi <-do_idle
          <idle>-0       [000] d...  2088.841740: rcu_nocb_flush_deferred_wakeup <-do_idle
          <idle>-0       [000] d...  2088.841740: tick_check_broadcast_expired <-do_idle
          <idle>-0       [000] d...  2088.841740: cpuidle_get_cpu_driver <-do_idle
          <idle>-0       [000] d...  2088.841740: cpuidle_not_available <-do_idle
          <idle>-0       [000] d...  2088.841741: cpuidle_select <-do_idle
          <idle>-0       [000] d...  2088.841741: menu_select <-do_idle
          <idle>-0       [000] d...  2088.841741: cpuidle_governor_latency_req <-menu_select

请记住当追踪打开后,这意味着追踪结果会被一直连续写入直至你关闭追踪。

关闭追踪

关闭追踪是简单的。你只需要在 current_tracer 文件中用 nop 替换 function 追踪器即可:

$ sudo cat current_tracer
function

$ sudo echo nop > current_tracer

$ sudo cat current_tracer
nop

启用 function\_graph 追踪器

现在尝试第二个名为 function_graph 的追踪器。你可以使用和上面相同的步骤:在 current_tracer 文件中写入 function_graph

$ sudo echo function_graph > current_tracer

$ sudo cat current_tracer
function_graph

function\_tracer 追踪器的追踪输出

注意到目前 trace 文件的输出格式已经发生变化。现在,你可以看到 CPU ID 和内核函数的执行时长。接下来,一个花括号表示一个函数的开始,以及它内部调用了哪些其他函数:

# cat trace | head -20

# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 6)               |              n_tty_write() {
 6)               |                down_read() {
 6)               |                  __cond_resched() {
 6)   0.341 us    |                    rcu_all_qs();
 6)   1.057 us    |                  }
 6)   1.807 us    |                }
 6)   0.402 us    |                process_echoes();
 6)               |                add_wait_queue() {
 6)   0.391 us    |                  _raw_spin_lock_irqsave();
 6)   0.359 us    |                  _raw_spin_unlock_irqrestore();
 6)   1.757 us    |                }
 6)   0.350 us    |                tty_hung_up_p();
 6)               |                mutex_lock() {
 6)               |                  __cond_resched() {
 6)   0.404 us    |                    rcu_all_qs();
 6)   1.067 us    |                  }

启用追踪的设置来增加追踪的深度

你可以使用下面的步骤来调整追踪器以看到更深层次的函数调用。完成之后,你可以查看 trace 文件的内容并发现输出变得更加详细了。为了文章的可读性,这个例子的输出被省略了:

# cat max_graph_depth
0

# echo 1 > max_graph_depth ## or:
# echo 2 > max_graph_depth

# sudo cat trace

查找要追踪的函数

上面的步骤足以让你开始追踪。但是它产生的输出内容是巨大的,当你想试图找到自己感兴趣的内容时,往往会很困难。通常你更希望能够只追踪特定的函数,而忽略其他函数。但如果你不知道它们确切的名称,你怎么知道要追踪哪些进程?有一个文件可以帮助你解决这个问题 —— available_filter_functions 文件提供了一个可供追踪的函数列表:

$ sudo wc -l available_filter_functions  
63165 available_filter_functions

查找一般的内核函数

现在试着搜索一个你所知道的简单内核函数。用户空间由 malloc 函数用来分配内存,而内核由 kmalloc 函数,它提供类似的功能。下面是所有与 kmalloc 相关的函数:

$ sudo grep kmalloc available_filter_functions
debug_kmalloc
mempool_kmalloc
kmalloc_slab
kmalloc_order
kmalloc_order_trace
kmalloc_fix_flags
kmalloc_large_node
__kmalloc
__kmalloc_track_caller
__kmalloc_node
__kmalloc_node_track_caller
[...]

查找内核模块或者驱动相关函数

available_filter_functions 文件的输出中,你可以看到一些以括号内文字结尾的行,例如下面的例子中的 [kvm_intel]。这些函数与当前加载的内核模块 kvm_intel 有关。你可以运行 lsmod 命令来验证:

$ sudo grep kvm available_filter_functions | tail
__pi_post_block [kvm_intel]
vmx_vcpu_pi_load [kvm_intel]
vmx_vcpu_pi_put [kvm_intel]
pi_pre_block [kvm_intel]
pi_post_block [kvm_intel]
pi_wakeup_handler [kvm_intel]
pi_has_pending_interrupt [kvm_intel]
pi_update_irte [kvm_intel]
vmx_dump_dtsel [kvm_intel]
vmx_dump_sel [kvm_intel]

$ lsmod  | grep -i kvm
kvm_intel             335872  0
kvm                   987136  1 kvm_intel
irqbypass              16384  1 kvm

仅追踪特定的函数

为了实现对特定函数或模式的追踪,你可以利用 set_ftrace_filter 文件来指定你要追踪上述输出中的哪些函数。这个文件也接受 * 模式,它可以扩展到包括具有给定模式的其他函数。作为一个例子,我在我的机器上使用 ext4 文件系统。我可以用下面的命令指定 ext4 的特定内核函数来追踪:

# mount | grep home
/dev/mapper/fedora-home on /home type ext4 (rw,relatime,seclabel)

# pwd
/sys/kernel/tracing

# cat set_ftrace_filter

#### all functions enabled ####
$
$ echo ext4_* > set_ftrace_filter
$
$ cat set_ftrace_filter
ext4_has_free_clusters
ext4_validate_block_bitmap
ext4_get_group_number
ext4_get_group_no_and_offset
ext4_get_group_desc
[...]

现在当你可以看到追踪输出时,你只能看到与内核函数有关的 ext4 函数,而你之前已经为其设置了一个过滤器。所有其他的输出都被忽略了:

# cat trace |head -20

## tracer: function
#
# entries-in-buffer/entries-written: 3871/3871   #P:8
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |
           cupsd-1066    [004] ....  3308.989545: ext4_file_getattr <-vfs_fstat
           cupsd-1066    [004] ....  3308.989547: ext4_getattr <-ext4_file_getattr
           cupsd-1066    [004] ....  3308.989552: ext4_file_getattr <-vfs_fstat
           cupsd-1066    [004] ....  3308.989553: ext4_getattr <-ext4_file_getattr
           cupsd-1066    [004] ....  3308.990097: ext4_file_open <-do_dentry_open
           cupsd-1066    [004] ....  3308.990111: ext4_file_getattr <-vfs_fstat
           cupsd-1066    [004] ....  3308.990111: ext4_getattr <-ext4_file_getattr
           cupsd-1066    [004] ....  3308.990122: ext4_llseek <-ksys_lseek
           cupsd-1066    [004] ....  3308.990130: ext4_file_read_iter <-new_sync_read

排除要被追踪的函数

你并不总是知道你想追踪什么,但是,你肯定知道你不想追踪什么。因此,有一个 set_ftrace_notrace —— 请注意其中的 “no”。你可以在这个文件中写下你想要的模式,并启用追踪。这样除了所提到的模式外,任何其他东西都会被追踪到。这通常有助于删除那些使我们的输出变得混乱的普通功能:

$ sudo cat set_ftrace_notrace
#### no functions disabled ####

具有目标性的追踪

到目前为止,你一直在追踪内核中发生的一切。但是,它无法帮助你追踪与某个特定命令有关的事件。为了达到这个目的,你可以按需打开和关闭跟踪,并且在它们之间,运行我们选择的命令,这样你就不会在跟踪输出中得到额外的输出。你可以通过向 tracing_on 写入 1 来启用跟踪,写 0 来关闭跟踪。

# cat tracing_on
0

# echo 1 > tracing_on

# cat tracing_on
1

### Run some specific command that we wish to trace here ###

# echo 0 > tracing_on

# cat tracing_on
0

追踪特定的 PID

如果你想追踪与正在运行的特定进程有关的活动,你可以将该 PID 写入一个名为 set_ftrace_pid 的文件,然后启用追踪。这样一来,追踪就只限于这个 PID,这在某些情况下是非常有帮助的。

$ sudo echo $PID > set_ftrace_pid

总结

ftrace 是一个了解 Linux 内核内部工作的很好方式。通过一些练习,你可以学会对 ftrace 进行调整以缩小搜索范围。要想更详细地了解 ftrace 和它的高级用法,请看 ftrace 的核心作者 Steven Rostedt 写的这些优秀文章。


via: https://opensource.com/article/21/7/linux-kernel-ftrace

作者:Gaurav Kamathe 选题:lujun9972 译者:萌新阿岩 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在内核层面上分析事件有很多的工具:SystemTapktapSysdigLTTNG 等等,你也可以在网络上找到关于这些工具的大量介绍文章和资料。

而对于使用 Linux 原生机制去跟踪系统事件以及检索/分析故障信息的方面的资料却很少找的到。这就是 ftrace,它是添加到内核中的第一款跟踪工具,今天我们来看一下它都能做什么,让我们从它的一些重要术语开始吧。

内核跟踪和分析

内核分析 Kernel profiling 可以发现性能“瓶颈”。分析能够帮我们发现在一个程序中性能损失的准确位置。特定的程序生成一个 概述 profile — 这是一个事件总结 — 它能够用于帮我们找出哪个函数占用了大量的运行时间。尽管这些程序并不能识别出为什么会损失性能。

瓶颈经常发生在无法通过分析来识别的情况下。要推断出为什么会发生事件,就必须保存发生事件时的相关上下文,这就需要去 跟踪 tracing

跟踪可以理解为在一个正常工作的系统上活动的信息收集过程。它使用特定的工具来完成这项工作,就像录音机来记录声音一样,用它来记录各种系统事件。

跟踪程序能够同时跟踪应用级和操作系统级的事件。它们收集的信息能够用于诊断多种系统问题。

有时候会将跟踪与日志比较。它们两者确时很相似,但是也有不同的地方。

对于跟踪,记录的信息都是些低级别事件。它们的数量是成百上千的,甚至是成千上万的。对于日志,记录的信息都是些高级别事件,数量上通常少多了。这些包含用户登录系统、应用程序错误、数据库事务等等。

就像日志一样,跟踪数据可以被原样读取,但是用特定的应用程序提取的信息更有用。所有的跟踪程序都能这样做。

在内核跟踪和分析方面,Linux 内核有三个主要的机制:

  • 跟踪点 tracepoint :一种基于静态测试代码的工作机制
  • 探针 kprobe :一种动态跟踪机制,用于在任意时刻中断内核代码的运行,调用它自己的处理程序,在完成需要的操作之后再返回
  • perf\_events —— 一个访问 PMU( 性能监视单元 Performance Monitoring Unit )的接口

我并不想在这里写关于这些机制方面的内容,任何对它们感兴趣的人可以去访问 Brendan Gregg 的博客

使用 ftrace,我们可以与这些机制进行交互,并可以从用户空间直接得到调试信息。下面我们将讨论这方面的详细内容。示例中的所有命令行都是在内核版本为 3.13.0-24 的 Ubuntu 14.04 中运行的。

ftrace:常用信息

ftrace 是 Function Trace 的简写,但它能做的远不止这些:它可以跟踪上下文切换、测量进程阻塞时间、计算高优先级任务的活动时间等等。

ftrace 是由 Steven Rostedt 开发的,从 2008 年发布的内核 2.6.27 中开始就内置了。这是为记录数据提供的一个调试 Ring 缓冲区的框架。这些数据由集成到内核中的跟踪程序来采集。

ftrace 工作在 debugfs 文件系统上,在大多数现代 Linux 发行版中都默认挂载了。要开始使用 ftrace,你将进入到 sys/kernel/debug/tracing 目录(仅对 root 用户可用):

# cd /sys/kernel/debug/tracing

这个目录的内容看起来应该像这样:

аvailable_filter_functions  options            stack_trace_filter
available_tracers           per_cpu             trace
buffer_size_kb              printk_formats      trace_clock
buffer_total_size_kb        README              trace_marker
current_tracer              saved_cmdlines      trace_options
dyn_ftrace_total_info       set_event           trace_pipe
enabled_functions           set_ftrace_filter   trace_stat
events                      set_ftrace_notrace  tracing_cpumask
free_buffer                 set_ftrace_pid      tracing_max_latency
function_profile_enabled    set_graph_function  tracing_on
instances                   set_graph_notrace   tracing_thresh
kprobe_events               snapshot            uprobe_events
kprobe_profile              stack_max_size      uprobe_profile

我不想去描述这些文件和子目录;它们的描述在 官方文档 中已经写的很详细了。我只想去详细介绍与我们这篇文章相关的这几个文件:

  • available\_tracers —— 可用的跟踪程序
  • current\_tracer —— 正在运行的跟踪程序
  • tracing\_on —— 负责启用或禁用数据写入到 Ring 缓冲区的系统文件(如果启用它,数字 1 被添加到文件中,禁用它,数字 0 被添加)
  • trace —— 以人类友好格式保存跟踪数据的文件

可用的跟踪程序

我们可以使用如下的命令去查看可用的跟踪程序的一个列表:

root@andrei:/sys/kernel/debug/tracing#: cat available_tracers
blk mmiotrace function_graph wakeup_rt wakeup function nop

我们来快速浏览一下每个跟踪程序的特性:

  • function —— 一个无需参数的函数调用跟踪程序
  • function\_graph —— 一个使用子调用的函数调用跟踪程序
  • blk —— 一个与块 I/O 跟踪相关的调用和事件跟踪程序(它是 blktrace 使用的)
  • mmiotrace —— 一个内存映射 I/O 操作跟踪程序
  • nop —— 最简单的跟踪程序,就像它的名字所暗示的那样,它不做任何事情(尽管在某些情况下可能会派上用场,我们将在后文中详细解释)

函数跟踪程序

在开始介绍函数跟踪程序 ftrace 之前,我们先看一个测试脚本:

#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

这个脚本是非常简单的,但是还有几个需要注意的地方。命令 sysctl ftrace.enabled=1 启用了函数跟踪程序。然后我们通过写它的名字到 current_tracer 文件来启用 current tracer

接下来,我们写入一个 1tracing_on,它启用了 Ring 缓冲区。这些语法都要求在 1> 符号前后有一个空格;写成像 echo 1> tracing_on 这样将不能工作。一行之后我们禁用它(如果 0 写入到 tracing_on, 缓冲区不会被清除并且 ftrace 并不会被禁用)。

我们为什么这样做呢?在两个 echo 命令之间,我们看到了命令 sleep 1。我们启用了缓冲区,运行了这个命令,然后禁用它。这将使跟踪程序采集了这个命令运行期间发生的所有系统调用的信息。

在脚本的最后一行,我们写了一个在控制台上显示跟踪数据的命令。

一旦脚本运行完成后,我们将看到下列的输出(这里只列出了一个小片断):

# tracer: function
#
# entries-in-buffer/entries-written: 29571/29571   #P:2
#
#                           _-----=> irqs-off
#                           / _----=> need-resched
#                           | / _---=> hardirq/softirq
#                           || / _--=> preempt-depth
#                           ||| /   delay
#           TASK-PID   CPU#  ||||   TIMESTAMP  FUNCTION
#           | |     |   ||||    |       |
        trace.sh-1295  [000] ....   90.502874: mutex_unlock <-rb_simple_write
        trace.sh-1295  [000] ....   90.502875: __fsnotify_parent <-vfs_write
        trace.sh-1295  [000] ....   90.502876: fsnotify <-vfs_write
        trace.sh-1295  [000] ....   90.502876: __srcu_read_lock <-fsnotify
        trace.sh-1295  [000] ....   90.502876: __srcu_read_unlock <-fsnotify
        trace.sh-1295  [000] ....   90.502877: __sb_end_write <-vfs_write
        trace.sh-1295  [000] ....   90.502877: syscall_trace_leave <-int_check_syscall_exit_work
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_exit <-syscall_trace_leave
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_enter <-syscall_trace_leave
        trace.sh-1295  [000] d...   90.502878: vtime_user_enter <-context_tracking_user_enter
        trace.sh-1295  [000] d...   90.502878: _raw_spin_lock <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: __vtime_account_system <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: get_vtime_delta <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: account_system_time <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: cpuacct_account_field <-account_system_time
        trace.sh-1295  [000] d...   90.502879: acct_account_cputime <-account_system_time
        trace.sh-1295  [000] d...   90.502879: __acct_update_integrals <-acct_account_cputime

这个输出以“缓冲区中的信息条目数量”和“写入的全部条目数量”开始。这两者的数据差异是缓冲区中事件的丢失数量(在我们的示例中没有发生丢失)。

在这里有一个包含下列信息的函数列表:

  • 进程标识符(PID)
  • 运行这个进程的 CPU(CPU#)
  • 进程开始时间(TIMESTAMP)
  • 被跟踪函数的名字以及调用它的父级函数;例如,在我们输出的第一行,rb_simple_write 调用了 mutex-unlock 函数。

function\_graph 跟踪程序

function\_graph 跟踪程序的工作和函数跟踪程序一样,但是它更详细:它显示了每个函数的进入和退出点。使用这个跟踪程序,我们可以跟踪函数的子调用并且测量每个函数的运行时间。

我们来编辑一下最后一个示例的脚本:

#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function_graph > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

运行这个脚本之后,我们将得到如下的输出:

# tracer: function_graph
#
# CPU  DURATION                 FUNCTION CALLS
# |     |   |                   |   |   |   |
 0)   0.120 us  |                               } /* resched_task */
 0)   1.877 us  |                               } /* check_preempt_curr */
 0)   4.264 us  |                           } /* ttwu_do_wakeup */
 0) + 29.053 us   |                         } /* ttwu_do_activate.constprop.74 */
 0)   0.091 us  |                           _raw_spin_unlock();
 0)   0.260 us  |                           ttwu_stat();
 0)   0.133 us  |                           _raw_spin_unlock_irqrestore();
 0) + 37.785 us   |                         } /* try_to_wake_up */
 0) + 38.478 us   |                     } /* default_wake_function */
 0) + 39.203 us   |                     } /* pollwake */
 0) + 40.793 us   |                 } /* __wake_up_common */
 0)   0.104 us  |                   _raw_spin_unlock_irqrestore();
 0) + 42.920 us   |                 } /* __wake_up_sync_key */
 0) + 44.160 us   |             } /* sock_def_readable */
 0) ! 192.850 us  |             } /* tcp_rcv_established */
 0) ! 197.445 us  |         } /* tcp_v4_do_rcv */
 0)   0.113 us  |           _raw_spin_unlock();
 0) ! 205.655 us  |         } /* tcp_v4_rcv */
 0) ! 208.154 us  |     } /* ip_local_deliver_finish */

在这个图中,DURATION 展示了花费在每个运行的函数上的时间。注意使用 +! 符号标记的地方。加号(+)意思是这个函数花费的时间超过 10 毫秒;而感叹号(!)意思是这个函数花费的时间超过了 100 毫秒。

FUNCTION_CALLS 下面,我们可以看到每个函数调用的信息。

和 C 语言一样使用了花括号({)标记每个函数的边界,它展示了每个函数的开始和结束,一个用于开始,一个用于结束;不能调用其它任何函数的叶子函数用一个分号(;)标记。

函数过滤器

ftrace 输出可能会很大,精确找出你所需要的内容可能会非常困难。我们可以使用过滤器去简化我们的搜索:输出中将只显示与我们感兴趣的函数相关的信息。为实现过滤,我们只需要在 set_ftrace_filter 文件中写入我们需要过滤的函数的名字即可。例如:

root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_filter

如果禁用过滤器,我们只需要在这个文件中添加一个空白行即可:

root@andrei:/sys/kernel/debug/tracing# echo  > set_ftrace_filter

通过运行这个命令:

root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_notrace 

我们将得到相反的结果:输出将包含除了 kfree() 以外的任何函数的信息。

另一个有用的选项是 set_ftrace_pid。它是为在一个特定的进程运行期间调用跟踪函数准备的。

ftrace 还有很多过滤选项。对于它们更详细的介绍,你可以去查看 Steven Rostedt 在 LWN.net 上的文章。

跟踪事件

我们在上面提到到跟踪点机制。跟踪点是插入的触发系统事件的特定代码。跟踪点可以是动态的(意味着可能会在它们上面附加几个检查),也可以是静态的(意味着不会附加任何检查)。

静态跟踪点不会对系统有任何影响;它们只是在测试的函数末尾增加几个字节的函数调用以及在一个独立的节上增加一个数据结构。

当相关代码片断运行时,动态跟踪点调用一个跟踪函数。跟踪数据是写入到 Ring 缓冲区。

跟踪点可以设置在代码的任何位置;事实上,它们确实可以在许多的内核函数中找到。我们来看一下 kmem_cache_alloc 函数(取自 这里):

{
    void *ret = slab_alloc(cachep, flags, _RET_IP_);

    trace_kmem_cache_alloc(_RET_IP_, ret,
                            cachep->object_size, cachep->size, flags);
         return ret;
         }

trace_kmem_cache_alloc 它本身就是一个跟踪点。我们可以通过查看其它内核函数的源代码找到这样无数的例子。

在 Linux 内核中为了从用户空间使用跟踪点,它有一个专门的 API。在 /sys/kernel/debug/tracing 目录中,这里有一个事件目录,它是为了保存系统事件。这些只是为了跟踪系统事件。在这个上下文中系统事件可以理解为包含在内核中的跟踪点。

可以通过运行如下的命令来查看这个事件列表:

root@andrei:/sys/kernel/debug/tracing# cat available_events

这个命令将在控制台中输出一个很长的列表。这样看起来很不方便。我们可以使用如下的命令来列出一个结构化的列表:

root@andrei:/sys/kernel/debug/tracing# ls events

block           gpio        mce     random      skb     vsyscall
btrfs           header_event  migrate  ras          sock    workqueue
compaction      header_page   module   raw_syscalls  spi    writeback
context_tracking  iommu         napi    rcu         swiotlb   xen
enable          irq         net     regmap      syscalls  xfs
exceptions      irq_vectors   nmi   regulator   task    xhci-hcd
ext4            jbd2        oom     rpm         timer
filemap         kmem        pagemap  sched      udp
fs              kvm         power   scsi        vfs
ftrace          kvmmmu      printk   signal     vmscan

所有可能的事件都按子系统分组到子目录中。在我们开始跟踪事件之前,我们要先确保启用了 Ring 缓冲区写入:

root@andrei:/sys/kernel/debug/tracing# cat tracing_on

如果在控制台中显示的是数字 0,那么,我们可以运行如下的命令来启用它:

root@andrei:/sys/kernel/debug/tracing# echo 1 > tracing_on

在我们上一篇的文章中,我们写了关于 chroot() 系统调用的内容;我们来跟踪访问一下这个系统调用。对于我们的跟踪程序,我们使用 nop 因为函数跟踪程序和 function_graph 跟踪程序记录的信息太多,它包含了我们不感兴趣的事件信息。

root@andrei:/sys/kernel/debug/tracing# echo nop > current_tracer

所有事件相关的系统调用都保存在系统调用目录下。在这里我们将找到一个进入和退出各种系统调用的目录。我们需要在相关的文件中通过写入数字 1 来激活跟踪点:

root@andrei:/sys/kernel/debug/tracing# echo 1 > events/syscalls/sys_enter_chroot/enable

然后我们使用 chroot 来创建一个独立的文件系统(更多内容,请查看 之前这篇文章)。在我们执行完我们需要的命令之后,我们将禁用跟踪程序,以便于不需要的信息或者过量信息不会出现在输出中:

root@andrei:/sys/kernel/debug/tracing# echo 0 > tracing_on

然后,我们去查看 Ring 缓冲区的内容。在输出的结束部分,我们找到了有关的系统调用信息(这里只是一个节选)。

root@andrei:/sys/kernel/debug/tracing# сat trace

......
          chroot-11321 [000] ....  4606.265208: sys_chroot(filename: 7fff785ae8c2)
          chroot-11325 [000] ....  4691.677767: sys_chroot(filename: 7fff242308cc)
            bash-11338 [000] ....  4746.971300: sys_chroot(filename: 7fff1efca8cc)
            bash-11351 [000] ....  5379.020609: sys_chroot(filename: 7fffbf9918cc)

关于配置事件跟踪的更的信息可以在 这里 找到。

结束语

在这篇文篇中,我们做了一个 ftrace 的功能概述。我们非常感谢你的任何意见或者补充。如果你想深入研究这个主题,我们为你推荐下列的资源:


via:https://blog.selectel.com/kernel-tracing-ftrace/

作者:Andrej Yemelianov 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 跟踪很神奇!

跟踪器 tracer 是一个高级的性能分析和调试工具,如果你使用过 strace(1) 或者 tcpdump(8),你不应该被它吓到 ... 你使用的就是跟踪器。系统跟踪器能让你看到很多的东西,而不仅是系统调用或者数据包,因为常见的跟踪器都可以跟踪内核或者应用程序的任何东西。

有大量的 Linux 跟踪器可供你选择。由于它们中的每个都有一个官方的(或者非官方的)的吉祥物,我们有足够多的选择给孩子们展示。

你喜欢使用哪一个呢?

我从两类读者的角度来回答这个问题:大多数人和性能/内核工程师。当然,随着时间的推移,这也可能会发生变化,因此,我需要及时去更新本文内容,或许是每年一次,或者更频繁。(LCTT 译注:本文最后更新于 2015 年)

对于大多数人

大多数人(开发者、系统管理员、运维人员、网络可靠性工程师(SRE)…)是不需要去学习系统跟踪器的底层细节的。以下是你需要去了解和做的事情:

1. 使用 perf\_events 进行 CPU 剖析

可以使用 perf\_events 进行 CPU 剖析 profiling 。它可以用一个 火焰图 来形象地表示。比如:

git clone --depth 1 https://github.com/brendangregg/FlameGraph
perf record -F 99 -a -g -- sleep 30
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > perf.svg

Linux 的 perf\_events(即 perf,后者是它的命令)是官方为 Linux 用户准备的跟踪器/分析器。它位于内核源码中,并且维护的非常好(而且现在它的功能还在快速变强)。它一般是通过 linux-tools-common 这个包来添加的。

perf 可以做的事情很多,但是,如果我只能建议你学习其中的一个功能的话,那就是 CPU 剖析。虽然从技术角度来说,这并不是事件“跟踪”,而是 采样 sampling 。最难的部分是获得完整的栈和符号,这部分在我的 Linux Profiling at Netflix 中针对 Java 和 Node.js 讨论过。

2. 知道它能干什么

正如一位朋友所说的:“你不需要知道 X 光机是如何工作的,但你需要明白的是,如果你吞下了一个硬币,X 光机是你的一个选择!”你需要知道使用跟踪器能够做什么,因此,如果你在业务上确实需要它,你可以以后再去学习它,或者请会使用它的人来做。

简单地说:几乎任何事情都可以通过跟踪来了解它。内部文件系统、TCP/IP 处理过程、设备驱动、应用程序内部情况。阅读我在 lwn.net 上的 ftrace 的文章,也可以去浏览 perf\_events 页面,那里有一些跟踪(和剖析)能力的示例。

3. 需要一个前端工具

如果你要购买一个性能分析工具(有许多公司销售这类产品),并要求支持 Linux 跟踪。想要一个直观的“点击”界面去探查内核的内部,以及包含一个在不同堆栈位置的延迟热力图。就像我在 Monitorama 演讲 中描述的那样。

我创建并开源了我自己的一些前端工具,虽然它是基于 CLI 的(不是图形界面的)。这样可以使其它人使用跟踪器更快更容易。比如,我的 perf-tools,跟踪新进程是这样的:

# ./execsnoop
Tracing exec()s. Ctrl-C to end.
 PID PPID ARGS
 22898 22004 man ls
 22905 22898 preconv -e UTF-8
 22908 22898 pager -s
 22907 22898 nroff -mandoc -rLL=164n -rLT=164n -Tutf8
[...]

在 Netflix 公司,我正在开发 Vector,它是一个实例分析工具,实际上它也是一个 Linux 跟踪器的前端。

对于性能或者内核工程师

一般来说,我们的工作都非常难,因为大多数人或许要求我们去搞清楚如何去跟踪某个事件,以及因此需要选择使用哪个跟踪器。为完全理解一个跟踪器,你通常需要花至少一百多个小时去使用它。理解所有的 Linux 跟踪器并能在它们之间做出正确的选择是件很难的事情。(我或许是唯一接近完成这件事的人)

在这里我建议选择如下,要么:

A)选择一个全能的跟踪器,并以它为标准。这需要在一个测试环境中花大量的时间来搞清楚它的细微差别和安全性。我现在的建议是 SystemTap 的最新版本(例如,从 源代码 构建)。我知道有的公司选择的是 LTTng ,尽管它并不是很强大(但是它很安全),但他们也用的很好。如果在 sysdig 中添加了跟踪点或者是 kprobes,它也是另外的一个候选者。

B)按我的 Velocity 教程中 的流程图。这意味着尽可能使用 ftrace 或者 perf\_events,eBPF 已经集成到内核中了,然后用其它的跟踪器,如 SystemTap/LTTng 作为对 eBPF 的补充。我目前在 Netflix 的工作中就是这么做的。

以下是我对各个跟踪器的评价:

1. ftrace

我爱 ftrace,它是内核黑客最好的朋友。它被构建进内核中,它能够利用跟踪点、kprobes、以及 uprobes,以提供一些功能:使用可选的过滤器和参数进行事件跟踪;事件计数和计时,内核概览; 函数流步进 function-flow walking 。关于它的示例可以查看内核源代码树中的 ftrace.txt。它通过 /sys 来管理,是面向单一的 root 用户的(虽然你可以使用缓冲实例以让其支持多用户),它的界面有时很繁琐,但是它比较容易 调校 hackable ,并且有个前端:ftrace 的主要创建者 Steven Rostedt 设计了一个 trace-cmd,而且我也创建了 perf-tools 集合。我最诟病的就是它不是 可编程的 programmable ,因此,举个例子说,你不能保存和获取时间戳、计算延迟,以及将其保存为直方图。你需要转储事件到用户级以便于进行后期处理,这需要花费一些成本。它也许可以通过 eBPF 实现可编程。

2. perf\_events

perf\_events 是 Linux 用户的主要跟踪工具,它的源代码位于 Linux 内核中,一般是通过 linux-tools-common 包来添加的。它又称为 perf,后者指的是它的前端,它相当高效(动态缓存),一般用于跟踪并转储到一个文件中(perf.data),然后可以在之后进行后期处理。它可以做大部分 ftrace 能做的事情。它不能进行函数流步进,并且不太容易调校(而它的安全/错误检查做的更好一些)。但它可以做剖析(采样)、CPU 性能计数、用户级的栈转换、以及使用本地变量利用 调试信息 debuginfo 进行 行级跟踪 line tracing 。它也支持多个并发用户。与 ftrace 一样,它也不是内核可编程的,除非 eBPF 支持(补丁已经在计划中)。如果只学习一个跟踪器,我建议大家去学习 perf,它可以解决大量的问题,并且它也相当安全。

3. eBPF

扩展的伯克利包过滤器 extended Berkeley Packet Filter (eBPF)是一个 内核内 in-kernel 的虚拟机,可以在事件上运行程序,它非常高效(JIT)。它可能最终为 ftrace 和 perf\_events 提供 内核内编程 in-kernel programming ,并可以去增强其它跟踪器。它现在是由 Alexei Starovoitov 开发的,还没有实现完全的整合,但是对于一些令人印象深刻的工具,有些内核版本(比如,4.1)已经支持了:比如,块设备 I/O 的 延迟热力图 latency heat map 。更多参考资料,请查阅 Alexei 的 BPF 演示,和它的 eBPF 示例

4. SystemTap

SystemTap 是一个非常强大的跟踪器。它可以做任何事情:剖析、跟踪点、kprobes、uprobes(它就来自 SystemTap)、USDT、内核内编程等等。它将程序编译成内核模块并加载它们 —— 这是一种很难保证安全的方法。它开发是在内核代码树之外进行的,并且在过去出现过很多问题(内核崩溃或冻结)。许多并不是 SystemTap 的过错 —— 它通常是首次对内核使用某些跟踪功能,并率先遇到 bug。最新版本的 SystemTap 是非常好的(你需要从它的源代码编译),但是,许多人仍然没有从早期版本的问题阴影中走出来。如果你想去使用它,花一些时间去测试环境,然后,在 irc.freenode.net 的 #systemtap 频道与开发者进行讨论。(Netflix 有一个容错架构,我们使用了 SystemTap,但是我们或许比起你来说,更少担心它的安全性)我最诟病的事情是,它似乎假设你有办法得到内核调试信息,而我并没有这些信息。没有它我实际上可以做很多事情,但是缺少相关的文档和示例(我现在自己开始帮着做这些了)。

5. LTTng

LTTng 对事件收集进行了优化,性能要好于其它的跟踪器,也支持许多的事件类型,包括 USDT。它的开发是在内核代码树之外进行的。它的核心部分非常简单:通过一个很小的固定指令集写入事件到跟踪缓冲区。这样让它既安全又快速。缺点是做内核内编程不太容易。我觉得那不是个大问题,由于它优化的很好,可以充分的扩展,尽管需要后期处理。它也探索了一种不同的分析技术。很多的“黑匣子”记录了所有感兴趣的事件,以便可以在 GUI 中以后分析它。我担心该记录会错失之前没有预料的事件,我真的需要花一些时间去看看它在实践中是如何工作的。这个跟踪器上我花的时间最少(没有特别的原因)。

6. ktap

ktap 是一个很有前途的跟踪器,它在内核中使用了一个 lua 虚拟机,不需要调试信息和在嵌入时设备上可以工作的很好。这使得它进入了人们的视野,在某个时候似乎要成为 Linux 上最好的跟踪器。然而,由于 eBPF 开始集成到了内核,而 ktap 的集成工作被推迟了,直到它能够使用 eBPF 而不是它自己的虚拟机。由于 eBPF 在几个月过去之后仍然在集成过程中,ktap 的开发者已经等待了很长的时间。我希望在今年的晚些时间它能够重启开发。

7. dtrace4linux

dtrace4linux 主要由一个人(Paul Fox)利用业务时间将 Sun DTrace 移植到 Linux 中的。它令人印象深刻,一些 供应器 provider 可以工作,还不是很完美,它最多应该算是实验性的工具(不安全)。我认为对于许可证的担心,使人们对它保持谨慎:它可能永远也进入不了 Linux 内核,因为 Sun 是基于 CDDL 许可证发布的 DTrace;Paul 的方法是将它作为一个插件。我非常希望看到 Linux 上的 DTrace,并且希望这个项目能够完成,我想我加入 Netflix 时将花一些时间来帮它完成。但是,我一直在使用内置的跟踪器 ftrace 和 perf\_events。

8. OL DTrace

Oracle Linux DTrace 是将 DTrace 移植到 Linux (尤其是 Oracle Linux)的重大努力。过去这些年的许多发布版本都一直稳定的进步,开发者甚至谈到了改善 DTrace 测试套件,这显示出这个项目很有前途。许多有用的功能已经完成:系统调用、剖析、sdt、proc、sched、以及 USDT。我一直在等待着 fbt(函数边界跟踪,对内核的动态跟踪),它将成为 Linux 内核上非常强大的功能。它最终能否成功取决于能否吸引足够多的人去使用 Oracle Linux(并为支持付费)。另一个羁绊是它并非完全开源的:内核组件是开源的,但用户级代码我没有看到。

9. sysdig

sysdig 是一个很新的跟踪器,它可以使用类似 tcpdump 的语法来处理 系统调用 syscall 事件,并用 lua 做后期处理。它也是令人印象深刻的,并且很高兴能看到在系统跟踪领域的创新。它的局限性是,它的系统调用只能是在当时,并且,它转储所有事件到用户级进行后期处理。你可以使用系统调用来做许多事情,虽然我希望能看到它去支持跟踪点、kprobes、以及 uprobes。我也希望看到它支持 eBPF 以查看内核内概览。sysdig 的开发者现在正在增加对容器的支持。可以关注它的进一步发展。

深入阅读

我自己的工作中使用到的跟踪器包括:

不好意思,没有更多的跟踪器了! … 如果你想知道为什么 Linux 中的跟踪器不止一个,或者关于 DTrace 的内容,在我的 从 DTrace 到 Linux 的演讲中有答案,从 第 28 张幻灯片 开始。

感谢 Deirdre Straughan 的编辑,以及跟踪小马的创建(General Zoi 是小马的创建者)。


via: http://www.brendangregg.com/blog/2015-07-08/choosing-a-linux-tracer.html

作者:Brendan Gregg 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大家好!今天我们将去讨论一个调试工具:ftrace,之前我的博客上还没有讨论过它。还有什么能比一个新的调试工具更让人激动呢?

这个非常棒的 ftrace 并不是个新的工具!它大约在 Linux 的 2.6 内核版本中就有了,时间大约是在 2008 年。这一篇是我用谷歌能找到的最早的文档。因此,如果你是一个调试系统的“老手”,可能早就已经使用它了!

我知道,ftrace 已经存在了大约 2.5 年了(LCTT 译注:距本文初次写作时),但是还没有真正的去学习它。假设我明天要召开一个专题研究会,那么,关于 ftrace 应该讨论些什么?因此,今天是时间去讨论一下它了!

什么是 ftrace?

ftrace 是一个 Linux 内核特性,它可以让你去跟踪 Linux 内核的函数调用。为什么要这么做呢?好吧,假设你调试一个奇怪的问题,而你已经得到了你的内核版本中这个问题在源代码中的开始的位置,而你想知道这里到底发生了什么?

每次在调试的时候,我并不会经常去读内核源代码,但是,极个别的情况下会去读它!例如,本周在工作中,我有一个程序在内核中卡死了。查看到底是调用了什么函数,能够帮我更好的理解在内核中发生了什么,哪些系统涉及其中!(在我的那个案例中,它是虚拟内存系统)。

我认为 ftrace 是一个十分好用的工具(它肯定没有 strace 那样使用广泛,也比它难以使用),但是它还是值得你去学习。因此,让我们开始吧!

使用 ftrace 的第一步

不像 straceperf,ftrace 并不是真正的 程序 – 你不能只运行 ftrace my_cool_function。那样太容易了!

如果你去读 使用 ftrace 调试内核,它会告诉你从 cd /sys/kernel/debug/tracing 开始,然后做很多文件系统的操作。

对于我来说,这种办法太麻烦——一个使用 ftrace 的简单例子像是这样:

cd /sys/kernel/debug/tracing
echo function > current_tracer
echo do_page_fault > set_ftrace_filter
cat trace

这个文件系统是跟踪系统的接口(“给这些神奇的文件赋值,然后该发生的事情就会发生”)理论上看起来似乎可用,但是它不是我的首选方式。

幸运的是,ftrace 团队也考虑到这个并不友好的用户界面,因此,它有了一个更易于使用的界面,它就是 trace-cmd!!!trace-cmd 是一个带命令行参数的普通程序。我们后面将使用它!我在 LWN 上找到了一个 trace-cmd 的使用介绍:trace-cmd: Ftrace 的一个前端

开始使用 trace-cmd:让我们仅跟踪一个函数

首先,我需要去使用 sudo apt-get install trace-cmd 安装 trace-cmd,这一步很容易。

对于第一个 ftrace 的演示,我决定去了解我的内核如何去处理一个页面故障。当 Linux 分配内存时,它经常偷懒,(“你并不是真的计划去使用内存,对吗?”)。这意味着,当一个应用程序尝试去对分配给它的内存进行写入时,就会发生一个页面故障,而这个时候,内核才会真正的为应用程序去分配物理内存。

我们开始使用 trace-cmd 并让它跟踪 do_page_fault 函数!

$ sudo trace-cmd record -p function -l do_page_fault
  plugin 'function'
Hit Ctrl^C to stop recording

我将它运行了几秒钟,然后按下了 Ctrl+C。 让我大吃一惊的是,它竟然产生了一个 2.5MB 大小的名为 trace.dat 的跟踪文件。我们来看一下这个文件的内容!

$ sudo trace-cmd report
          chrome-15144 [000] 11446.466121: function:             do_page_fault
          chrome-15144 [000] 11446.467910: function:             do_page_fault
          chrome-15144 [000] 11446.469174: function:             do_page_fault
          chrome-15144 [000] 11446.474225: function:             do_page_fault
          chrome-15144 [000] 11446.474386: function:             do_page_fault
          chrome-15144 [000] 11446.478768: function:             do_page_fault
 CompositorTileW-15154 [001] 11446.480172: function:             do_page_fault
          chrome-1830  [003] 11446.486696: function:             do_page_fault
 CompositorTileW-15154 [001] 11446.488983: function:             do_page_fault
 CompositorTileW-15154 [001] 11446.489034: function:             do_page_fault
 CompositorTileW-15154 [001] 11446.489045: function:             do_page_fault

看起来很整洁 – 它展示了进程名(chrome)、进程 ID(15144)、CPU ID(000),以及它跟踪的函数。

通过察看整个文件,(sudo trace-cmd report | grep chrome)可以看到,我们跟踪了大约 1.5 秒,在这 1.5 秒的时间段内,Chrome 发生了大约 500 个页面故障。真是太酷了!这就是我们做的第一个 ftrace!

下一个 ftrace 技巧:我们来跟踪一个进程!

好吧,只看一个函数是有点无聊!假如我想知道一个程序中都发生了什么事情。我使用一个名为 Hugo 的静态站点生成器。看看内核为 Hugo 都做了些什么事情?

在我的电脑上 Hugo 的 PID 现在是 25314,因此,我使用如下的命令去记录所有的内核函数:

sudo trace-cmd record --help # I read the help!
sudo trace-cmd record -p function -P 25314 # record for PID 25314

sudo trace-cmd report 输出了 18,000 行。如果你对这些感兴趣,你可以看 这里是所有的 18,000 行的输出

18,000 行太多了,因此,在这里仅摘录其中几行。

当系统调用 clock_gettime 运行的时候,都发生了什么:

 compat_SyS_clock_gettime
    SyS_clock_gettime
       clockid_to_kclock
       posix_clock_realtime_get
          getnstimeofday64
             __getnstimeofday64
                arch_counter_read
    __compat_put_timespec

这是与进程调试相关的一些东西:

 cpufreq_sched_irq_work
    wake_up_process
       try_to_wake_up
          _raw_spin_lock_irqsave
             do_raw_spin_lock
          _raw_spin_lock
             do_raw_spin_lock
          walt_ktime_clock
             ktime_get
                arch_counter_read
          walt_update_task_ravg
             exiting_task

虽然你可能还不理解它们是做什么的,但是,能够看到所有的这些函数调用也是件很酷的事情。

“function graph” 跟踪

这里有另外一个模式,称为 function_graph。除了它既可以进入也可以退出一个函数外,其它的功能和函数跟踪器是一样的。这里是那个跟踪器的输出

sudo trace-cmd record -p function_graph -P 25314

同样,这里只是一个片断(这次来自 futex 代码):

             |      futex_wake() {
             |        get_futex_key() {
             |          get_user_pages_fast() {
  1.458 us   |            __get_user_pages_fast();
  4.375 us   |          }
             |          __might_sleep() {
  0.292 us   |            ___might_sleep();
  2.333 us   |          }
  0.584 us   |          get_futex_key_refs();
             |          unlock_page() {
  0.291 us   |            page_waitqueue();
  0.583 us   |            __wake_up_bit();
  5.250 us   |          }
  0.583 us   |          put_page();
+ 24.208 us  |        }

我们看到在这个示例中,在 futex_wake 后面调用了 get_futex_key。这是在源代码中真实发生的事情吗?我们可以检查一下!!这里是在 Linux 4.4 中 futex\_wake 的定义 (我的内核版本是 4.4)。

为节省时间我直接贴出来,它的内容如下:

static int
futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
{
    struct futex_hash_bucket *hb;
    struct futex_q *this, *next;
    union futex_key key = FUTEX_KEY_INIT;
    int ret;
    WAKE_Q(wake_q);

    if (!bitset)
        return -EINVAL;

    ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_READ);

如你所见,在 futex_wake 中的第一个函数调用真的是 get_futex_key! 太棒了!相比阅读内核代码,阅读函数跟踪肯定是更容易的找到结果的办法,并且让人高兴的是,还能看到所有的函数用了多长时间。

如何知道哪些函数可以被跟踪

如果你去运行 sudo trace-cmd list -f,你将得到一个你可以跟踪的函数的列表。它很简单但是也很重要。

最后一件事:事件!

现在,我们已经知道了怎么去跟踪内核中的函数,真是太酷了!

还有一类我们可以跟踪的东西!有些事件与我们的函数调用并不相符。例如,你可能想知道当一个程序被调度进入或者离开 CPU 时,都发生了什么事件!你可能想通过“盯着”函数调用计算出来,但是,我告诉你,不可行!

由于函数也为你提供了几种事件,因此,你可以看到当重要的事件发生时,都发生了什么事情。你可以使用 sudo cat /sys/kernel/debug/tracing/available_events 来查看这些事件的一个列表。

我查看了全部的 schedswitch 事件。我并不完全知道 schedswitch 是什么,但是,我猜测它与调度有关。

sudo cat /sys/kernel/debug/tracing/available_events
sudo trace-cmd record -e sched:sched_switch
sudo trace-cmd report

输出如下:

 16169.624862:   Chrome_ChildIOT:24817 [112] S ==> chrome:15144 [120]
 16169.624992:   chrome:15144 [120] S ==> swapper/3:0 [120]
 16169.625202:   swapper/3:0 [120] R ==> Chrome_ChildIOT:24817 [112]
 16169.625251:   Chrome_ChildIOT:24817 [112] R ==> chrome:1561 [112]
 16169.625437:   chrome:1561 [112] S ==> chrome:15144 [120]

现在,可以很清楚地看到这些切换,从 PID 24817 -> 15144 -> kernel -> 24817 -> 1561 -> 15114。(所有的这些事件都发生在同一个 CPU 上)。

ftrace 是如何工作的?

ftrace 是一个动态跟踪系统。当我们开始 ftrace 内核函数时,函数的代码会被改变。让我们假设去跟踪 do_page_fault 函数。内核将在那个函数的汇编代码中插入一些额外的指令,以便每次该函数被调用时去提示跟踪系统。内核之所以能够添加额外的指令的原因是,Linux 将额外的几个 NOP 指令编译进每个函数中,因此,当需要的时候,这里有添加跟踪代码的地方。

这是一个十分复杂的问题,因为,当不需要使用 ftrace 去跟踪我的内核时,它根本就不影响性能。而当我需要跟踪时,跟踪的函数越多,产生的开销就越大。

(或许有些是不对的,但是,我认为的 ftrace 就是这样工作的)

更容易地使用 ftrace:brendan gregg 的工具及 kernelshark

正如我们在文件中所讨论的,你需要去考虑很多的关于单个的内核函数/事件直接使用 ftrace 都做了些什么。能够做到这一点很酷!但是也需要做大量的工作!

Brendan Gregg (我们的 Linux 调试工具“大神”)有个工具仓库,它使用 ftrace 去提供关于像 I/O 延迟这样的各种事情的信息。这是它在 GitHub 上全部的 perf-tools 仓库。

这里有一个权衡,那就是这些工具易于使用,但是你被限制仅能用于 Brendan Gregg 认可并做到工具里面的方面。它包括了很多方面!:)

另一个工具是将 ftrace 的输出可视化,做的比较好的是 kernelshark。我还没有用过它,但是看起来似乎很有用。你可以使用 sudo apt-get install kernelshark 来安装它。

一个新的超能力

我很高兴能够花一些时间去学习 ftrace!对于任何内核工具,不同的内核版本有不同的功效,我希望有一天你能发现它很有用!

ftrace 系列文章的一个索引

最后,这里是我找到的一些 ftrace 方面的文章。它们大部分在 LWN (Linux 新闻周刊)上,它是 Linux 的一个极好的资源(你可以购买一个 订阅!)


via: https://jvns.ca/blog/2017/03/19/getting-started-with-ftrace/

作者:Julia Evans 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出