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时间
扫描二维码关注公众号,回复: 5259335 查看本文章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…
本文首发于公众号”小米运维“,点击查看原文。