linux性能优化CPU使用率过高分析
Posted sysu_lluozh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux性能优化CPU使用率过高分析相关的知识,希望对你有一定的参考价值。
最常用什么指标来描述系统的 CPU 性能呢?
可能不是平均负载,也不是 CPU上下文切换,而是另一个更直观的指标CPU使用率
CPU使用率是单位时间内CPU使用情况的统计,以百分比的方式展示
那么,作为最常用也是最熟悉的CPU指标,CPU使用率到底是怎么算出来的呢?再有,诸如top、ps之类的性能工具展示的%user、%nice、%system、%iowait 、%steal等等,它们之间的不同在哪里?
接下来了解CPU使用率的内容,同时,以最常用的反向代理服务器nginx为例,一步步操作和分析中深入理解
一、CPU使用率
Linux作为一个多任务操作系统,将每个CPU的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉
1.1 节拍率
为了维护 CPU 时间,Linux通过事先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量Jiffies记录了开机以来的节拍数,每发生一次时间中断,Jiffies的值就加1
- 系统节拍率
节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值,可以通过查询/boot/config
内核选项来查看它的配置值。比如在系统中,节拍率设置成了250,也就是每秒钟触发 250 次时间中断
$ grep 'CONFIG_HZ=' /boot/config‑$(uname ‑r)
CONFIG_HZ=250
- 用户节拍率
因为节拍率HZ是内核选项,所以用户空间程序并不能直接访问
为了方便用户空间程序,内核还提供了一个用户空间节拍率USER_HZ,总是固定为100,也就是1/100 秒
这样,用户空间程序并不需要关心内核中HZ被设置成了多少,因为它看到的总是固定值USER_HZ
1.2 /proc/stat任务统计
Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而/proc/stat 提供的就是系统的CPU和任务统计信息。比方说,如果只关注CPU的话,可以执行下面的命令:
# 只保留各个CPU的数据
$ cat /proc/stat | grep ^cpu
cpu 280580 7407 286084 172900810 83602 0 583 0 0 0
cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0
cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0
这里的输出结果是一个表格。其中
- 第一列表示的是CPU编号,如cpu0、cpu1
- 第一行没有编号的cpu,表示的是所有CPU的累加
- 其他列则表示不同场景下CPU的累加节拍数,单位是USER_HZ,也就是10 ms(1/100秒),所以这其实就是不同场景下的CPU 时间
1.3 相关的重要指标
这里每一列的顺序并不需要背下来,有需要的时候查询man proc即可
不过,要清楚man proc文档里每一列的涵义,它们都是CPU使用率相关的重要指标,还会在很多其他的性能工具中看到,下面依次解读一下:
- 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)
代表以低优先级运行虚拟机的时间
1.4 CPU使用率计算
通常所说的CPU使用率,就是除了空闲时间外的其他时间占总CPU时间的百分比,用公式来表示就是:
根据这个公式,可以从/proc/stat
中的数据,很容易地计算出CPU使用率
当然,也可以用每一个场景的CPU时间,除以总的CPU时间,计算出每个场景的CPU使用率
不过先不要着急计算,直接用/proc/stat
的数据,算的是什么时间段的CPU使用率吗?
/proc/stat
统计的是开机以来的节拍数累加值,所以直接算出来的是开机以来的平均CPU使用率,一般没啥参考价值
事实上,为了计算CPU使用率,性能工具一般都会取间隔一段时间(比如3秒)的两次值,作差后,再计算出这段时间内的平均CPU使用率,即
这个公式,就是平常用各种性能工具所看到的CPU使用率的实际计算方法
1.5 进程CPU使用率计算
现在,知道了系统CPU使用率的计算方法,那进程的呢?
跟系统的指标类似,Linux也给每个进程提供了运行情况的统计信息,也就是/proc/[pid]/stat
不过,这个文件包含的数据就比较丰富了,总共有 52 列的数据
当然,并不需要掌握每一列的含义,需要的时候查man proc
就可以了
1.6 CPU使用率工具对比
那是不是说要查看CPU使用率,就必须先读取/proc/stat
和/proc/[pid]/stat
这两个文件,然后再按照上面的公式计算出来呢?
当然不是,各种各样的性能分析工具已经帮我们计算好了
不过要注意的是,性能分析工具给出的都是间隔一段时间的平均CPU使用率,所以要注意间隔时间的设置,特别是用多个工具对比分析时,一定要保证它们用的是相同的间隔时间
比如,对比一下top和ps这两个工具报告的CPU使用率,默认的结果很可能不一样,因为top默认使用 3 秒时间间隔,而ps使用的却是进程的整个生命周期
二、怎么查看CPU使用率
知道了CPU 使用率的含义,再来看看要怎么查看CPU使用率
说到查看CPU使用率的工具,第一反应肯定是top和ps
的确,top和ps是最常用的性能分析工具:
- top显示了系统总体的CPU和内存使用情况,以及各个进程的资源使用情况
- ps显示了每个进程的资源使用情况
2.1 top
top 的输出格式为:
# 默认每3秒刷新一次
$ top
top ‑ 11:58:59 up 9 days, 22:47, 1 user, load average: 0.03, 0.02, 0.00
Tasks: 123 total, 1 running, 72 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8169348 total, 5606884 free, 334640 used, 2227824 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 7497908 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 78088 9288 6696 S 0.0 0.1 0:16.83 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.05 kthreadd
4 root 0 ‑20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
...
这个输出结果中,第三行%Cpu
就是系统的CPU使用率,具体每一列的含义可以看1.3 相关的重要指标的说明
不过需要注意,top默认显示的是所有CPU的平均值,这个时候需要按下数字1,可以切换到每个CPU的使用率
继续往下看,空白行之后是进程的实时信息,每个进程都有一个%CPU
列,表示进程的CPU使用率。它是用户态和内核态CPU使用率的总和,包括进程用户空间使用的CPU、通过系统调用执行的内核空间CPU 、在就绪队列等待运行的CPU。在虚拟化环境中,还包括运行虚拟机占用的CPU
2.2 pidstat
可以发现,top并没有细分进程的用户态CPU和内核态CPU
那要怎么查看每个进程的详细情况呢?可以使用工具pidstat
分析每个进程CPU使用情况
比如,下面的pidstat
命令,间隔1秒展示进程的5组CPU使用率,包括:
-
%usr
用户态CPU使用率 -
%system
内核态CPU使用率 -
%guest
运行虚拟机CPU使用率 -
%wait
等待CPU使用率 -
%CPU
总的CPU使用率
最后的Average部分,还计算了5组数据的平均值
# 每隔1秒输出一组数据,共输出5组
$ pidstat 1 5
15:56:02 UID PID %usr %system %guest %wait %CPU CPU Command
15:56:03 0 15006 0.00 0.99 0.00 0.00 0.99 1 dockerd
...
Average: UID PID %usr %system %guest %wait %CPU CPU Command
Average: 0 15006 0.00 0.99 0.00 0.00 0.99 ‑ dockerd
三、CPU使用率过高怎么办
通过 top、ps、pidstat等工具,能够轻松找到CPU使用率较高(比如100%)的进程
接下来,占用CPU的到底是代码里的哪个函数呢?找到它才能更高效、更针对性地进行优化
3.1 GDB
第一个想到的应该是GDB(The GNU Project Debugger), 这个功能强大的程序调试利器
的确,GDB在调试程序错误方面很强大。但是,GDB 并不适合在性能分析的早期应用
为什么呢?因为GDB调试程序的过程会中断程序运行,这在线上环境往往是不允许的
GDB只适合用在性能分析的后期,当找到出问题的大致函数后,线下再借助它来进一步调试函数内部的问题
3.2 perf
那么哪种工具适合在第一时间分析进程的CPU问题呢?推荐是perf
perf是Linux 2.6.31以后内置的性能分析工具
它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题
使用perf分析CPU性能问题,接下来说明一下两种最常见的用法
- perf top
第一种常见用法是perf top,类似于top,能够实时显示占用CPU时钟最多的函数或者指令,因此可以用来查找热点函数,使用界面如下所示:
$ perf top
Samples: 833 of event 'cpu‑clock', Event count (approx.): 97742399
Overhead Shared Object Symbol
7.28% perf [.] 0x00000000001f78a4
4.72% [kernel] [k] vsnprintf
4.32% [kernel] [k] module_get_kallsym
3.65% [kernel] [k] _raw_spin_unlock_irqrestore
...
输出结果中,第一行包含三个数据,分别是:
- 采样数(Samples)
- 事件类型(event)
- 事件总数量(Event count)
比如这个例子中,perf总共采集了833个CPU时钟事件,而总事件数则为97742399
采样数需要特别注意
如果采样数过少(比如只有十几个),那下面的排序和百分比就没什么实际参考价值
再往下看是一个表格式样的数据,每一行包含四列,分别是:
- 第一列 Overhead
该符号的性能事件在所有采样中的比例,用百分比来表示
- 第二列 Shared
该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等
- 第三列 Object
动态共享对象的类型,如[.] 表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间
- 第四列 Symbol
符号名,也就是函数名,当函数名未知时,用十六进制的地址来表示
还是以上面的输出为例,可以看到占用CPU时钟最多的是perf工具自身,不过它的比例也只有7.28%,说明系统并没有CPU性能问题
- perf record和perf report
接着看看第二种常见用法,也就是perf record
和perf report
perf top虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或者后续的分析
perf record: 提供保存数据的功能
perf report: 解析展示保存的数据
$ 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
参数,开启调用关系的采样,方便根据调用链来分析性能问题
四、案例一
下面以Nginx + php的Web服务为例,看看当CPU使用率过高的问题后,要怎么使用top等工具找出异常的进程,又要怎么利用perf 找出引发性能问题的函数
4.1 背景介绍
- ab工具
ab(apache bench)是一个常用的HTTP服务性能测试工具,这里用来模拟Ngnix的客户端
由于Nginx和PHP的配置比较麻烦,所以打包成了两个Docker镜像,这样只需要运行两个容器,就可以得到模拟环境
一台用作Web服务器,来模拟性能问题
一台用作Web服务器的客户端,来给Web服务增加压力请求
4.2 操作
首先,在第一个终端执行下面的命令来运行Nginx和PHP应用:
$ docker run ‑‑name nginx ‑p 10000:80 ‑itd feisky/nginx
$ docker run ‑‑name phpfpm ‑itd ‑‑network container:nginx feisky/php‑fpm
然后,在第二个终端使用curl访问http://[VM1的IP]:10000,确认Nginx已正常启动
# 192.168.0.10是第一台虚拟机的IP地址
$ curl http://192.168.0.10:10000/
It works!
接着,开始测试Nginx服务的性能,在第二个终端运行下面的ab命令:
# 并发10个请求测试Nginx性能,总共测试100个请求
$ ab ‑c 10 ‑n 100 http://192.168.0.10:10000/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd,
...
Requests per second: 11.63 [#/sec] (mean)
Time per request: 859.942 [ms] (mean)
...
从ab的输出结果可以看到,Nginx能承受的每秒平均请求数只有11.63
这qps也太差了吧,那到底是哪里出了问题呢?使用top和pidstat观察一下
4.3 top分析进程cpu
这次,在第二个终端将测试的请求总数增加到10000,这样当在第一个终端使用性能分析工具时,Nginx的压力还是继续
继续在第二个终端,运行ab命令:
$ ab ‑c 10 ‑n 10000 http://10.240.0.5:10000/
接着,回到第一个终端运行top命令,并按下数字1,切换到每个CPU的使用率:
$ top
...
%Cpu0 : 98.7 us, 1.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21514 daemon 20 0 336696 16384 8712 R 41.9 0.2 0:06.00 php‑fpm
21513 daemon 20 0 336696 13244 5572 R 40.2 0.2 0:06.08 php‑fpm
21515 daemon 20 0 336696 16384 8712 R 40.2 0.2 0:05.67 php‑fpm
21512 daemon 20 0 336696 13244 5572 R 39.9 0.2 0:05.87 php‑fpm
21516 daemon 20 0 336696 16384 8712 R 35.9 0.2 0:05.61 php‑fpm
根据top命令数据可以看到:
- 系统中有几个php-fpm进程的CPU使用率加起来接近 200%
- 每个CPU的用户使用率(us)也已经超过了98%,接近饱和
这样,可以确认正是用户空间的php-fpm进程,导致CPU使用率骤升
4.4 perf top分析问题函数
那再往下走,怎么知道是php-fpm的哪个函数导致了CPU使用率升高呢?使用perf分析一下
在第一个终端运行下面的perf命令:
# ‑g开启调用关系分析,‑p指定php‑fpm的进程号21515
$ perf top ‑g ‑p 21515
按方向键切换到php-fpm,再按下回车键展开php-fpm的调用关系,可以发现调用关系最终到了sqrt
和add_function
看来需要从这两个函数入手
拷贝出Nginx应用的源码 ,看看是不是调用了这两个函数:
# 从容器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内置函数
原来只有sqrt
函数在app/index.php
文件中调用
那么最后一步,需要看看这个文件的源码:
$ cat app/index.php
<?php
// test only.
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
$x += sqrt($x);
}
echo "It works!"
有没有发现问题在哪里呢?竟然是测试代码没删就直接发布应用了
4.5 问题修复验证
为了方便你验证优化后的效果,把修复后的应用也打包成了一个Docker镜像,可以在第一个终端中执行下面的命令来运行:
# 停止原来的应用
$ 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
接着,到第二个终端来验证一下修复后的效果
首先Ctrl+C停止之前的ab命令后,再运行下面的命令:
$ ab ‑c 10 ‑n 10000 http://10.240.0.5:10000/
...
Complete requests: 10000
Failed requests: 0
Total transferred: 1720000 bytes
html transferred: 90000 bytes
Requests per second: 2237.04 [#/sec] (mean)
Time per request: 4.470 [ms] (mean)
Time per request: 0.447 [ms] (mean, across all concurrent requests)
Transfer rate: 375.75 [Kbytes/sec] received
...
可以发现,现在每秒的平均请求数,已经从原来的11变成2237
这么很小的问题,却会极大的影响性能,并且查找起来也并不容易
五、案例二
使用top、vmstat、pidstat等工具,排查高CPU使用率的进程,然后再使用perf top
工具,定位应用内部函数的问题
5.1 背景介绍
似乎感觉高 CPU 使用率的问题,还是挺容易排查的。那是不是所有CPU使用率高的问题,都可以这么分析呢?答案应该是否定的
因为当发现系统的CPU使用率很高的时候,不一定能找到相对应的高CPU使用率的进程
接下来继续使用一个Nginx + PHP的Web服务的案例,分析一下这种情况
由于Nginx和PHP的配置比较麻烦,所以把它们打包成了两个Docker镜像,这样只需要运行两个容器,就可以得到模拟环境
一台用作Web服务器,来模拟性能问题
一台用作Web服务器的客户端,来给Web服务增加压力请求
5.2 操作
首先,在第一个终端,执行下面的命令运行Nginx和PHP应用:
$ docker run ‑‑name nginx ‑p 10000:80 ‑itd feisky/nginx:sp
$ docker run ‑‑name phpfpm ‑itd ‑‑network container:nginx feisky/php‑fpm:sp
然后,在第二个终端,使用curl访问http://[VM1的IP]:10000,确认Nginx已正常启动
# 192.168.0.10是第一台虚拟机的IP地址
$ curl http://192.168.0.10:10000/
It works!
接着,测试一下这个 Nginx 服务的性能
在第二个终端运行下面的ab命令,需要并发100个请求测试Nginx性能,总共测试1000个请求
# 并发100个请求测试Nginx性能,总共测试1000个请求
$ ab ‑c 100 ‑n 1000 http://192.168.0.10:10000/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd,
...
Requests per second: 87.86 [#/sec] (mean)
Time per request: 1138.229 [ms] (mean)
...
5.3 top分析
从ab的输出结果可以看到,Nginx能承受的每秒平均请求数,只有87+,到底是哪里出了问题呢?再用top和pidstat观察一 下
在第二个终端,将测试的并发请求数改成5,同时把请求时长设置为10分钟(-t 600),这样当在第一个终端使用性能分析工具时,Nginx的压力还是继续的
在第二个终端运行ab命令:
$ ab ‑c 5 ‑t 600 http://192.168.0.10:10000/
然后,在第一个终端运行top命令,观察系统的CPU使用情况:
$ top
...
%Cpu(s): 80.8 us, 15.1 sy, 0.0 ni, 2.8 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6882 root 20 0 8456 5052 3884 S 2.7 0.1 0:04.78 docker‑containe
6947 systemd+ 20 0 33104 3716 2340 S 2.7 0.0 0:04.92 nginx
7494 daemon 20 0 336696 15012 7332 S 2.0 0.2 0:03.55 php‑fpm
7495 daemon 20 0 336696 15160 7480 S 2.0 0.2 0:03.55 php‑fpm
10547 daemon 20 0 336696 16200 8520 S 2.0 0.2 0:03.13 php‑fpm
10155 daemon 20 0 336696 16200 8520 S 1.7 0.2 0:03.12 php‑fpm
10552 daemon 20 0 336696 16200 8520 S 1.7 0.2 0:03.12 php‑fpm
15006 root 20 0 1168608 66264 37536 S 1.0 0.8 9:39.51 dockerd
4323 root 20 0 0 0 0 I 0.3 0.0 0:00.87 kworker/u4:1
...
观察top输出的进程列表可以发现,CPU使用率最高的进程才2.7%,看起来并不高
然而,再看系统CPU使用率(%Cpu)这一行,可以发现系统的整体CPU使用率比较高:
- 用户CPU使用率(us) 达到了80%
- 系统CPU为15.1%
- 空闲 CPU(id)只有 2.8%
5.4 top分析进程列表
为什么用户CPU使用率这么高呢?再重新分析一下进程列表,看看有没有可疑进程:
- docker-containerd
用来运行容器的,2.7%的CPU使用率看起来正常 - Nginx和php-fpm
运行 Web 服务,它们会占用一些CPU也不意外,并且2%的CPU使用率也不算高 - kworker
只有0.3%的CPU使用率,看起来不太像会导致用户CPU使用率达到 80%
那就奇怪了,用户CPU使用率80%,可挨个分析一遍进程列表,还是找不到高CPU使用率的进程
5.5 pidstat分析进程CPU
看来top是不管用了,那还有其他工具可以查看进程CPU使用情况吗?那么pidstat可以用来分析进程的CPU使用情况
接下来,还是在第一个终端,运行pidstat命令:
# 间隔1秒输出一组数据(按Ctrl+C结束)
$ pidstat 1
...
04:36:24 UID PID %usr %system %guest %wait %CPU CPU Command
04:36:25 0 6882 1.00 3.00 0.00 0.00 4.00 0 docker‑containe
04:36:25 101 6947 1.00 2.00 0.00 1.00 3.00 1 nginx
04:36:25 1 14834 1.00 1.00 0.00 1.00 2.00 0 php‑fpm
04:36:25 1 14835 1.00 1.00 0.00 1.00 2.00 0 php‑fpm
04:36:25 1 14845 0.00 2.00 0.00 2.00 2.00 1 php‑fpm
04:36:25 1 14855 0.00 1.00 0.00 1.00 1.00 1 php‑fpm
04:36:25 1 14857 1.00 2.00 0.00 1.00 3.00 0 php‑fpm
04:36:25 0 15006 0.00 1.00 0.00 0.00 1.00 0 dockerd
04:36:25 0 15801 0.00 1.00 0.00 0.00 1.00 1 pidstat
04:36:25 1 17084 1.00 0.00 0.00 2.00 1.00 0 stress
04:36:25 0 31116 0.00 1.00 0.00 0.00 1.00 0 atopacctd
...
观察一会儿发现,所有进程的CPU使用率也都不高,最高的Docker和Nginx也只有4%和3%,即使所有进程的CPU使用率都加起来,也不过是21%,离80%还差得远呢
明明用户CPU使用率已经高达 80%,但却怎么都找不到是哪个进程的问题。还能不能再做进一步的分析呢?
5.6 top分析进程状态
很可能是因为前面的分析漏了一些关键信息,先暂停一下,重新操作检查一遍,或者返回去分析top的输出,看看能不能有新发现
现在,回到第一个终端,重新运行top命令,并观察一会儿:
$ top
top ‑ 04:58:24 up 14 days, 15:47, 1 user, load average: 3.39, 3.82, 2.74
Tasks: 149 total, 6 running, 93 sleeping, 0 stopped, 0 zombie
%Cpu(s): 77.7 us, 19.3 sy, 0.0 ni, 2.0 id, 0.0 wa, 0.0 hi, 1.0 si, 0.0 st
KiB Mem : 8169348 total, 2543916 free, 457976 used, 5167456 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 7363908 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6947 systemd+ 20 0 33104 3764 2340 S 4.0 0.0 0:32.69 nginx
6882 root 20 0 12108 8360 3884 S 2.0 0.1 0:31.40 docker‑containe
15465 daemon 20 0 336696 15256 7576 S 2.0 0.2 0:00.62 php‑fpm
15466 daemon 20 0 336696 15196 7516 S 2.0 0.2 0:00.62 php‑fpm
15489 daemon 20 0 336696 16200 8520 S 2.0 0.2 0:00.62 php‑fpm
6948 systemd+ 20 0 33104 3764 2340 S 1.0 0.0 0:00.95 nginx
15006 root 20 0 1168608 65632 37536 S 1.0 0.8 9:51.09 dockerd
15476 daemon 20 0 336696 16200 8520 S 1.0 0.2 0:00.61 php‑fpm
15477 daemon 20 0 336696 16200 8520 S 1.0 0.2 0:00.61 php‑fpm
24340 daemon 20 0 8184 1616 536 R 1.0 0.0 0:00.01 stress
24342 daemon 20 0 8196 1580 492 R 1.0 0.0 0:00.01 stress
24344 daemon 20 0 8188 1056 492 R 1.0 0.0 0:00.01 stress
24347 daemon 20 0 8184 1356 540 R 1.0 0.0 0:00.01 stress
...
从头开始看top的每行输出,Tasks这一行看起来有点奇怪,就绪队列中居然有6个Running状态的进程(6 running),是不是有点多呢?
ab测试的参数中并发请求数是5,再看进程列表里,php-fpm的数量也是5,再加上Nginx,好像同时有6个进程也并不奇怪,但真的是这样吗?
再仔细看进程列表,这次主要看Running(R)
状态的进程
可以发现Nginx和所有的php-fpm都处于Sleep(S)
状态,而真正处于Running®状态的,却是几个stress进程,这几个stress进程就比较奇怪了,需要做进一步的分析
5.7 pidstat分析具体进程
还是使用pidstat来分析这几个进程,并且使用-p选项指定进程的PID
首先,从上面top的结果中,找到这几个进程的PID,比如随便找一个 24344,然后用pidstat命令看一下CPU的使用情况:
$ pidstat ‑p 24344
16:14:55 UID PID %usr %system %guest %wait %CPU CPU Command
奇怪,居然没有任何输出,难道是pidstat命令出问题了么?
5.8 ps分析具体进程
在怀疑性能工具出问题前,还是先用其他工具交叉确认一下。那用什么工具呢?
ps应该是最简单易用的,在终端里运行下面的命令,看看24344进程的状态:
# 从所有进程中查找PID是24344的进程
$ ps aux | grep 24344
root 9628 0.0 0.0 14856 1096 pts/0 S+ 16:15 0:00 grep ‑‑color=auto 24344
还是没有输出
现在终于发现问题,原来这个进程已经不存在了,所以pidstat就没有任何输出
既然进程都没了,那性能问题应该也跟着没了吧
5.9 top确认进程变化
再用top命令确认一下:
$ top
...
%Cpu(s): 80.9 us, 14.9 sy, 0.0 ni, 2.8 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6882 root 20 0 12108 8360 3884 S 2.7 0.1 0:45.63 docker‑containe
6947 systemd+ 20 0 33104 3764 2340 R 2.7 0.0 0:47.79 nginx
3865 daemon 20 0 336696 15056 7376 S 2.0 0.2 0:00.15 php‑fpm
6779 daemon 20 0 8184 1112 556 R 0.3 0.0 0:00.01 stress
...
好像又错了
结果还跟原来一样,用户CPU使用率还是高达80.9%,系统 CPU 接近15%,而空闲CPU只有2.8%,Running状态的进程有Nginx、stress等
可是,刚刚看到stress进程不存在了,怎么现在还在运行呢?
再细看一下top的输出,原来这次stress进程的PID跟前面不一样了,原来的PID24344不见了,现在的是6779
5.10 进程PID不断变化
进程的PID在变,这说明:
- 要么是这些进程在不停地重启
- 要么就是全新的进程
这无非也就两个原因:
-
进程在不停地崩溃重
进程在不停地崩溃重启,比如因为段错误、配置错误等等,这时,进程在退出后可能又被监控系统自动重启了 -
短时进程
短时进程也就是在其他应用内部通过exec调用的外面命令,这些命令一般都只运行很短的时间就会结束,很难用top这种间隔时间比较长的工具发现(上面的案例中碰巧被发现了这些进程)
5.11 pstree查看父进程
至于stress是一个常用的压力测试工具
stress的PID在不断变化中,看起来像是被其他进程调用的短时进程,要想继续分析下去,还得找到它们的父进程
要怎么查找一个进程的父进程呢?没错,用pstree就可以用树状形式显示所有进程之间的关系:
$ pstree | grep stress
|‑docker‑containe‑+‑php‑fpm‑+‑php‑fpm‑‑‑sh‑‑‑stress
| |‑3*[php‑fpm‑‑‑sh‑‑‑stress‑‑‑stress]
从这里可以看到,stress是被php-fpm调用的子进程,并且进程数量不止一个(这里是3个)
找到父进程后,可以进入app的内部分析
5.12 定位问题源码
首先,当然应该去看看它的源码
运行下面的命令,把案例应用的源码拷贝到app目录,然后再执行grep查找是不是有代码再调用stress命令:
# 拷贝源码到本地
$ 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);
可以发现果然是app/index.php
文件中直接调用了stress命令
再来看看app/index.php的源代码:
$ cat app/index.php
<?php
// fake I/O with stress (via write()/unlink()).
$result = exec("/usr/local/bin/stress ‑t 1 ‑d 1 2>&1", $output, $status);
if (isset($_GET["verbose"]) && $_GET["verbose"]==1 && $status != 0) {
echo "Server internal error: ";
Linux [buff/cache]内存缓存占用过高分析和优化