Linux性能优化学习知识
part02 平均负载
uptime
1.每次发现系统变慢时,先执行top 或者 uptime
命令,了解系统的负载情况.
flyingzc@ubuntu:~$ uptime
09:24:28 up 25 min, 2 users, load average: 0.00, 0.02, 0.16
当前时间,系统运行时间,正在登录用户数 过去1分钟,5分钟,15分钟的平均负载(Load Average)
平均负载:
单位时间内,系统处于可运行状态
和不可中断状态
的平均进程数,即平均活跃进程数
,它和CPU使用率并没有直接关系.
可运行状态的进程:
正在使用CPU
或正在等待CPU的进程
,ps命令中: 处于R状态(Running 或 Runnable)的进程
.
不可中断状态的进程:
正处于内核态关键流程中的进程,并且这些流程是不可打断的
,比如最常见的是等待硬件设备的I/O响应,ps命令中: D状态(Uninterruptible Sleep)的进程
.
比如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态.
若此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题.
不可中断状态实际上是系统对进程和硬件设备的一种保护机制.
平均负载值含义
比如当平均负载为2时,意味着:
在有2个CPU的系统上,则所有的CPU都刚好被完全占用.
在有4个CPU的系统上,则CPU有50%的空闲.
在只有1个CPU的系统中,则有一半的进程竞争不到CPU.
mac查看cpu个数
1. 查看物理CPU个数
➜ myshell sysctl hw.physicalcpu
hw.physicalcpu: 4
2. 查看逻辑CPU个数
➜ myshell sysctl hw.logicalcpu
hw.logicalcpu: 8
3. 查看硬件信息
➜ myshell system_profiler SPHardwareDataType
Hardware:
Hardware Overview:
Model Name: MacBook Pro
Model Identifier: MacBookPro16,3
Processor Name: Quad-Core Intel Core i5
Processor Speed: 1.4 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 256 KB
L3 Cache: 6 MB
Hyper-Threading Technology: Enabled
Memory: 16 GB
Boot ROM Version: 1037.147.1.0.0 (iBridge: 17.16.16065.0.0,0)
Serial Number (system): FVFDD085P3YV
Hardware UUID: 344D207F-FE80-5E6B-87E2-4CEA2146DED2
Activation Lock Status: Disabled
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y7trv73j-1627403340456)(2021-05-21-13-57-03.png)]
平均负载合理值
平均负载最理想的情况是等于 CPU个数.
在评判平均负载时,先要知道系统有几个 CPU
,可通过 top 命令或从文件 /proc/cpuinfo 中读取.
flyingzc@ubuntu:~$ grep 'model name' /proc/cpuinfo | wc -l
1
有了CPU 个数,就能判断出,当平均负载比 CPU 个数还大的时候,系统已经出现了过载
.
三个不同时间间隔的负载平均值,提供了分析系统负载趋势的数据来源,能更全面地理解目前的负载状况.
若1分钟,5分钟,15分钟的三个值基本相同,或相差不大,则说明系统负载很平稳.
若1分钟的值远小于15 分钟的值,说明系统最近1分钟的负载在减少,而过去15分钟内却有很大的负载.
若1分钟的值远大于 15 分钟的值,说明最近1分钟的负载在增加,这种增加有可能只是临时性的,也有可能还会持续增加下去,需要持续观察.
一旦1分钟的平均负载接近或超过了CPU的个数,说明系统正在发生过载的问题,这时就得分析调查是哪里导致的问题,并要想办法优化了.
假设我们在一个单 CPU(1个cpu) 系统上看到平均负载为 1.73,0.60,7.98,说明在过去 1 分钟内,系统有 73% 的超载,而在 15 分钟内,有 698% 的超载,从整体趋势来看,系统的负载在降低.
当平均负载高于 CPU 数量 70%的时候,你就应该分析排查负载高的问题了.一旦负载过高,就可能导致进程响应变慢,进而影响服务的正常功能.
平均负载 与 CPU使用率
平均负载: 即平均活跃进程数.单位时间内,系统处于可运行状态
和不可中断状态
的平均进程数,也就是平均活跃进程数
.
它包括正在使用 CPU 的进程,和 等待 CPU 和 等待 I/O 的进程
.
CPU 使用率: 单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应.
比如:
CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
大量等待 CPU 的进程调度也会导致平均负载升高,此时的CPU使用率也会比较高.
I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
平均负载案例
预先安装 stress 和 sysstat 包,如:
sudo apt install stress sysstat
stress: Linux 系统压力测试工具,可用作异常进程 模拟平均负载升高的场景.
sysstat 包含了常用的 Linux 性能工具,用来监控和分析系统的性能.
命令:
mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有CPU的平均指标.
pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU,内存,I/O 以及上下文切换等性能指标.
CPU密集型进程
session1
stress --cpu 1 --timeout 600
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5AdoAvcf-1627403340459)(2019-12-29-23-00-14.png)]
session2
watch -d uptime
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HkUE4b3v-1627403340461)(2019-12-29-22-59-34.png)]
session3
mpstat -P ALL 5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrjfkPNL-1627403340463)(2019-12-29-23-01-07.png)]
cpu1使用率100%,iowait为0
pidstat 来查询到底是哪个进程导致了 CPU 使用率为 100%
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhVVFgBF-1627403340465)(2019-12-29-23-03-05.png)]
IO密集型进程
1分钟的平均负载load升高,其中一个cpu使用率升高,它对应的iowait高达67%,说明平均负载的升高是由于 iowait 的升高.
平均负载总结
平均负载高有可能是 CPU 密集型进程导致的;
平均负载高并不一定代表 CPU 使用率高,还有可能是 I/O 更繁忙了;
当发现负载高的时候,你可以使用 mpstat,pidstat 等工具,辅助分析负载的来源.
part03~04 cpu上下文切换
上下文切换
多个进程竞争 CPU,也会导致系统的负载升高.
在每个进程任务运行前,CPU 都需要知道任务从哪加载,又从哪里开始运行.
即 需要系统事先帮它设置好 CPU 寄存器 和 程序计数器(Program Counter,PC)
.
CPU 寄存器: 是 CPU 内置的容量小,但速度极快的内存.
程序计数器: 则是用来存储 CPU 正在执行的指令位置,或者即将执行的下一条指令位置.
二者都是 CPU 在运行任何任务前,必须的依赖环境,又叫 CPU 上下文.
CPU 上下文切换:
先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务.
这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来.
这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行.
每次上下文切换都需要 几十纳秒 到 数微秒 的 CPU 时间
.
这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在 寄存器,内核栈 以及 虚拟内存 等资源的 保存 和 恢复 上
.
进而缩短了真正运行进程的时间,会导致平均负载升高.
vmstat
vmstat: 查看系统总体的上下文切换情况
flyingzc@ubuntu:~$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 2410196 177196 862672 0 0 172 65 55 77 0 5 92 3 0
1 0 0 2410188 177196 862708 0 0 0 0 39 54 0 0 100 0 0
cs (context switch): 每秒上下文切换的次数.
in (interrupt): 每秒中断的次数.
r (Running or Runnable): 就绪队列的长度,即正在运行和等待CPU的进程数,比如cpu个数是2,但是有8个在等待.
b (Blocked): 处于不可中断睡眠状态的进程数.
us(user)和 sy(system): 用户态cpu使用率 和 内核态cpu使用率. 这两列的CPU 使用率加起来上升到了 100%,其中系统 CPU 使用率,也就是 sy 列高达 84%,说明 CPU 主要是被内核占用了.
系统的就绪队列过长,即正在运行和等待CPU的进程数过多,导致了大量的上下文切换,而上下文切换又导致了系统 CPU 的占用率升高.
pidstat
pidstat -w 5:
查看每个进程上下文切换的情况
flyingzc@ubuntu:~$ pidstat -w 5
Linux 5.0.0-37-generic (ubuntu) 01/04/2020 _x86_64_ (2 CPU)
08:31:20 PM UID PID cswch/s nvcswch/s Command
08:31:25 PM 0 10 5.19 0.00 rcu_sched
08:31:25 PM 0 11 0.20 0.00 migration/0
08:31:25 PM 0 17 0.20 0.00 migration/1
08:31:25 PM 0 25 3.39 0.00 kworker/0:2-events
08:31:25 PM 0 348 0.20 0.00 kworker/0:1H-kblockd
08:31:25 PM 0 746 10.78 0.00 vmtoolsd
08:31:25 PM 0 799 0.20 0.00 irqbalance
08:31:25 PM 0 869 0.20 0.00 wpa_supplicant
08:31:25 PM 0 1513 0.20 0.00 packagekitd
08:31:25 PM 121 1658 1.00 0.00 gsd-color
08:31:25 PM 0 2048 2.99 0.00 kworker/1:3-events
08:31:25 PM 1000 2183 1.80 0.00 sshd
08:31:25 PM 1000 2225 1.80 1.60 watch
08:31:25 PM 0 2770 2.79 0.00 kworker/u256:1-events_unbound
08:31:25 PM 1000 5179 0.20 0.00 pidstat
cswch: 表示每秒自愿上下文切换(voluntary context switches)的次数
nvcswch: 表示每秒非自愿上下文切换(non voluntary context switches)的次数.
自愿上下文切换:
进程无法获取所需资源,导致的上下文切换.
比如: I/O,内存等系统资源不足时,就会发生自愿上下文切换.
非自愿上下文切换:
进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换.
比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换
.
模拟上下文切换问题和定位
Ques: 上下文切换频率是多少次才算正常呢
1.安装sysstat
sudo apt install sysbench sysstat
2.以10个线程运行5分钟的基准测试,模拟多线程切换的问题
# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题
$ sysbench --threads=10 --max-time=300 threads run
3.第二个终端运行 vmstat ,观察上下文切换情况
系统的就绪队列过长,也就是正在运行和等待CPU的进程数过多,导致了大量的上下文切换,而上下文切换又导致了系统 CPU 的占用率升高.
4.查看哪个进程导致了这些问题
# 每隔1秒输出1组数据(需要 Ctrl+C 才结束)
# -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标
$ pidstat -w -u 1
CPU 使用率的升高果然是 sysbench 导致的,它的 CPU 使用率已经达到了 100%.
但上下文切换则是来自其他进程,包括非自愿上下文切换频率最高的 pidstat ,以及自愿上下文切换频率最高的内核线程 kworker 和 sshd.
pidstat 默认显示进程的指标数据,加上 -t 参数后,才会输出线程的指标.
# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
虽然 sysbench 进程(也就是主线程)的上下文切换次数看起来并不多,但它的子线程的上下文切换次数却有很多.
看来,上下文切换罪魁祸首,还是过多的 sysbench 线程.
part05 cpu使用率100%
CPU 使用率: 单位时间内 CPU 使用情况的统计
Linux将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,造成多任务同时运行的错觉
.
维护 CPU 时间: Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数.每发生一次时间中断,Jiffies 的值就加 1.
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): 以低优先级运行虚拟机的时间.
CPU 使用率: 除了 空闲时间(idle) 外的 其他时间 占总 CPU 时间的百分比
查看cpu使用率
top: 系统总体的 CPU 和内存使用情况,各个进程的资源使用情况.
ps: 每个进程的资源使用情况.
pidstat: 分析每个进程 CPU 使用情况的工具.
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 使用率.
top 默认显示的是所有 CPU 的平均值,此时按下数字 1 ,即可切换到每个 CPU 的使用率了.
空白行之后是进程的实时信息,每个进程都有一个 %CPU 列,表示进程的 CPU 使用率。
它是用户态和内核态 CPU 使用率的总和
,包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的 CPU。
top 并没有细分进程的用户态 CPU 和内核态 CPU
pidstat
专门分析每个进程 CPU 使用情况的工具.
间隔 1 秒展示了进程的 5 组 CPU 使用率,包括:
用户态CPU使用率 (%usr);
内核态CPU使用率(%system);
运行虚拟机CPU使用率(%guest);
等待 CPU使用率(%wait);
以及总的CPU使用率(%CPU).
# 每隔 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使用率过高分析
用户 CPU(user) 和 Nice CPU(nice) 高:
用户态进程占用了较多的 CPU,应着重排查进程的性能问题
.
系统 CPU(system) 高:
内核态占用了较多的 CPU,应着重排查内核线程或者系统调用的性能问题
.
I/O 等待 CPU 高:
等待 I/O 的时间较长,应着重排查系统存储是不是出现了 I/O 问题.
软中断和硬中断高:
软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序.
碰到 CPU 使用率升高的问题,可以借助 top,pidstat 等工具,确认引发 CPU 性能问题的来源.
perf
perf: 性能分析工具。它以性能事件采样为基础,可以分析系统的各种事件和内核性能,还能分析指定应用程序的性能问题。
使用 perf 分析 CPU 性能问题:
perf 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
...
part06 cpu使用率高 但找不到高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
...
CPU 使用率最高的进程才 2.7%,并不高.
系统 CPU 使用率( %Cpu )这一行,系统的整体 CPU 使用率是比较高的:
用户 CPU 使用率(us)已经到了 80%,系统 CPU 为 15.1%,空闲 CPU (id)则只有 2.8%
.
为什么用户 CPU 使用率这么高呢?
再重新分析一下进程列表,看看有没有可疑进程:
所有进程cpu使用率都不高.
再用 pidstat 分析, pidstat 用来分析进程的 CPU 使用情况
.
# 间隔 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% 还差得远呢!
重新运行 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
...
Tasks 这一行看起来有点奇怪,就绪队列中居然有 6 个 Running 状态的进程(6 running)
,是不是有点多呢?
再仔细看进程列表,这次主要看 Running® 状态的进程.
你有没有发现, Nginx 和所有的 php-fpm 都处于 Sleep(S)状态,而真正处于 Running®状态的,却是几个 stress 进程.这几个 stress 进程就比较奇怪了,需要我们做进一步的分析.
我们还是使用 pidstat 来分析这几个进程,并且使用 -p 选项指定进程的 PID.
首先,从上面 top 的结果中,找到这几个进程的 PID.比如,先随便找一个 24344,然后用 pidstat 命令看一下它的 CPU 使用情况:
$ pidstat -p 24344
16:14:55 UID PID %usr %system %guest %wait %CPU CPU Command
还是没有输出.现在终于发现问题,原来这个进程已经不存在了,所以 pidstat 就没有任何输出
.
既然进程都没了,那性能问题应该也跟着没了吧.
再用 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 跟前面不一样了,原来的 PID 24344 不见了,现在的是 6779
.
进程的 PID 在变,这说明: 1.要么是这些进程在不停地重启;2.要么就是全新的进程
原因1:进程在不停地崩溃重启,比如因为段错误,配置错误等等,这时,进程在退出后可能又被监控系统自动重启了.
原因2:这些进程都是短时进程,也就是在其他应用内部通过 exec 调用的外面命令.这些命令一般都只运行很短的时间就会结束,你很难用 top 这种间隔时间比较长的工具发现.
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 的内部分析了.
首先,当然应该去看看它的源码.运行下面的命令,把案例应用的源码拷贝到 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);
看错误消息 mkstemp failed: Permission denied ,以及 failed run completed in 0s.
原来 stress 命令并没有成功,它因为权限问题失败退出了.
看来,我们发现了一个 PHP 调用外部 stress 命令的 bug:没有权限创建临时文件.
正是由于权限错误,大量的 stress 进程在启动时初始化失败,进而导致用户 CPU 使用率的升高
.
perf 可以用来分析 CPU 性能事件
.
perf record -g
命令 ,并等待一会儿(比如 15 秒)后按 Ctrl+C 退出.然后再运行 perf report 查看报告:
记录性能事件,等待大约 15 秒后按 Ctrl+C 退出
$ perf record -g
查看报告
$ perf report
这样,你就可以看到下图这个性能报告:
stress 占了所有 CPU 时钟事件的 77%,而 stress 调用调用栈中比例最高的,是随机数生成函数 random(),看来它的确就是 CPU 使用率升高的原因
.
随后的优化就很简单了,只要修复权限问题,并减少或删除 stress 的调用,就可以减轻系统的 CPU 压力.
当然,实际生产环境中的问题一般都要比这个案例复杂,在你找到触发瓶颈的命令行后,却可能发现,这个外部命令的调用过程是应用核心逻辑的一部分,并不能轻易减少或者删除.
这时,你就得继续排查,为什么被调用的命令,会导致 CPU 使用率升高或 I/O 升高等问题.
最后,在案例结束时,不要忘了清理环境,执行下面的 Docker 命令,停止案例中用到的 Nginx 进程:
$ docker rm -f nginx phpfpm
execsnoop
在这个案例中,我们使用了 top,pidstat,pstree 等工具分析了系统 CPU 使用率高的问题,并发现 CPU 升高是短时进程 stress 导致的,但是整个分析过程还是比较复杂的.对于这类问题,有没有更好的方法监控呢?
execsnoop 就是一个专为短时进程设计的工具.它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID,父进程 PID,命令行参数以及执行的结果.
比如,用 execsnoop 监控上述案例,就可以直接得到 stress 进程的父进程 PID 以及它的命令行参数,并可以发现大量的 stress 进程在不停启动:
$ execsnoop
PCOMM PID PPID RET ARGS
sh 30394 30393 0
stress 30396 30394 0 /usr/local/bin/stress -t 1 -d 1
sh 30398 30393 0
stress 30399 30398 0 /usr/local/bin/stress -t 1 -d 1
sh 30402 30400 0
stress 30403 30402 0 /usr/local/bin/stress -t 1 -d 1
sh 30405 30393 0
stress 30407 30405 0 /usr/local/bin/stress -t 1 -d 1
...
execsnoop 所用的 ftrace 是一种常用的动态追踪技术,一般用于分析 Linux 内核的运行时行为,后面课程我也会详细介绍并带你使用.
小结
碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题
,比如有可能是下面这两种情况.
1.应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现.
2.应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU.
对于这类进程,我们可以用 pstree 或者 execsnoop 找到它们的父进程,再从父进程所在的应用入手,排查问题的根源.
短时应用的运行时间比较短,很难在 top 或者 ps 这类展示系统概要和进程快照的工具中发现,需要使用记录事件的工具来配合诊断,比如 execsnoop 或者 perf top.