1、理解
CPU使用率是单位时间内CPU使用情况的统计,以百分比的方式展示。
Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。
为了维护CPU时间,Linux通过事先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。
节拍率HZ是内核的可配置选项,可以设置为100、250、1000等。不同的系统可能设置不同的值,可以通过查询 /boot/config 内核选项来查看它的配置值。比如,节拍率设置为 250,也就是每秒触发 250次时间中断。
同时,因为节拍率HZ是内核选项,所以用户空间不能直接访问,为了方便用户空间程序,内核提供了一个用户空间节拍率USER_HZ,固定100,也就是 1/100 秒。这样,用户空间程序不关心内核中HZ被设置成了多少,因为它看到的总是固定值USER_HZ。
Linux通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态信息,而 /proc/stat 提供的就是系统的CPU和任务统计信息。
其中,第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。
2、proc参数含义
| 名称 | 缩写 | 含义 | 备注 |
|---|---|---|---|
| user | us | 用户态CPU时间 | 不包括下面的nice时间,但包含guest时间 |
| nice | ni | 低优先级用户态CPU时间,也就是进程的nice值被调整为 1-19之间时的CPU时间 | nice的可取值范围是 -20到 19,值越大,优先级越低 |
| system | sys | 内核态CPU时间 | |
| idle | id | 空闲时间 | 不包括等待 I/O 的时间(iowait) |
| iowait | wa | 等待 I/O 的CPU时间 | |
| irq | hi | 处理硬中断的CPU时间 | |
| softirq | si | 处理软中断的CPU时间 | |
| steal | st | 当前系统运行在虚拟机中的时候,被其他虚拟机占用的CPU时间 | |
| guest | guest | 通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的CPU时间 | |
| guest_nice | gnice | 以低优先级运行虚拟机的事件 |
3、CPU使用率计算
CPU使用率,就是除了空闲时间外的其他时间占总CPU时间的百分比,
根据这个公式,可以从 /proc/stat 中的数据,计算出CPU使用率。以及每个场景的CPU使用率。
但是,/proc/stat 的数据,是开机以来的节拍数累加的值,所以直接算出来的时,开机以来的平均CPU使用率。
一般,性能工具会取间隔一段时间(比如3s)的两次值,通过差值计算。
单个进程的计算的话,可以/proc/[pid]/stat中查看。

按下1之后,切换到每个CPU的使用率查看。
top并没有细分进程的用户态CPU和内核态CPU。这里就需要用 pidstat。
- %usr:用户态CPU使用率
- %system:内核态CPU使用率
- %guest:运行虚拟机CPU使用率
- %wait:等待CPU使用率
- %CPU:总得CPU使用率
5、CPU使用率过高怎么办
# 能够实时显示占用CPU时钟最多的函数或指定,可以用来查看热点函数perf top

包含三个数据:采样数(Samples)、事件类型(event)和事件总数量(Event count)。
如图中,perf总共采集了271个CPU时钟事件、而总时间数为42634171 。
下面表格中,每行四列数据,分别是:
- Overhead:该符号的性能事件在所有采样中的比例,用百分比表示
- Shared:该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名】内核模块名等。
- Object:是动态共享对象的类型。如 [.] 表示用户空间的可执行程序、或者动态链接库, [k] 表示内核空间
- Symbol:函数名,当函数名未知时,用十六进制地址表示。
也可以生成报告
$ perf record # 按Ctrl+C终止采样[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0.452 MB perf.data (6093 samples) ]$ perf report # 展示类似于perf top的报告
为 perf top 和 perf record 加上 -g 参数,开启调用关系的采样,方便我们根据调用链来分析性能问题。
6、Demo:easy
1、准备
预先安装docker、sysstat、perf、ab 等工具
apt install docker.io sysstat linux-tools-common apache2-utils
docker安装Nginx和PHP应用:
docker run --name nginx -p 10000:80 -itd feisky/nginxdocker run --name phpfpm -itd --network container:nginx feisky/php-fpm
2、分析

通过ab命令,并发10个请求测试Nginx性能,总共测试100个请求,可以看到Nginx能承受的每秒平均请求数只有17 ,是非常差劲的。将请求增加到10000个
通过top命令,可以看到每个CPU的用户使用率(us)都超过了98%,系统中有几个 php-fpm 进程的CPU使用率加起来接近 200%,可以确定,正是用户空间的 php-
定位查找这两个函数
# 从容器phpfpm中将PHP源码拷贝出来$ docker cp phpfpm:/app .# 使用grep查找函数调用$ grep sqrt -r app/ #找到了sqrt调用app/index.php: $x += sqrt($x);$ grep add_function -r app/ #没找到add_function调用,这其实是PHP内置函数
那就在app/index.php中查找问题即可
.
很明显,是这里,循环的问题,因为该逻辑是个测试逻辑。
3、修复
# 停止原来的应用$ docker rm -f nginx phpfpm# 运行优化后的应用,去掉循环的测试逻辑$ docker run --name nginx -p 10000:80 -itd feisky/nginx:cpu-fix$ docker run --name phpfpm -itd --network container:nginx feisky/php-fpm:cpu-fix

可以看到,每秒的平均请求数,已经从原来的17 变成了 689 。
7、Demo:hard
1、准备
docker run --name nginx -p 10000:80 -itd feisky/nginx:spdocker run --name phpfpm -itd --network container:nginx feisky/php-fpm:sp
# 并发100个请求测试Nginx性能,总共测试1000个请求ab -c 100 -n 1000 http://127.0.0.1:10000/
2、分析
1、top+pidstat
# 加上 -t 参数,设置请求时长,10分钟ab -c 5 -t 600 http://127.0.0.1:10000/
通过 top和 pidstat来查看

可以看到CPU使用率在60%以上,但是我们的服务,nginx和php-fpm确只占3%左右。
pidstat中看到,所有的任务的CPU使用率加起来也才 30% 左右。
这里需要注意:
tasks的任务队列中,有5个正在跑(running)的任务,可是占用cpu前几的任务都是大部分处于睡眠(sleep)的状态。
再观察top,会发现有一个 stress 的进程很奇怪。
使用 pidstat -p 24344和 ps aux | grep 24344都发现,这个进程又不见了,说明该进程在不停的更新。要么,进程在不停地崩溃、重启,要么,都是短时进程,如exec调用外部命令。
通过pstree查看进程调用关系,查找到 stress 的父进程是php-fpm。就需要分析该进程的源码逻辑。
# 拷贝源码到本地
$ docker cp phpfpm:/app .
# grep 查找看看是不是有代码在调用stress命令
$ grep stress -r app
app/index.php:// fake I/O with stress (via write()/unlink()).
app/index.php:$result = exec("/usr/local/bin/stress -t 1 -d 1 2>&1", $output, $status);

发现每个请求都会调用一个 stress 命令,模拟 I/O 压力。
再发现错误消息 mkstemp failed: Permission denied ,以及 failed run completed in 0s。stress 命令因为权限问题退出了。
2、perf
上面方法考验对异常进程的识别能力,因此,可以使用
# 记录性能事件,等待大约15秒后按 Ctrl+C 退出
perf record -g
# 查看报告
perf report

可以看到stress占了所有CPU时钟的63.40%,其调用栈最高额是random()函数,这样便很快的找到了了原因。进行进一步优化即可。
8、小结
- 用户CPU(%user)和Nice CPU(%nice)高,说明用户态进程占用了较多的CPU,所以应该着重排查进程的性能问题
- 系统CPU(%system)高,说明内核态占用了较多的CPU,所以应该着重排查内核线程或者系统调用的性能问题
- I/O 等待(%iowait)CPU高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题
- 软中断(%irq)和硬中断(%softirq)高,说明软中断或硬中断的处理程序占用了较多CPU,所以应该着重排查内核中的中断服务程序

