CPU使用率过高分析及优化

    2019年03月24日 Linux 字数:6688

破山中贼易,破心中贼难

前面的两篇文章主要介绍了系统的平均负载CPU上下文切换。但是实际上我们更常用CPU使用率来描述系统的CPU性能。

CPU使用率是单位时间内CPU使用情况的统计,然后以百分比的方式进行展示,前面我们使用的top, mpstat, pidstat, ps等命令都可以查看CPU使用率。下面主要介绍一下不同场景的CPU使用率的具体含义,相关场景CPU使用率的计算及通过一个案例,学习在CPU使用率过高时,如何进行分析,然后进行相关优化。

CPU使用率

我们知道,Linux作为一个多任务操作系统,将每个CPU时间划分成很短的时间片段,再通过调度器轮流分配给各个任务使用,造成多任务同时运行的错觉。

Linux使用节拍率(HZ)来维护CPU时间,每次触发时间中断,Jiffies变量会加一。Jiffies是一个全局变量,记录的是开机以来的节拍数。我们可以通过查询/boot/config-$(uname -r)内核文件来查看节拍率, 可以看到我主机的节拍率是250HZ,也就是每秒会触发250次时间中断。

$ grep '' /boot/config-$(uname -r)

节拍率

但是由于节拍率是内核选项参数,用户空间不能直接访问。为了方便用户空间程序,内核提供一个用户空间的节拍率USER_HZ,USER_HZ总是等于100,也就是1/100秒。

不同场景的CPU使用率含义

使用mpstat命令,我们发现一共存在10中场景下的CPU使用率。

不同场景的CPU使用率含义

下面主要介绍一下几个常用的场景:

  • user(us, usr): 表示用户态时间。不包括nice时间
  • nice(ni): 表示低优先级用户态时间.
  • system(sys): 内核态CPU时间
  • idle: 空闲时间。不包含等待IO时间(iowait)
  • iowait(wa): 等待IO的CPU时间
  • irq(hi): 处理硬件中断的CPU时间
  • sofirq(si): 处理软中断的CPU时间

CPU使用率的计算公式

我们可以通过查看/proc/stat文件查看相关的CPU时间信息

$ cat /proc/stat

CPU时间信息

通过man proc查看相关类字段说明

$ man proc

CPU时间信息说明

根据说明,我们可以知道,这些数值的单位是100HZ,以及各个场景的CPU时间(开机以来的总时间)

根据这些数据,我们可以很轻易的算出各个场景的平均CPU使用率。

$$平均CPU使用率 = 1- \frac{空闲时间(idle)}{总CPU时间}$$

但是由于/proc/stat中的数据是自开机以来的总时间,不能准确的表示当前CPU的平均使用率,所以为了准确表示CPU的平均的使用率,我们需要取一段小的时间间隔的两次值,做差后,在计算出这段时间内的平均CPU使用率, 即:

$$平均CPU使用率 = 1 - \frac{空闲时间(idle)_{new} - 空闲时间(idle)_{old}}{总CPU时间_{new} - 总CPU时间_{old}}$$

查看平均使用率

  1. 使用top查看系统平均CPU使用率和进程的CPU平均使用率
$ top
  1. 使用ps查看进程的CPU平均使用率
$ ps -aux
  1. 使用pidstat查看进程的CPU平均使用率
$ pidstat -u
  1. 使用mpstat查看系统平均CPU使用率和各个逻辑CPU平均使用率(指标最全)
$ mpstat -P ALL

案例分析(CPU使用率过高怎么办?)

通过我们上面提及的相关工具找出CPU使用率高的进程之后,我们需要知道这个进程中的哪个函数占用了过高的CPU,然后才能更高效、更有针对性的进行优化。

下面主要介绍一款相关的分析工具perf.

perf 以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。

perf每隔一个固定的时间,就在CPU上(每个核上都有)产生一个中断,在中断上看看,当前是哪个pid,哪个函数,然后给对应的pid和函数加一个统计值,这样,我们就知道CPU有百分几的时间在某个pid,或者某个函数上了。

我们主要使用perf一下命令:

# 实时显示占用CPU时间最多的函数或指令及调用关系
$ perf top -g 

由于perf top不能保存数据,不能用于离线后者后续的分析,perf提供了另外两个命令:

  • perf record: 记录系统的性能信息,退出后,将数据保存在执行命令的当前文件夹的perf.data文件中
  • perf report: 展示perf record命令记录的数据

perf top数据信息

字段说明:

  • symbol: 符号名,也就是函数名,当函数名未知时,使用16进制地址表示
  • Children: 表示该符号名下调用函数性能事件在所有采样中的比例

我们主要关注上述的两个指标,找出占比最高的行,即可确定CPU占用最高的函数,然后就可以去应用程序源码中寻找,然后进行相关优化。

案例

这个案例需要两台虚机,其中一台作为Web服务器,用来模拟性能问题。另一台作为Web服务器的客户端,用来给Web服务器添加压力请求。

两台虚机

  1. 环境启动

为了方便,使用docker进行相关部署。在Web服务器上进行以下操作,启动Web服务

# 拉取代码
git clone https://github.com/feiskyer/linux-perf-examples.git

# 打开相关文件夹
cd linux-perf-examples/nginx-high-cpu/

# 创建相关镜像
sudo make build

# 启动容器

sudo make run

然后在Web服务器客户端访问相关服务:

$ curl http://Web服务器IP:10000

如果返回It works!说明环境已经启动完毕。

  1. 测试Web服务器的服务性能
# 并发10个请求测试Nginx性能,总共测试100个请求
$ ab -c 10 -n 100 http://Web服务器IP:10000/

Nginx服务器的性能

ab的输出中我们可以看到,Nginx能承受的每秒平均请求数只有27.47,性能很差!所以我们开始进行分析,找出问题所在。

为了持续的给Nginx服务器增压,我们使用以下命令,并发10个请求,持续10分钟

$ ab -c 10 -t 600 http://Web服务器IP:10000/

然后在Nginx服务器上使用top命令查看CPU的具体使用情况

$ top

可以发现,4个逻辑CPU使用率都为100%,占用CPU时间最多的是php-fpm进程,与我们预想的一样,用户空间的php-fpm进程导致CPU使用率过高。

然后我们在Nginx服务器使用perf命令查找php-fpm进程中,占用CPU最多的函数

$ sudo perf top -g

根据输出结果,我们发现没有发现相关的函数名,只有16进制的地址。这是因为perf无法找到待分析进程所依赖的库,所有的依赖库都在docker容器中,为了方便起见,我们可以在容器外面保存下相关数据(perf.data),再拷贝到容器中查看结果。

# 记录分析数据, 15秒后,使用CTRL + C结束记录
$ sudo perf record -g

# 将perf.data拷贝到容器中
$ sudo docker cp ./perf.data phpfpm:/tmp/
# 进入docker容器中
$ sudo docker exec -it phpfmp bash
# 在容器中安装perf
$  cd /tmp/
$  apt-get update && apt-get install -y linux-perf linux-tools procps
# 查看perf.data数据
$ perf_4.9 report

可以发现是sqrt函数占用的CPU最多, 所以在源码中查找sqrt函数

$ cd linux-perf-examples/nginx-high-cpu/

$ grep sqrt -r app

发现sqrt函数在index.php文件中,然后进入index.php文件, 发现测试代码没有删除就直接发布了,这可能就是引起问题的原因,删除多余的代码。

$ vim index.php
<?php
// test only.
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
  $x += sqrt($x);
}
echo "It works!"
?>

删除//test only以下的代码,然后重新创建docker镜像,运行docker容器。然后在Web服务器客户端给Web服务器添加压力请求。

可以发现平均请求数从原来的27.47升到了512.94,性能有了很大的提升。

但是有的时候系统的CPU使用率很高,但是会找不到高CPU的应用

因为短时应用就可以导致这样的问题:

  • 应用里直接调用了其他的二进制程序,这些程序运行时间通常较短,通过top等工具很难发现
  • 应用本身在不停的崩溃重启,而启动过程的资源初始化,可能会占用相当多的CPU.

这类问题的分析方法:

  • 使用perf top或者execsnoop命令找出短时进程
  • 通过pstree命令查找短时进程的父进程,最后对其父进程进行相关的分析和优化.

小结

我们需要弄清楚各个场景下各个CPU使用率指标用户的含义,这个会给我们分析问题的时候提供一个很明确的方向。

  • 用户CPU或者nice CPU很高,说明用户态进程占用CPU较多,应该重点排查进程的性能问题
  • 系统CPU过高,说明内核态占用较多的内存,应该重点排查内核线程或者系统调用的性能问题
  • IO等待CPU过高,说明IO等待时间过长,应该重点排查系统存储IO、网络IO是否除了问题
  • 软中断、硬件中断CPU过高,说明中断处理程序占用了过多的CPU,应该重点排查内核中的中断服务程

参考链接

极客时间-基础篇:某个应用的CPU使用率居然达到100%,我该怎么办?

在Linux下做性能分析3:perf

请我喝杯咖啡

取消

感谢您的支持,我会继续写出更优秀的文章!

扫码支持
扫码支持
请我喝杯咖啡

打开微信扫一扫,即可进行扫码打赏哦

comments powered by Disqus