内核行跟踪:Linux perf 驾驭火箭

坐火箭
这是什么意思??Ubuntu Trusty 在我们的云实例中丢包,给我们留下了神秘的系统消息:
[85290.555808] xen_netfront:xennet:
skb 乘坐火箭:19 个插槽
这使我们乘上了高级 Linux 内核跟踪的火箭……
在这篇文章中,我将演示使用 perf_events 进行 Linux 内核跟踪的一些相当未知的特性(实际上是秘密),它是 Linux 内核源代码 (tools/perf) 的一部分。
神秘的系统消息总比没有系统消息要好,因为我们至少有一些东西要搜索。该消息来自以下函数(这是 Linux 3.13.6),它从 Xen 来宾(在 AWS EC2 上)传输数据包:
静态 int xennet_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
[…]
插槽 = DIV_ROUND_UP(偏移量 + 长度,PAGE_SIZE)+
xennet_count_skb_frag_slots(skb);
如果(不太可能(插槽> MAX_SKB_FRAGS + 1)){
net_alert_ratelimited(
“xennet:skb 乘坐火箭:%d 个插槽\n”,插槽);
转到下降;
}
是的,goto drop,它会碰到一个计数器并释放数据包。现在客户端问题!客户端将在引发性能问题的超时后重新传输数据包。让我们希望重新传输的数据包也不会像火箭一样(…这可能导致指数延迟。)
我们知道slot的值,因为它是系统消息 (19) 的一部分。我们从内核源代码中知道 MAX_SKB_FRAGS 是什么:它是 16,并且与限制可以发送到设备环形缓冲区的片段或页面的数量有关。我们不知道offset、len或xennet_count_skb_frag_slots(skb)的返回值。

我已经对这个代码路径有点熟悉了,并且对这些 skb 的实际大小有所了解(例如,通过使用 perf-tools 中的“perf-stat-hist net:net_dev_xmit len 10”;参见示例),并且 偏移量可能是多少。 我想直接在这里检查它们,但我将首先使用 perf_events 检测 xennet_count_skb_frag_slots() 的返回:

# perf probe 'xennet_count_skb_frag_slots%return ret=$retval'
Return probe must be on the head of a real function.
  Error: Failed to add events. (-22)
# grep xennet_count_skb_frag_slots /proc/kallsyms
# 

…真正的编译器,你不能内联别人吗?
所以这个符号不存在。 对于大多数示踪剂来说,这是一条死胡同。
在使用内核动态跟踪器十年之后,我开发了各种技巧和技巧来解决这个问题:也许我可以跟踪 xennet_count_skb_frag_slots() 函数的一个子函数; 也许我可以从 xennet_start_xmit() 复制逻辑,在那里我可以观察 skb。 我开始尝试这样的变通方法,但在这种情况下,它变得很繁重。 在这一点上,我通常会开始考虑编辑内核并插入工具来查看这一点,这涉及到编译、测试和部署周期。
但是在 Linux 上,使用内核调试信息,我可以直接走得更远。 让我们切换到该函数的入口,并使用“-nv”来显示性能探针不这样做会做什么:

# perf probe -nv xennet_count_skb_frag_slots
probe-definition(0): xennet_count_skb_frag_slots 
symbol:xennet_count_skb_frag_slots file:(null) line:0 offset:0 return:0 lazy:(null)
0 arguments
Looking at the vmlinux_path (6 entries long)
symsrc__init: cannot get elf header.
Using /lib/modules/3.13.11.6/build/vmlinux for symbols
found inline addr: 0xffffffff8152dae8
Probe point found: xennet_start_xmit+88
find 1 probe_trace_events.
Opening /sys/kernel/debug//tracing/kprobe_events write=1
Added new event:
Writing event: p:probe/xennet_count_skb_frag_slots xennet_start_xmit+88
  probe:xennet_count_skb_frag_slots (on xennet_count_skb_frag_slots)

You can now use it in all perf tools, such as:

    perf record -e probe:xennet_count_skb_frag_slots -aR sleep 1

哇,所以这个内联函数的开头确实可以检测,因为 Linux 可以跟踪内核指令(或行号)。 注意偏移量“+88”。
也可以跟踪此函数内的行(尽管它是内联的)。 perf report 命令将列出带有 -L 的候选者:

# perf probe -L xennet_count_skb_frag_slots

      0  static int xennet_count_skb_frag_slots(struct sk_buff *skb)
         {
      2         int i, frags = skb_shinfo(skb)->nr_frags;
      3         int pages = 0;
         
      5         for (i = 0; i < frags; i++) {
                        skb_frag_t *frag = skb_shinfo(skb)->frags + i;
      7                 unsigned long size = skb_frag_size(frag);
                        unsigned long offset = frag->page_offset;
         
                        /* Skip unused frames from start of page */
     11                 offset &= ~PAGE_MASK;
         
     13                 pages += PFN_UP(offset + size);
                }
         
                return pages;

这是相当惊人的。 perf 以蓝色显示可以直接检测的行号和不能直接检测的行号。
也可以检查一些局部变量。 让我们看看第 11 行有哪些内容:

# perf probe -V xennet_count_skb_frag_slots:11
Available variables at xennet_count_skb_frag_slots:11
        @<xennet_start_xmit+155>
                int     pages
                long unsigned int       size

缺少偏移变量,但我还有其他人可以使用,这非常有用。 我还可以包含外部符号; 在列表的单行末尾添加一个“–externs”。
但是你不讨厌内核调试信息吗?
是的,它是带有内核调试信息的彩虹和小马,但它也超过 100 MB。 在 Netflix,我们经常创建和销毁云实例(自动扩展和代码部署),保持实例大小以减少创建时间和降低网络流量非常重要。
我想出了一个简单的解决方法:为每个使用的内核版本创建一个带有内核调试信息的小型测试实例,并将其用作参考。 假设我想在 xennet_count_skb_frag_slots() 的第 11 行跟踪这些局部变量。 在我的参考实例上:

# perf probe -nv 'xennet_count_skb_frag_slots:11 pages size' 2>&1 | grep Writing
Writing event: p:probe/xennet_count_skb_frag_slots xennet_start_xmit+155 pages=%si:s32 size=%di:u64

现在我用鼠标复制并粘贴突出显示的详细信息,以便在没有调试信息的系统上用于 perf probe(或者我的 perf-tools 中的 kprobe,如果它是一个真正的函数条目)。 例如:

# perf probe 'xennet_count_skb_frag_slots:11 pages size'
Failed to find path of kernel module.
Failed to open debuginfo file.
  Error: Failed to add events. (-2)
# perf probe 'xennet_start_xmit+155 pages=%si:s32 size=%di:u64'
Failed to find path of kernel module.
Added new event:
  probe:xennet_start_xmit (on xennet_start_xmit+155 with pages=%si:s32 size=%di:u64)

You can now use it in all perf tools, such as:

    perf record -e probe:xennet_start_xmit -aR sleep 1

Awesome!
我首先表明这个系统确实没有内核调试信息。第二次尝试使用来自参考系统的寄存器详细信息,成功了。请注意,如果内核版本相同,这种方法只会创建一个有效的探测。如果您在不同的内核上尝试此操作,它可能会工作,但会提供无效的结果。
SystemTap 可以自动使用参考系统,尽管当我尝试时,由于我们的环境,我遇到了防火墙和端口转发的问题。至于其他方式:我在 LinuxCon North America 遇到了 Masami Hiramatsu,他想出了一种方法来构建一个简单的函数和变量文本数据库——将 debuginfo 剥离到我需要的内容。当我有机会时,我会写博客。
这些参考系统也可用于在生产中使用它们之前测试跟踪调用。
一些输出
启用此探测:

perf record -e probe:xennet_start_xmit -aR sleep 10

[性能记录:唤醒1次写入数据]
[性能记录:捕获并写入 1.466 MB perf.data(~64033 个样本)]
#性能脚本
[…]
sshd 92592 [009] 87585.288990:探针:xennet_start_xmit:(ffffffff8152e6bb)页面=0 大小=280
sshd 92592 [009] 87585.295461:探针:xennet_start_xmit:(ffffffff8152e6bb)页面=0 大小=3058
sshd 92592 [009] 87585.295472:探针:xennet_start_xmit:(ffffffff8152e6bb)页面=0 大小=4f8
sshd 92592 [009] 87585.296417: 探针:xennet_start_xmit: (ffffffff8152e6bb) 页面=0 大小=538
sshd 92592 [009] 87585.296426: 探针:xennet_start_xmit: (ffffffff8152e6bb) 页面=0 大小=1c8
sshd 92592 [009] 87585.304101:探针:xennet_start_xmit:(ffffffff8152e6bb)页面=0 大小=29e0
sshd 92592 [009] 87585.304102:探针:xennet_start_xmit:(ffffffff8152e6bb)页面=3 大小=bdc
sshd 92592 [009] 87585.304111: 探针:xennet_start_xmit: (ffffffff8152e6bb) 页面=0 大小=4d4
[…]
伟大的。由于 TCP 发送卸载 (TSO),大小大于预期的 MTU。警告:任何网络数据包跟踪都可能产生巨大的开销,尤其是对于 10 GbE 及更高的速度,因此请注意网络路径并首先寻找其他解决方案。
火箭呢?
这是 TSO 的驱动程序错误。一个非常大的 skb 可能跨越太多页面(超过 16 个)而无法放入驱动程序环形缓冲区。一种解决方法是“sudo ethtool -K eth0 tso off”,用于您的界面。互联网上有很多关于此的文章,由于我们的神秘信息,它们很容易找到。:slight_smile:
结论
我并不总是需要 Linux 内核行号跟踪,但有时它非常方便。也可以检查给定行号的局部变量。这对于性能分析和调试都很有用,例如分析我们的“skb 乘火箭”问题。
为了在所有云实例上使用此功能而无需内核调试信息,我使用了参考系统方法。在将其用于生产之前,该参考系统还可用作测试特定跟踪的场所。
与所有内核跟踪一样:小心,因为过去曾出现过恐慌和冻结,并且在使用之前知道你在做什么。有关性能的更多信息,请参阅我的perf_events页面和perf wiki。

转载翻译自: https://www.brendangregg.com