CPU使用率100%排查方法

本篇文章主要介绍了CPU使用率的排查方法、需要做的前期准备及注意事项。
上篇文章回顾: bats-Bash自动化测试工具

                              1、CPU使用率

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

为了维护CPU时间,Linux通过事先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量Jiffies记录了开机以来的节拍数。每发生一次时间中断,Jiffies的值就加1。

节拍率HZ是内核的可配选项,可以自定义配置,可通过/boot/config来查询

$ grep 'CONFIG_HZ=' /boot/config-$(uname -r)
CONFIG_HZ=1000复制代码

CPU使用率有很多重要指标,具体含义如下:

  • user(通常缩写为us),代表用户态CPU时间。注意,它包括下面的nice时间,但

包括了guest时间。

  • nice(通常缩写为ni),代表低优先级用户态CPU时间,也就是进程的nice值被调

    整为1-19之间是的CPU时间。

  • 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时间

而我们通常所说的CPU使用率,就是除了空闲时间外的其他时间占总CPU时间的百分比,用公式表示为:

上面这个计算方式是不具备参考意义的,因为总CPU时间是机器开机以来的,事实上,为了计算CPU使用率,性能工具都会取间隔一段时间(比如5秒)的两次值,做差后,再计算出这段时间内的平均CPU使用率,即:

不过需要注意的是,性能分析工具给出的都是间隔一段时间的平均CPU使用率,所以要注意间隔时间的设置,特别是多个工具对比分析时,需要保证它们的间隔时间是相同的。

比如,对比一下top和ps这两个工具报告的CPU使用率,默认的结果可能不一样,因为top默认使用3秒时间间隔,而ps使用的却是进程的整个生命周期。

                            2、查看CPU使用率的方法

知道了cpu使用率的含义后,我们再来看看要怎么查看CPU使用率,说道查看cpu使用率性能工具,首先会想到ps、top。

  • top显示了系统总体的CPU和内存使用情况,以及各个进程的资源使用情况

  • ps则是显示了每个进程的资源使用情况

比如,top的输出格式:

# 默认每 3 秒刷新一次
$ top
top - 11:58:59 up 9 days, 22:47,  1 user,  load average: 0.03, 0.02, 0.00Tasks: 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复制代码

需要注意的,top默认显示的所有CPU的平均值,这个时候只需要按下数字1,就可以切换到每个CPU的使用率了。

继续往下看,空白行之后是进程的实时信息,每个进程都有一个%CPU列,表示进程的CPU使用率,它是用户态和内核态CPU使用率的总和,包括进程用户空间、使用的CPU、通过系统调用执行的内核空间CPU、以及在就绪队列等待运行的CPU。

分析进程的命令,比如pidstat,改命令包括:

  • 用户态CPU使用率(%user)

  • 内核态CPU使用率(%system)

  • 运行虚拟机CPU使用率(%guest)

  • 等待CPU使用率(%wait)

  • 以及总的CPU使用率(%CPU)

# 每隔 1 秒输出一组数据,共输出 5 组
$ pidstat 1 515:56:02      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command15: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
# 最后的Average部分,计算了5组数据的平均值复制代码

                       3、CPU使用率过高怎么办

通过top、ps、pidstat等工具,可以找到具体的进程,但如果还想知道是代码中的哪个函数呢?找到它,才能更高效、更有针对性地进行优化。

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

第一种常用方法是perf top

$ 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)。

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

  • 第一列Overhead,是该符号的性能事件在所有采样中的比例,用百分比表示

  • 第二列Shared,是该函数或指令所在的动态共享对象,如内核、进程名、动态链接库名等

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

  • 最后一列Symbol是符号名,也就是函数名。当函数名未知时,用十六进制 的地址表示

第二种用法,就是perf record和perf report。perf top虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就是无法用于离线或者后续的分析,

而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 的报告复制代码

                                   4、案例

下面我们就以Nginx + PHP的web服务为例,来看看当CPU使用率过高的问题后,怎么通过top、pidstat等性能工具找出异常进程,怎么通过perf找到导致性能问题的函数。

                                5、前期准备

案例基于Ubuntu,同样适用其他的系统。案例环境如下所示:

  • 机器配置:2 CPU,8G内存

  • 预装docker、sysstat、perf、ab等工具

操作步骤

首先是安装Nginx和PHP应用

$ docker run --name nginx -p 10000:80 -itd feisky/nginx
$ docker run --name phpfpm -itd --network container:nginx feisky/php-fpm复制代码

然后在客户端通过curl进行访问

# 10.0.1.14是第一台虚拟机的IP地址
$ curl http://10.0.1.14:10000It works!复制代码

接下来我们通过ab命令测试一下Nginx服务的性能

# 并发 10 个请求测试 Nginx 性能,总共测试 100 个请求
$ ab -c 10 -n 100 http://10.0.1.14:10000/This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/...
Requests per second:    28.36 [#/sec] (mean)
Time per request:       352.598 [ms] (mean)
...复制代码

从ab的输出结果我们可以看到,Nginx能承受的每秒平均请求数只有28.36,从这个数值上来看,性能也太差了,到底是什么地方出现问题了?我们可以通过top和pidstat进行观察,继续通过ab进行压测,这次请求总数增加到10000:

$ ab -c 10 -n 10000 http://10.0.1.14:10000/复制代码

接着在server端运行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+ COMMAND21514 daemon    20   0  336696  16384   8712 R  41.9  0.2   0:06.00 php-fpm21513 daemon    20   0  336696  13244   5572 R  40.2  0.2   0:06.08 php-fpm21515 daemon    20   0  336696  16384   8712 R  40.2  0.2   0:05.67 php-fpm21512 daemon    20   0  336696  13244   5572 R  39.9  0.2   0:05.87 php-fpm21516 daemon    20   0  336696  16384   8712 R  35.9  0.2   0:05.61 php-fpm复制代码

可以看到,系统中有几个php-fpm进行的CPU使用率加起来接近200%,而每个CPU的用户使用率超过98%,我们可以确定的是导致CPU使用率飙升的进程就是php-fpm。

如何查看具体是php-fpm哪个函数导致的呢?我们可以使用perf进行分析一下,在server中运行perf命令

# -g 开启调用关系分析,-p 指定 php-fpm 的进程号 21515$ perf top -g -p 21515复制代码

可以看到输出信息,发现最终的调用关系是到了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!"复制代码

直接进入该容器,将该函数注释掉,然后重启容器

# 进入容器,更改配置
$ docker exec -i -t phpfpm bash
$ cat /app/index.php
<?php// test only./*
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
  $x += sqrt($x);
}
 */echo "It works!"?>
# 重启容器生效
$ docker restart phpfpm复制代码

然后再近些测试:

# 进入容器,更改配置
$ docker exec -i -t phpfpm bash
$ cat /app/index.php
<?php// test only./*
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
  $x += sqrt($x);
}
 */echo "It works!"?>
# 重启容器生效
$ docker restart phpfpm复制代码

从这里可以发现,现在每秒的平均请求数,已经从原来的28.36变成了4549.11。

注意:如果使用centos7的系统,使用 perf top -g -p pid没有看到函数名称,只能看到一堆十六进制的东西。

(1)分析:

当没有看到函数名称,只看到了十六进制符号,下面有Failed to open /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.4, continuing without symbols 这说明perf无法找到待分析进程所依赖的库。这里只显示了一个,但其实依赖的库还有很多。这个问题其实是在分析Docker容器应用时经常会碰到的一个问题,因为容器应用所依赖的库都在镜像里面。

(2)解决思路:

  • 在容器外面构建相同路径的依赖库。这种方法不推荐,一是因为找出这些依赖比较麻烦,更重要的是构建这些路径会污染虚拟机的环境

  • 在容器外面把分析纪录保存下来,到容器里面再去查看结果,这样库和符号的路径就都是对的了

(3)操作:

  • 在Centos系统上运行 perf record -g -p <pid>,执行一会儿(比如15秒)按ctrl+c停止

  • 把生成的 perf.data(这个文件生成在执行命令的当前目录下,当然也可以通过查找它的路径 find | grep perf.data或 find / -name perf.data)文件拷贝到容器里面分析
    docker cp perf.data phpfpm:/tmp
    docker exec -i -t phpfpm bash
    $ cd /tmp
    $ apt-get update && apt-get install -y linux-perf linux-tools procps
    $ perf_4.9 report

                                    6、小结

CPU使用率是最直观和最常见的系统性能指标,更是我们在排查问题时,通常会关注的第一个指标。所以我们需要熟悉它的含义,需要弄清楚(%user)、Nice(%nice)、系统(%system)、等待I/O(%iowait)中断(%irq),比如:

  • 用户CPU和Nice CPU高,说明用户态进程占用了较多的CPU,所以应该着重排查进程的性能问题

  • 系统CPU高,说明内核态占用了较多的CPU,所以应该着重排查内核线程或系统调用的性能问题

  • I/O等待CPU高,说明等待I/O的时间比较长,所以应该着重排查系统存储是不是出现了I/O问题

碰到CPU使用率升高的问题,可以借助top、pidstat等工具,确认引发CPU性能问题的来源,然后通过perf工具,排查引起性能问题的具体函数。

参考地址:

time.geekbang.org/column/arti…

本文首发于公众号”小米运维“,点击查看原文


猜你喜欢

转载自juejin.im/post/5c6e4568e51d4523e53c13a7