【CPU】有效排查与优化技巧(1)—排查

138 阅读8分钟

思考

在面试过程中,面试官常常会问:“当线上出现性能问题时,你会如何进行排查和定位?”面对这种开放性的问题,我时常有一种冲动:如果在现场,我可以直接演示,给你们见识下我优秀的动手能力。然而,遗憾的是,我只能用语言表达,而我的语言能力并不出色。

所以本文章用两个篇幅:总结了最近的阅读和实际排查过程中的笔记,重点讨论CPU相关问题。

分析

top命令

CPU作为性能指标排查之一,该如何进行分析?

第一反应:打开top命令,按下大写的"P",将top命令进行CPU排序。

举例

举例

根据上图,cpu使用率最高的是top命令。这样就排查完成了? Nonono。

首先来看下,第一行的load average(平均负载) : 指单位时间内,系统处于可运行状态不可中断状态的平均进程数,也就是平均活跃进程数。

:::info 可运行状态:正在运行中和正在等待CPU的进程。R 状态。

不可中断状态:正处于内核执行关键过程中,不可打断。 D状态。

:::

而平均负载又与CPU数有关,如何获取当前机器的CPU个数:

1. 使用top后,按下1;
2. 从系统配置中获取:grep 'model name' /proc/cpuinfo | wc -l

测试机器的cpu个数为8, 所以示例图中表示的意思是:

1分钟内,cpu的平均使用率为 0.5 %(0.04 / 8 *100);

5分钟内,cpu的平均使用率为 1.25%;

15分钟内,cpu的平均使用率为 1.125%;如果为8,那么cpu的个数正好占满。如果为10,那么过载为25%;

第二行Tasks: 分别为:进程总数,正在运行的进程数,睡眠的进程数,停止的进程数,僵尸进程数。

其中僵尸进程(Z/zombie):进程已经退出,但父进程没有回收其资源。

第三行CPU执行状态占比:

  • us: 用户态执行占用百分比;
  • sy:内核态执行占用百分比;
  • ni:进程优先级的用户进程执行的 CPU 时间;
  • id:表示 CPU 处于空闲态的时间占比;
  • wa:CPU 在等待 I/O 操作完成所花费的时间;
  • hi:CPU 处理硬中断所花费的时间;
  • si:CPU 处理软中断所花费的时间;
  • st:CPU 被其他虚拟机占用的时间;
  • CPU 被其他虚拟机占用的时间

问题:什么是用户态,什么是内核态?

:::info 用户态:普通应用程序运行的模式,程序权限受到限制,它无法直接访问硬件或操作系统内核的资源;

内核态:操作系统内核运行的模式。在这个模式下,程序拥有完全的访问权限,可以直接操作硬件和系统资源;

:::

为什么需要用户态以及内核态?

:::info 安全性:将程序分为用户态和内核态,可以限制普通程序的权限,降低系统被恶意程序攻击的风险。

稳定性:用户态的隔离确保了一个程序的错误不会导致整个系统崩溃,提高了系统的稳定性。

资源管理:内核态负责管理系统资源,确保多任务操作时的资源分配合理,避免资源冲突。

性能优化:通过系统调用的方式,内核和用户程序之间可以进行高效的交互,保证系统的性能。

:::

用户态切换到内核态: 系统调用。是用户态程序与内核态之间进行交互的接口。它允许用户程序请求操作系统提供的服务,如文件操作、进程控制、网络通信等。通过系统调用,用户程序可以执行一些特权操作,而无需直接访问内核。

这里需要注意,在系统性能优化过程中,优化方法的其中之一:降低系统调用切换频率。 比如:零拷贝机制。

注意:系统调用同一个进程中的特权模式切换。

中断

继续上面的top第三行执行状态分析:硬/软中断。

什么是中断?

系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。

场景:主机在执行过程中,往往需要网络的参与,而网络设备的收发数据包,则就是中断的一种。

为什么需要中断?

首先,在主机执行过程中,不可能持续的轮询网络设备是否存在数据包需要处理,过于低效。所以采用一种异步的方式进行处理。所以:中断其实是一种异步的事件处理机制,可以提高系统的并发处理能力。

硬件设备等产生中断往往需要程序对中断的数据进行处理,如网卡收到数据后相关处理。所以中断不是一个串行的过程,而是分为硬中断、软中断。

硬中断直接处理硬件请求,快速处理:当硬件数据就绪产生硬件中断,设置硬件对应寄存器,再发送软中断信号,通知内核处理——延迟处理。

如何观测到中断?

# 软中断
top -p `pgrep ksoftirqd | paste -sd "," -`
Tasks:   8 total,   0 running,   8 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st 
MiB Mem :  31771.5 total,  30345.3 free,    919.7 used,    909.3 buff/cache     
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  30851.8 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                        
     16 root      20   0       0      0      0 S   0.0   0.0   0:00.13 ksoftirqd/0                                                                                                                                    
     24 root      20   0       0      0      0 S   0.0   0.0   0:00.02 ksoftirqd/1                                                                                                                                    
     30 root      20   0       0      0      0 S   0.0   0.0   0:00.01 ksoftirqd/2                                                                                                                                    
     36 root      20   0       0      0      0 S   0.0   0.0   0:00.01 ksoftirqd/3                                                                                                                                    
     42 root      20   0       0      0      0 S   0.0   0.0   0:00.00 ksoftirqd/4                                                                                                                                    
     48 root      20   0       0      0      0 S   0.0   0.0   0:00.01 ksoftirqd/5                                                                                                                                    
     54 root      20   0       0      0      0 S   0.0   0.0   0:00.01 ksoftirqd/6                                                                                                                                    
     60 root      20   0       0      0      0 S   0.0   0.0   0:00.01 ksoftirqd/7  

paste 命令用于合并文件的列。 paste -sd "," 表示多行使用,拼接。

软中断进程名:ksoftirqd/<cpu编号>

软中断运行状态:cat /proc/softirqs

硬中断运行状况:<font style="color:rgb(53, 53, 53);">cat /proc/interrupts</font>

在top命令中,就可以分析出当前系统整体可能存在的一部分问题。但还是不够,在现代的计算机系统中,多进程的执行/切换等,也会影响到性能。 (系统整体--> 多进程切换)

上下文切换

CPU为固定资源,在一台机器上多个进程都需要抢占,必然涉及到切换。切换的过程 CPU需要执行一系列操作来保存和恢复进程的状态,包括: 寄存器状态(程序计数器PC,栈指针等), 内存管理信息,进程控制块(PCB)等。

切换分类:进程上下文,线程上下文,中断上下文。

切换的时机:

  • 时间片;
  • I/O请求;
  • 主动调用函数挂起,如sleep函数。
  • 优先级调度; 使用nice命令设置进程优先级。
  • 硬件中断。

怎么查看CPU的抢占,切换等信息?

vmstatpidstat -w

vmstat可以用来查看当前虚拟机的执行状态;

vmstat  
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 215948 633876 930076    0    0     1    70    0    0 11  7 81  0  0

其中procs中给出:

r: 正在运行以及等待CPU的进程数;

b:不可中断睡眠状态的进程数;

cpu的指标与top中相似。system中展示出 in(每秒中断次数)和 cs(每秒上下文切换次数);vm采集的是整个系统层级(因为叫vmstat嘛)。

对于进程而言,可以使用pidstat来查看进程切换的次数:

pidstat -w -p 13559
Linux 5.4.0-91-generic (zone-30210-k8s-m1)      09/19/2024      _x86_64_        (2 CPU)
09:36:14 AM   UID       PID   cswch/s nvcswch/s  Command
09:36:14 AM     0     13559     56.84     40.69  coredns

其中指标:

  • Cswch/s:每秒主动任务上下文切换数量
  • Nvcswch/s:每秒被动任务上下文切换数量

而只查看进程的情况下,无法真正的了解到系统最小执行单元的切换:线程。所以在pidstat的基础上需要加上** -t;**

CPU 性能问题排查-perf

在程序执行过程中,通过上面的操作往往只能查看到某个进程的CPU使用异常,但是为什么会使用异常,没办法明确的知道,更好的办法:火焰图。

通过火焰图,我们可以完整的观测到问题原因,具体操作如下:

  • 安装perf
# ubuntu
sudo apt-get update
sudo apt-get upgrade
sudo apt-get autoremove
sudo apt-get install linux-tools-common linux-tools-$(uname -r)
# Centos
yum install -y perf
  • 下载FlameGraph工具
git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
  • 使用perf生成perf.data
perf record -F 99 -p <pid> -g --call-graph dwarf
- -F 99:这个参数设置了采样频率,单位是赫兹(Hz)。在这个例子中,99 表示每秒采集 99 次样本。这个频率越高,你获得的数据就越详细,但同时也会增加系统的性能开销。
- -p <pid>:这个参数指定了要监控的进程的进程ID(PID)。你需要将 <pid> 替换为实际的进程ID。perf 会附加到这个进程并开始收集性能数据。
- -g:这个参数告诉 perf record 收集调用图形(callgraph)信息。调用图形是程序中函数调用的树状结构,可以帮助你理解程序的执行路径和性能瓶颈。
- --call-graph dwarf:这个参数指定了调用图形的类型。dwarf 是一种基于 DWARF 调试信息的调用图形。DWARF 是一种广泛使用的调试文件格式,它包含了程序的源代码和调试信息。使用 dwarf 选项可以提供更详细的调用栈信息,包括函数的行号和源文件名。

执行完命令后,在本目录下会生成perf.data文件。

  • 将 perf record 命令收集的性能数据转换成文本格式的调用栈
perf script > out.perf
  • 将文本格式调用栈转换为 火焰图的数据格式, 并生成svg图。
./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
./FlameGraph/flamegraph.pl out.folded > out.svg

FAQ

golang自带“干电池”,可以通过pprof直接生成,具体操作可参考:【翻译】GO如何进行性能分析

为什么的火焰图中,没有详细的函数签名或调试信息?

:::info 比如golang,在使用go build的时候,关注是否使用ldflag将调试信息关闭。在提高编译效率以及减少包体的情况下,可能会关闭。

另外使用go run .启动的进程,默认情况下也不会携带调试信息,官方文档:
By default, 'go run' compiles the binary without generating the informationused by debuggers, to reduce build time.

:::

在golang中忘记内置“干电池”的情况下,可以使用该方法,一般情况下,使用golang就意味着你的程序要么在docker中运行,要么在容器中运行。 那么就会导致perf无法捕获到进程调试信息,处理如下:

:::info mkdir /tmp/foo

PID=$(docker inspect --format {{.State.Pid}} phpfpm)

bindfs /proc/$PID/root /tmp/foo

perf report --symfs /tmp/foo

使用完成后不要忘记解除绑定

umount /tmp/foo/

:::

总结

本篇从top命令入手,系统层面 ——> 多进程——>特定进程的分析路径以及相关工具,也作为阅读多个小册的总结性文章。

下一篇将提供相关的CPU优化方案。

参考

  1. 《linux性能优化实战》
  2. 《系统性能调优必知必会》
  3. linux 命令:top 详解_linux top e-CSDN博客