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使用率,包括:

  1. %usr
    用户态CPU使用率

  2. %system
    内核态CPU使用率

  3. %guest
    运行虚拟机CPU使用率

  4. %wait
    等待CPU使用率

  5. %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

采样数需要特别注意
如果采样数过少(比如只有十几个),那下面的排序和百分比就没什么实际参考价值

再往下看是一个表格式样的数据,每一行包含四列,分别是:

  1. 第一列 Overhead

该符号的性能事件在所有采样中的比例,用百分比来表示

  1. 第二列 Shared

该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等

  1. 第三列 Object

动态共享对象的类型,如[.] 表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间

  1. 第四列 Symbol

符号名,也就是函数名,当函数名未知时,用十六进制的地址来表示

还是以上面的输出为例,可以看到占用CPU时钟最多的是perf工具自身,不过它的比例也只有7.28%,说明系统并没有CPU性能问题

  • perf record和perf report

接着看看第二种常见用法,也就是perf recordperf 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 topperf 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的调用关系,可以发现调用关系最终到了sqrtadd_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使用率比较高:

  1. 用户CPU使用率(us) 达到了80%
  2. 系统CPU为15.1%
  3. 空闲 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在变,这说明:

  • 要么是这些进程在不停地重启
  • 要么就是全新的进程

这无非也就两个原因:

  1. 进程在不停地崩溃重
    进程在不停地崩溃重启,比如因为段错误、配置错误等等,这时,进程在退出后可能又被监控系统自动重启了

  2. 短时进程
    短时进程也就是在其他应用内部通过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]内存缓存占用过高分析和优化

java性能分析 - CPU飙高分析工具

java进程占用cpu过高分析是哪些线程

Linux下java进程CPU占用率高分析方法

Linux [buff/cache]内存缓存占用过高分析和优化

性能测试 | 服务器CPU使用率高分析实例