[转帖]《Linux性能优化实战》笔记(23)—— 内核线程 CPU 利用率过高,perf 与 火焰图

linux,性能,优化,实战,笔记,内核,线程,cpu,利用率,perf,火焰 · 浏览次数 : 0

小编点评

**1. 火焰图简介** * 火焰图是一种动态矢量图格式,可以用来展示函数调用堆栈。 * 火焰图可以分为多个层级,每个层级代表一个函数。 * 火焰图的颜色表示函数执行的次数,红色表示增长,蓝色表示衰减。 **2. 生成火焰图** * 可以使用 `git clone` 下载 `FlameGraph` 工具。 * 执行 `perf script` 生成的 `perf.data` 文件。 * 使用 `stackcollapse-perf.pl` 脚本合并调用栈信息。 * 使用 `flamegraph.pl` 生成火焰图。 **3. 分析火焰图** * 可以从下往上看火焰图,沿着调用栈最宽的函数来分析执行次数最多的函数。 * 最开始,还是 `net_rx_action` 到 `netif_receive_skb` 处理网络收包。 * 然后, `br_handle_frame` 到 `br_nf_pre_routing` ,在网桥中接收并执行 `netfilter` 钩子函数。 * 再向上, `br_pass_frame_up` 到 `netif_receive_skb` ,从网桥转到其他网络设备又一次接收。 * 最后, `ip_forward` 这里,已经看不清函数名称了。 **4. 总结** * 火焰图可以用来展示内核线程的性能瓶颈。 * 了解火焰图的原理可以帮助我们分析内核线程的性能瓶颈。

正文

在排查网络问题时,我们还经常碰到的一个问题,就是内核线程的 CPU 使用率很高。比如,在高并发的场景中,内核线程 ksoftirqd 的 CPU 使用率通常就会比较高。回顾一下前面学过的 CPU 和网络模块,你应该知道,这是网络收发的软中断导致的。
要分析 ksoftirqd 这类 CPU 使用率比较高的内核线程,如果用前面介绍过的那些分析方法,一般需要借助于其他性能工具,进行辅助分析,并不算快捷。有没有其他更简单的方法,可以直接观察内核线程的行为,更快定位瓶颈呢?今天,我就继续以 ksoftirqd 为例,带你一起看看,如何分析内核线程的性能问题。

 

一、 内核线程

既然要讲内核线程的性能问题,我们就先来看看,有哪些常见的内核线程。

我们知道,在 Linux 中,用户态进程的“祖先”,都是 PID 号为 1 的 init 进程。现在主流的 Linux 发行版中,init 都是 systemd 进程;而其他的用户态进程,会通过systemd 来进行管理。

Linux 在启动过程中,有三个特殊的进程,也就是 PID 号最小的三个进程:

  • 0 号进程为 idle 进程,这也是系统创建的第一个进程,它在初始化 1 号和 2 号进程后,演变为空闲任务。当 CPU 上没有其他任务执行时,就会运行它。
  • 1 号进程为 init 进程,通常是 systemd 进程,在用户态运行,用来管理其他用户态进程。
  • 2 号进程为 kthreadd 进程,在内核态运行,用来管理内核线程。

所以,要查找内核线程,我们只需要从 2 号进程开始,查找它的子孙进程即可。比如,你可以使用 ps 命令,来查找 kthreadd 的子进程:

  1. ps -f --ppid 2 -p 2
  2. UID PID PPID C STIME TTY TIME CMD
  3. root 2 0 0 12:02 ? 00:00:01 [kthreadd]
  4. root 9 2 0 12:02 ? 00:00:21 [ksoftirqd/0]
  5. root 10 2 0 12:02 ? 00:11:47 [rcu_sched]
  6. root 11 2 0 12:02 ? 00:00:18 [migration/0]
  7. ...
  8. root 11094 2 0 14:20 ? 00:00:00 [kworker/1:0-eve]
  9. root 11647 2 0 14:27 ? 00:00:00 [kworker/0:2-cgr]

从上面的输出,你能够看到,内核线程的名称(CMD)都在中括号里,所以,更简单的方法,就是直接查找名称包含中括号的进程。比如

  1. ps -ef | grep "\[.*\]"
  2. root 2 0 0 08:14 ? 00:00:00 [kthreadd]
  3. root 3 2 0 08:14 ? 00:00:00 [rcu_gp]
  4. root 4 2 0 08:14 ? 00:00:00 [rcu_par_gp]
  5. ...

了解内核线程的基本功能,对我们排查问题有非常大的帮助。比如,我们曾经在软中断案例中提到过 ksoftirqd。它是一个用来处理软中断的内核线程,并且每个 CPU 上都有一个。如果你知道这一点,那么以后遇到 ksoftirqd 的 CPU 使用高的情况,就会首先怀疑是软中断的问题,然后从软中断的角度来进一步分析。

其实,除了刚才看到的 kthreadd 和 ksoftirqd 外,还有很多常见的内核线程,我们在性能分析中都经常会碰到,比如下面这几个内核线程:

  • kswapd0:用于内存回收(swap)
  • kworker:用于执行内核工作队列,分为绑定 CPU和未绑定 CPU两类。
  • migration:在负载均衡过程中把进程迁移到 CPU 上,每个 CPU 都有一个migration 内核线程。
  • jbd2/sda1-8:jbd 是 Journaling Block Device 的缩写,用来为文件系统提供日志功能,以保证数据的完整性;名称中的 sda1-8,表示磁盘分区名称和设备号。每个使用了ext4 文件系统的磁盘分区,都会有一个 jbd2 内核线程。
  • pdflush:用于将内存中的脏页写入磁盘(已经在 3.10 中合并入了 kworker 中)。

 

二、 案例分析

运行 hping3 命令,模拟 Nginx 的客户端请求:

  1. # -S参数表示设置TCP协议的SYN(同步序列号),-p表示目的端口为80
  2. # -i u10表示每隔10微秒发送一个网络帧
  3. # 注:如果你在实践过程中现象不明显,可以尝试把10调小,比如调成5甚至1
  4. hping3 -S -p 80 -i u10 192.168.0.30

你会发现192.168.0.30服务器的系统响应明显变慢了。我们不妨执行 top,观察一下系统和进程的 CPU 使用情况:

  1. $ top
  2. top - 08:31:43 up 17 min, 1 user, load average: 0.00, 0.00, 0.02
  3. Tasks: 128 total, 1 running, 69 sleeping, 0 stopped, 0 zombie
  4. %Cpu0 : 0.3 us, 0.3 sy, 0.0 ni, 66.8 id, 0.3 wa, 0.0 hi, 32.4 si, 0.0 st
  5. %Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 65.2 id, 0.0 wa, 0.0 hi, 34.5 si, 0.0 st
  6. KiB Mem : 8167040 total, 7234236 free, 358976 used, 573828 buff/cache
  7. KiB Swap: 0 total, 0 free, 0 used. 7560460 avail Mem
  8. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  9. 9 root 20 0 0 0 0 S 7.0 0.0 0:00.48 ksoftirqd/0
  10. 18 root 20 0 0 0 0 S 6.9 0.0 0:00.56 ksoftirqd/1
  11. 2489 root 20 0 876896 38408 21520 S 0.3 0.5 0:01.50 docker-containe
  12. 3008 root 20 0 44536 3936 3304 R 0.3 0.0 0:00.09 top
  13. 1 root 20 0 78116 9000 6432 S 0.0 0.1 0:11.77 systemd

可以看到,两个 CPU 的软中断使用率都超过了 30%,而 CPU 使用率最高的进程是软中断内核线程 ksoftirqd/0 和 1。

虽然我们已经知道了 ksoftirqd 的基本功能,可以猜测是因为大量网络收发,引起了CPU 使用率升高;但它到底在执行什么逻辑,我们却并不知道。对于普通进程,我们要观察其行为有很多方法,比如 strace、pstack、lsof 等等。但这些工具并不适合内核线程。如果用 pstack 或者通过 /proc/pid/stack 查看ksoftirqd/0(进程号为 9)的调用栈,分别可以得到以下输出:

  1. $ pstack 9
  2. Could not attach to target 9: Operation not permitted.
  3. detach: No such process
  1. $ cat /proc/9/stack
  2. [<0>] smpboot_thread_fn+0x166/0x170
  3. [<0>] kthread+0x121/0x140
  4. [<0>] ret_from_fork+0x35/0x40
  5. [<0>] 0xffffffffffffffff

pstack 报出的是不允许挂载进程的错误;而 /proc/9/stack 方式虽然有输出,但输出中并没有详细的调用栈情况。那还有没有其他方法,来观察内核线程 ksoftirqd 的行为呢?

既然是内核线程,自然应该用到内核中提供的机制。我想你肯定还记得 perf 这个内核自带的性能剖析工具。perf 可以对指定的进程或者事件进行采样,并且还可以用调用栈的形式,输出整个调用链上的汇总信息。 不妨就用 perf ,来试着分析一下进程号为 9 的 ksoftirqd。

执行下面的 perf record 命令;并指定进程号 9 ,以便记录 ksoftirqd 的行为。

  1. # 采样30s后退出
  2. perf record -a -g -p 9 -- sleep 30

稍等一会儿,在上述命令结束后,继续执行 perf report命令,就可以得到 perf 的汇总报告。按上下方向键以及回车键,展开比例最高的 ksoftirqd 后,可以得到下面这个调用关系链图:

从这个图中,你可以清楚看到 ksoftirqd 执行最多的调用过程。虽然你可能不太熟悉内核源码,但通过这些函数,我们可以大致看出它的调用栈过程:

  • net_rx_action 和 netif_receive_skb,表明这是接收网络包(rx 表示 receive)。
  • br_handle_frame ,表明网络包经过了网桥(br 表示 bridge)。
  • br_nf_pre_routing ,表明在网桥上执行了 netfilter 的 PREROUTING(nf 表示netfilter),而我们已经知道 PREROUTING 主要用来执行 DNAT,所以可以猜测这里有 DNAT 发生。
  • br_pass_frame_up,表明网桥处理后,再交给桥接的其他桥接网卡进一步处理。比如,在新的网卡上接收网络包、执行 netfilter 过滤规则等等

我们的猜测对不对呢?实际上,我们案例最开始用 Docker 启动了容器,而 Docker 会自动为容器创建虚拟网卡、桥接到 docker0 网桥并配置 NAT 规则。这一过程,如下图所示

当然了,前面 perf report 界面的调用链还可以继续展开。但很不幸,我的屏幕不够大,如果展开更多的层级,最后几个层级会超出屏幕范围。这样,即使我们能看到大部分的调用过程,却也不能说明后面层级就没问题。那么,有没有更好的方法,来查看整个调用栈的信息呢?


三、 火焰图

1. 简介

针对 perf 汇总数据的展示问题,Brendan Gragg 发明了火焰图,通过矢量图的形式,更直观展示汇总结果。下图就是一个针对 mysql 的火焰图示例。

  • 横轴表示采样数和采样比例。一个函数占用的横轴越宽代表它的执行时间越长。同一层的多个函数,则是按照字母来排序。
  • 纵轴表示调用栈,由下往上根据调用关系逐个展开。换句话说,上下相邻的两个函数中,下面的函数,是上面函数的父函数。调用栈越深,纵轴就越高。
  • 图中的颜色并没有特殊含义,只是用来区分不同的函数。

火焰图是动态的矢量图格式,所以它还支持一些动态特性。比如,鼠标悬停到某个函数上时,就会自动显示这个函数的采样数和采样比例。而当你用鼠标点击函数时,火焰图就会把该层及其上的各层放大,方便观察细节。上面 mysql 火焰图的示例,就表示了 CPU 的繁忙情况,这种火焰图也被称为 on-CPU 火焰图。

 

如果我们根据性能分析的目标来划分,火焰图可以分为下面这几种。

  • on-CPU 火焰图:表示 CPU 的繁忙情况,用在 CPU 使用率比较高的场景中。
  • off-CPU 火焰图:表示 CPU 等待 I/O、锁等各种资源的阻塞情况。
  • 内存火焰图:表示内存的分配和释放情况。
  • 热 / 冷火焰图:表示将 on-CPU 和 off-CPU 结合在一起综合展示。
  • 差分火焰图:表示两个火焰图的差分情况,红色表示增长,蓝色表示衰减。常用来比较不同场景和不同时期的火焰图,以便分析系统变化前后对性能的影响情况。
     

接下来,我们再回到案例,运用火焰图来观察刚才perf record 得到的记录。

2. 生成火焰图

我们先下载几个能从 perf record 记录生成火焰图的工具,这些工具都放在 https://github.com/brendangregg/FlameGraph 上面。你可以执行下面的命令来下载:

  1. git clone https://github.com/brendangregg/FlameGraph
  2. cd FlameGraph

安装好工具后,要生成火焰图,其实主要需要三个步骤:

  • 执行 perf script ,将 perf record 的记录转换成可读的采样记录;
  • 执行 stackcollapse-perf.pl 脚本,合并调用栈信息;
  • 执行 flamegraph.pl 脚本,生成火焰图

在 Linux 中,我们可以使用管道,来简化这三个步骤的执行过程。假设刚才用 perf record 生成的文件路径为 /root/perf.data,执行下面的命令,你就可以直接生成火焰图:

perf script -i /root/perf.data | ./stackcollapse-perf.pl --all |  ./flamegraph.pl > ksoftirqd.svg

执行成功后,使用浏览器打开 ksoftirqd.svg ,你就可以看到生成的火焰图了。

 

3. 分析火焰图

根据刚刚讲过的火焰图原理,这个图应该从下往上看,沿着调用栈中最宽的函数来分析执行次数最多的函数。这儿看到的结果,其实跟刚才的 perf report 类似,但直观了很多,中间这一团火,很明显就是最需要我们关注的地方。我们顺着调用栈由下往上看(顺着图中蓝色箭头),就可以得到跟刚才 perf report 中一样的结果:

  • 最开始,还是 net_rx_action 到 netif_receive_skb 处理网络收包;
  • 然后, br_handle_frame 到 br_nf_pre_routing ,在网桥中接收并执行 netfilter 钩子函数;
  • 再向上, br_pass_frame_up 到 netif_receive_skb ,从网桥转到其他网络设备又一次接收。
  • 不过最后,到了 ip_forward 这里,已经看不清函数名称了。所以我们需要点击ip_forward,展开最上面这一块调用栈:

这样,就可以进一步看到 ip_forward 后的行为,也就是把网络包发送出去。根据这个调用过程,再结合我们前面学习的网络收发和 TCP/IP 协议栈原理,这个流程中的网络接收、网桥以及 netfilter 调用等,都是导致软中断 CPU 升高的重要因素,也就是影响网络性能的潜在瓶颈。

不过,回想一下网络收发的流程,你可能会觉得它缺了好多步骤。比如,这个堆栈中并没有 TCP 相关的调用,也没有连接跟踪 conntrack 相关的函数。实际上,这些流程都在其他更小的火焰中,你可以点击上图左上角的“Reset Zoom”,回到完
整火焰图中,再去查看其他小火焰的堆栈。

所以,在理解这个调用栈时要注意。从任何一个点出发、纵向来看的整个调用栈,其实只是最顶端那一个函数的调用堆栈,而非完整的内核网络执行流程。

另外,整个火焰图不包含任何时间的因素,所以并不能看出横向各个函数的执行次序。到这里,我们就找出了内核线程 ksoftirqd 执行最频繁的函数调用堆栈,而这个堆栈中的各层级函数,就是潜在的性能瓶颈来源。这样,后面想要进一步分析、优化时,也就有了根据。

文章知识点与官方知识档案匹配,可进一步学习相关知识
CS入门技能树Linux入门初识Linux32636 人正在系统学习中

与[转帖]《Linux性能优化实战》笔记(23)—— 内核线程 CPU 利用率过高,perf 与 火焰图相似的内容:

[转帖]《Linux性能优化实战》笔记(23)—— 内核线程 CPU 利用率过高,perf 与 火焰图

在排查网络问题时,我们还经常碰到的一个问题,就是内核线程的 CPU 使用率很高。比如,在高并发的场景中,内核线程 ksoftirqd 的 CPU 使用率通常就会比较高。回顾一下前面学过的 CPU 和网络模块,你应该知道,这是网络收发的软中断导致的。 要分析 ksoftirqd 这类 CPU 使用率比

[转帖]《Linux性能优化实战》笔记(一)—— 平均负载

最近在看极客时间的《Linux性能优化实战》课程,记录下学习内容。 一、 平均负载(Load Average) 1. 概念 我们都知道uptime命令的最后三列分别是过去 1 分钟、5 分钟、15 分钟系统的平均负载,到底平均负载是什么? 简单来说,平均负载是指单位时间内,系统处于可运行状态和不可中

[转帖]《Linux性能优化实战》笔记(二)—— CPU 上下文切换(上)

上一篇的最后一个例子,在多个进程竞争CPU时,我们看到每个进程实际上%usr部分只有20%多,70%多是在wait,但是load远远高于单个进程使用CPU达到100%。 这让我想到之前看的RWP公开课,里面有一篇连接池管理。为什么相同的业务量,起6千个连接(进程)远远要慢于200个连接,因为绝大多数

[转帖]《Linux性能优化实战》笔记(八)—— 内存是怎么工作的

一、 内存映射 我们通常所说的内存容量,指的是物理内存。物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存。那么,进程要访问内存时,该怎么办呢? Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以

[转帖]《Linux性能优化实战》笔记(22)—— 网络丢包问题分析

所谓丢包,是指在网络数据的收发过程中,由于种种原因,数据包还没传输到应用程序中,就被丢弃了。这些被丢弃包的数量,除以总的传输包数,也就是我们常说的丢包率。丢包率是网络性能中最核心的指标之一。丢包通常会带来严重的性能下降,特别是对 TCP 来说,丢包通常意味着网络拥塞和重传,进而还会导致网络延迟增大、

[转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术。动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就获得丰富的信息,帮你分析、定位想要排查的问题。 以往,在排查和调试性能问题时,我们往往需要先为应用程

[转帖]《Linux性能优化实战》笔记(25)—— 总结:Linux 性能工具速查

一、 性能工具速查 在梳理性能工具之前,首先给你提一个问题,那就是,在什么情况下,我们才需要去查找、挑选性能工具呢? 其实在我看来,只有当你想了解某个性能指标,却不知道该怎么办的时候,才会想到,“要是有一个性能工具速查表就好了”这个问题。如果已知一个性能工具可用,我们更多会去查看这个工具的手册,找出

[转帖]《Linux性能优化实战》笔记(21)—— 网络性能优化思路

一、 确定优化目标 优化前,我会先问问自己,网络性能优化的目标是什么?实际上,虽然网络性能优化的整体目标,是降低网络延迟(如 RTT)和提高吞吐量(如BPS 和 PPS),但具体到不同应用中,每个指标的优化标准可能会不同,优先级顺序也大相径庭。 拿NAT 网关来说,由于其直接影响整个数据中心的网络出

[转帖]《Linux性能优化实战》笔记(十九)—— DNS 解析原理与故障案例分析

一、 域名与 DNS 解析 域名主要是为了方便让人记住,而 IP 地址是机器间的通信的真正机制。以 time.geekbang.org 为例,最后面的 org 是顶级域名,中间的 geekbang 是二级域名,而最左边的 time 则是三级域名。点(.)是所有域名的根,所有域名都以点作为后缀。 把域

[转帖]《Linux性能优化实战》笔记(20)—— 使用 tcpdump 和 Wireshark 分析网络流量

tcpdump 和 Wireshark 是最常用的网络抓包和分析工具,更是分析网络性能必不可少的利器。 tcpdump 仅支持命令行格式使用,常用在服务器中抓取和分析网络包。Wireshark 除了可以抓包,还提供了强大的图形界面和汇总分析工具,在分析复杂的网络情景时,尤为简单和实用。因而,在实际分