本篇文章主要介绍了 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 工具, 排查引起性能问题的具体函数.
参考地址:
https://time.geekbang.org/column/article/70476
本文首发于公众号 "小米运维", 点击查看原文.
来源: https://juejin.im/post/5c6e4568e51d4523e53c13a7