这篇文章上次修改于 899 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
1 系统 CPU 利用高,但为啥找不到高 CPU 应用?
top 显示 CPU 利用率较高,但是每个进程的 CPU 利用率不高,且从 pidstat 也看出各 CPU 的利用率不高。
继续查看 top 输出,发现 running 为 6,大于 CPU 核数。
继续分析处于 R(Running)状态的进程,用 pidstat -p 指定进程 PID,发现没有任何输出,用 ps 查找,发现进程已经不存在了。
top 显示 CPU 利用率仍然较高,且之前的进程仍在,只是 PID 不一样了,说明那些进程在不停的重启,原因可能为:
- 进程不断崩溃重启。
- 这些进程都是短时进程,也就是在其应用内部通过 exec 调用的外部命令。短时进程较难用 top 这种间隔时间较长的工具发现。
PID 不断变化的进程看起来是被其他进程调用的短时进程,如果继续分析,需要找到它的父进程,可用 pstree 显示进程间的调用关系。
$ pstree | grep stress
|-tmux-+-4*[bash---bash---docker---23*[{docker}]]
找到父进程后,可进入 docker 分析,通过 grep 找到调用 stress 的地方。
分析出问题来源后,需要确认有大量的 tmux 进程,可通过 perf 查看。
# 记录性能事件,大约 15s 后按下 Ctrl + C
$ perf record -g
# 查看报告
$ perf report
可以看到 stress 占用了所有 CPU 时钟事件的 70%,调用栈中比例最高的是随机生成函数 random()。
更好的分析工具:execsnoop,可监控短时进程。它通过 fstrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行结果。
2 系统出现大量不可中断进程和僵尸进程怎么办
当 iowait 升高时,进程可能因为得不到硬件的响应,而长时间处于不可中断状态。ps 或 top 的输出中处于 D 状态时,就是不可中断状态。
$ top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3229 walle 20 0 3263992 565016 94156 S 0.7 0.9 363:58.07 compiz
1411 nx 20 0 895564 270148 11304 S 0.3 0.4 102:22.30 nxserver.bin
1431 root 20 0 1523336 23392 3440 S 0.3 0.0 45:29.56 containerd
1619 root -51 0 0 0 0 S 0.3 0.0 51:29.60 irq/145-nvidia
19760 root 20 0 43840 4092 3384 R 0.3 0.0 0:00.01 top
30401 root 20 0 370840 27400 22936 S 0.3 0.0 102:20.71 nxclient.bin
1 root 20 0 185456 5268 3248 S 0.0 0.0 0:16.81 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.30 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
S 列表示进程的状态。
- R Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或等待运行。
- D Disk Sleep 的缩写,是不可中断的睡眠状态,一般表示正在跟硬件交互,交互过程不可被其他进程或中断打断。该状态会在很短时间内结束,一般可以忽略。
- Z Zombie 的缩写。僵尸进程,进程实际上结束了,但是父进程还没有回收它的进程描述符、PID 等资源。
- S Interruptible Sleep 的缩写,可中断的睡眠状态,表示进程因为等待某个事件而被系统挂起。当等待的事件发生时,会被唤醒并进入 R 状态。
- I Idle 的缩写,空闲状态,用在不可中断睡眠的内核线程上。
- t Stopped 或 Traced 的缩写,表示进程处于暂停或跟踪状态。收到 SIGSTOP 后进入暂停状态,收到 SIGCONT 后恢复运行。
- X Dead 的缩写,表示已经消亡,不会出现在 top 或 ps 输出中。
# 按下数字 1 切换到所有 CPU 的使用情况,观察一会儿按 Ctrl+C 结束
$ top
top - 05:56:23 up 17 days, 16:45, 2 users, load average: 2.00, 1.68, 1.39
Tasks: 247 total, 1 running, 79 sleeping, 0 stopped, 115 zombie
%Cpu0 : 0.0 us, 0.7 sy, 0.0 ni, 38.9 id, 60.5 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.7 sy, 0.0 ni, 4.7 id, 94.6 wa, 0.0 hi, 0.0 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top
4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app
4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app
1 root 20 0 160072 9416 6752 S 0.0 0.1 0:38.59 systemd
...
- 通过 top 命令后,看到 CPU 负载达到系统 CPU 个数,说明出现性能问题。
- 第二行发现僵尸进程较多,且不停增加,说明子进程退出后没有被清理。
- CPU 利用率不高,但 iowait 较高。
- 有处于 D 状态的进程,可能在等待 I/O
可得到如下结论:
- iowait 太高,导致 CPU 负载较高,甚至达到 CPU 个数。
- 僵尸进程不断增多,说明程序没能正确清理子进程资源。
2.1 分析 iowait
接下来分析 iowait,一般需要查询系统的 I/O 情况。dstat 可同时观察系统的 CPU、磁盘 I/O、网络及内存使用情况。
# 间隔1秒输出10组数据
$ dstat 1 10
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read writ| recv send| in out | int csw
0 0 96 4 0|1219k 408k| 0 0 | 0 0 | 42 885
0 0 2 98 0| 34M 0 | 198B 790B| 0 0 | 42 138
0 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 42 135
0 0 84 16 0|5633k 0 | 66B 342B| 0 0 | 52 177
0 3 39 58 0| 22M 0 | 66B 342B| 0 0 | 43 144
0 0 0 100 0| 34M 0 | 200B 450B| 0 0 | 46 147
0 0 2 98 0| 34M 0 | 66B 342B| 0 0 | 45 134
0 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 39 131
0 0 83 17 0|5633k 0 | 66B 342B| 0 0 | 46 168
0 3 39 59 0| 22M 0 | 66B 342B| 0 0 | 37 134
可见,当 iowait(wai)升高时,磁盘读请求增大。
需要找出哪个进程在读磁盘,可用 pidstat 查看进程的资源使用情况。
# -d 展示 I/O 统计数据
# 间隔 1 秒输出多组数据 (这里是 20 组)
$ pidstat -d 1 20
...
06:48:46 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:47 0 4615 0.00 0.00 0.00 1 kworker/u4:1
06:48:47 0 6080 32768.00 0.00 0.00 170 app
06:48:47 0 6081 32768.00 0.00 0.00 184 app
06:48:47 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:48 0 6080 0.00 0.00 0.00 110 app
06:48:48 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:49 0 6081 0.00 0.00 0.00 191 app
06:48:49 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:50 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:51 0 6082 32768.00 0.00 0.00 0 app
06:48:51 0 6083 32768.00 0.00 0.00 0 app
06:48:51 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:52 0 6082 32768.00 0.00 0.00 184 app
06:48:52 0 6083 32768.00 0.00 0.00 175 app
06:48:52 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:53 0 6083 0.00 0.00 0.00 105 app
...
是 app 进程在读磁盘。app 在执行什么 I/O 操作呢,需要找出 app 的系统调用,可用如下命令查看:
$ perf record -g
$ perf report
可以看到,app 在调用 sys_read() 读取数据,通过 new_sync_read 和 blkdev_read_iter 可以看出,进程正在对磁盘进行直接读,绕过系统缓存,导致 I/O 升高。
查看源码,会看到它使用了 O_DIRECT 打开磁盘:
open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)
2.2 僵尸进程
需要找到父进程,然后在父进程中解决。可运行 pstree 命令。
# -a 表示输出命令行选项
# p表PID
# s表示指定进程的父进程
$ pstree -aps 3084
systemd,1
└─dockerd,15006 -H fd://
└─docker-containe,15024 --config /var/run/docker/containerd/containerd.toml
└─docker-containe,3991 -namespace moby -workdir...
└─app,4009
└─(app,3084)
可以看到 3084 进程的父进程是 4009 app 进程。接下在 app 代码中查看子进程退出时父进程是否调用 wait() 或 waitpid(),或者注册 SIGCHILD 信号处理函数即可。
参考
倪朋飞. Linux 性能优化实战.
没有评论