Linux 性能分析 - 如何理解 CPU 平均负载
(Linux 性能分析, Part 1)
平均负载的定义
uptime
是最简单的性能分析命令之一,它的输出非常简单,比如:
$ uptime
16:08:29 up 42 days, 5:11, 2 users, load average: 0.33, 0.22, 0.22
前面几列易于理解,分别是当前时间,系统已运行时间,登录的用户数。可是最后的平均负载是什么?查看uptime
帮助文档对其的定义:
System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.
大概翻译过来就是:
系统的平均负载是指处于可运行和不可中断状态的进程平均数量。所谓可运行状态是指正在使用或等待使用 CPU,而不可中断状态是指进程正执行某种 I/O 操作,比如读写磁盘。平均负载会计算 1 分钟、5 分钟和 15 分钟内的该指标。不过需要注意的是,该数据没有针对 CPU 个数作归一化处理,所以平均负载 1 在单核系统上意味满负载运行,而在 4 核系统上意味着只使用了 25% 的负载。
进程的状态
操作系统进程的基本状态
在操作系统的概念中,进程会不断改变其运行状态,其必须有以下三种基本状态:
- 就绪:进程所需要的资源都已经获得,但是还没有分配到 CPU 来运行
- 运行:进程分配到 CPU 在运行
- 阻塞:进程的某些资源还没有满足,比如缓冲区申请未分配,等待 I/O 完成
Linux 进程的基本状态
相比于标准的操作系统概念,Linux 中对于状态有自己的划分:
- D 不可中断睡眠,最常见的是正在访问硬件设备,若中断会导致磁盘数据和进程数据不一致
- R 运行和就绪,对,Linux 上这两个状态是被统计在一起的
- S 可中断睡眠,比如进程正在等待信号唤醒
- T 停止,是处于调试状态的进程
- Z 僵尸进程,即父进程未等待子进程退出
理解定义
由上述 Linux 的进程定义,所谓可运行和不可中断状态就是活跃的进程,即正在使用 CPU/等待 CPU/等待 IO 的进程,而平均负载可理解为单位时间内活跃进程的数量。
如果每个核上都正好跑着一个进程,则说明 CPU 被充分的利用。那么平均负载为 4 在一个四核处理器上就是 CPU 刚好被完全占用,而在单核处理器上,意味着有 3 个进程得不到 CPU,只能干等。
平均负载与 CPU 利用率
在未看过本文之前,你很可能将平均负载和 CPU 利用率划上等号,事实上,这是两个独立的概念。
- 对于 CPU 密集型进程,因为大量利用了 CPU,故而平均负载升高
- 对于 I/O 密集型进程,虽然 CPU 利用率不高,但是大量进程都阻塞住,使得活跃进程数量增加,所以平均负载也会升高
- 对于有大量等待调度的进程,这些进程都处于就绪态,平均负载也高。而且由于上下文切换,进程数量多,切换的机会也多,这无形中也增加了负载
实验
我们就上文提到的三个场景,分别做实验来模拟。
我们实验所用的系统是 ubuntu 18,机器配置为 2 CPU/16 GB。
工具介绍
工欲善其事,必先利其器。有很多现成的工具可以模拟负载,监控和分析系统性能。
stress
可以用来对系统施加指定类型的系统压力,它并不是一个基准测试工具mpstat
是一个多核的 CPU 性能分析工具,可以统计每个 CPU 的性能pidstat
是一个多进程性能分析工具,可以统计每个进程的性能
实验开始时首先查看一下系统的负载情况:
$ uptime
15:23:16 up 51 days, 4:26, 1 user, load average: 0.15, 0.30, 0.31
确认一下系统的是否是 2 核:
$ cat /proc/cpuinfo | grep processor
2
CPU 密集型
我们利用stress
模拟一个 CPU 跑满的场景:
$ stress --cpu 1 --timeout 600
过两分钟后查看uptime
查看平均负载:
$ uptime
15:41:09 up 51 days, 4:44, 2 users, load average: 1.18, 0.77, 0.49
可以看到系统负载越升越高,1 分钟内的平均负载高于 5 分钟内平均负载。
使用mpstat
查看 CPU 利用率的情况:
# ALL 表示输出所有 CPU 的情况,5 表示每隔 5s 输出一次
$ mpstat -P ALL 5
03:42:26 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
03:42:31 PM all 51.96 0.00 1.01 0.81 0.00 0.10 0.00 0.00 0.00 46.12
03:42:31 PM 0 3.44 0.00 2.02 1.62 0.00 0.20 0.00 0.00 0.00 92.71
03:42:31 PM 1 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
可以看到其中一个 CPU 利用率飙到了 100%,这个是平均负载升高的直接原因。
使用pidstat
查看具体是哪个进程占用了 CPU:
# 5 表示每隔 5s 输出一次
$ pidstat -u 5
Linux 4.15.0-96-generic (work) 06/07/2020 _x86_64_ (2 CPU)
03:46:19 PM UID PID %usr %system %guest %wait %CPU CPU Command
03:46:24 PM 0 10766 0.20 0.20 0.00 0.60 0.40 0 redis-server
03:46:24 PM 1000 27225 99.80 0.00 0.00 0.00 99.80 1 stress
03:46:24 PM 1000 27583 0.00 0.20 0.00 0.00 0.20 0 pidstat
虽然输出有很多行,但我们很容易就发现是stress
这个进程使用了 100% 的 CPU。
I/O 密集型
我们还是使用stress
模拟 I/O 压力
$ stress -i 1 --timeout 600
过两分钟后查看uptime
查看平均负载:
$ uptime
15:53:43 up 51 days, 4:57, 2 users, load average: 1.09, 0.94, 0.85
平均负载果然升高了。
同时我们看一下 CPU 利用率的情况:
03:54:43 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
03:54:48 PM all 2.57 0.00 3.81 29.35 0.00 1.75 0.00 0.00 0.00 62.51
03:54:48 PM 0 2.49 0.00 3.53 45.95 0.00 3.33 0.00 0.00 0.00 44.70
03:54:48 PM 1 2.86 0.00 4.09 12.88 0.00 0.20 0.00 0.00 0.00 79.96
可以看到两个 CPU 都没有被占满。但是%iowait
这一项和上一小节有明显的差异,%iowait
即是反映 I/O 压力的情况。
我们接着用pidstat
查看具体是哪个进程导致的%iowait
升高:
$ pidstat -u 5
Linux 4.15.0-96-generic (work) 06/07/2020 _x86_64_ (2 CPU)
03:59:31 PM UID PID %usr %system %guest %wait %CPU CPU Command
03:59:36 PM 0 7 0.00 0.20 0.00 0.00 0.20 0 ksoftirqd/0
03:59:36 PM 0 8 0.00 0.20 0.00 0.20 0.20 0 rcu_sched
03:59:36 PM 0 209 0.00 1.20 0.00 0.00 1.20 0 kworker/0:1H
03:59:36 PM 112 2047 0.20 0.20 0.00 0.00 0.40 1 beam.smp
03:59:36 PM 0 9976 0.00 0.20 0.00 0.00 0.20 1 supervisord
03:59:36 PM 0 9981 0.40 0.00 0.00 0.00 0.40 1 mongod
03:59:36 PM 0 10071 0.40 0.00 0.00 0.00 0.40 1 mongod
03:59:36 PM 0 10072 0.40 0.00 0.00 0.00 0.40 0 mongod
03:59:36 PM 0 10073 0.40 0.20 0.00 0.00 0.60 0 mongod
03:59:36 PM 0 10765 0.20 0.00 0.00 0.00 0.20 1 redis-server
03:59:36 PM 0 10766 0.20 0.20 0.00 0.60 0.40 0 redis-server
03:59:36 PM 0 10821 0.20 0.00 0.00 0.00 0.20 1 redis-server
03:59:36 PM 1000 27741 0.00 7.19 0.00 1.40 7.19 0 stress
可以看到%wait
这一项高的还是stress
进程。
大量等待调度的进程
stress
可以模拟出处于运行态的进程,以下模拟出 10 个待调度的进程:
$ stress -c 10 --timeout 600
过两分钟后查看uptime
查看平均负载:
$ uptime
16:09:20 up 51 days, 5:12, 2 users, load average: 9.38, 4.54, 2.35
平均负载又升高了,而且远高于以上两个实验,这也是当然的,根据平均负载的定义,这里有 10 个处于可运行的进程,那么平均负载的值肯定会接近 10。
$ pidstat -u 5
Linux 4.15.0-96-generic (work) 06/07/2020 _x86_64_ (2 CPU)
04:11:59 PM UID PID %usr %system %guest %wait %CPU CPU Command
04:12:04 PM 0 8 0.00 0.20 0.00 0.20 0.20 0 rcu_sched
04:12:04 PM 0 9976 0.20 0.00 0.00 0.00 0.20 1 supervisord
04:12:04 PM 0 9981 0.60 0.00 0.00 0.00 0.60 1 mongod
04:12:04 PM 0 10071 0.20 0.00 0.00 0.00 0.20 1 mongod
04:12:04 PM 0 10072 0.20 0.00 0.00 0.00 0.20 0 mongod
04:12:04 PM 0 10073 0.20 0.00 0.00 0.00 0.20 0 mongod
04:12:04 PM 0 10260 0.40 0.20 0.00 0.00 0.60 1 mongod
04:12:04 PM 0 10261 0.40 0.00 0.00 0.00 0.40 1 mongod
04:12:04 PM 0 10262 0.20 0.00 0.00 0.00 0.20 1 mongod
04:12:04 PM 0 10763 0.20 0.00 0.00 0.20 0.20 1 redis-server
04:12:04 PM 0 10764 0.20 0.00 0.00 0.20 0.20 1 redis-server
04:12:04 PM 0 10766 0.20 0.20 0.00 0.80 0.40 0 redis-server
04:12:04 PM 0 10820 0.20 0.00 0.00 0.00 0.20 1 redis-server
04:12:04 PM 1000 28242 19.48 0.00 0.00 80.12 19.48 0 stress
04:12:04 PM 1000 28243 19.28 0.00 0.00 80.12 19.28 1 stress
04:12:04 PM 1000 28244 19.28 0.00 0.00 80.12 19.28 0 stress
04:12:04 PM 1000 28245 19.28 0.00 0.00 79.72 19.28 1 stress
04:12:04 PM 1000 28246 19.48 0.00 0.00 80.12 19.48 0 stress
04:12:04 PM 1000 28247 19.28 0.00 0.00 79.92 19.28 0 stress
04:12:04 PM 1000 28248 19.48 0.00 0.00 79.92 19.48 1 stress
04:12:04 PM 1000 28249 19.48 0.00 0.00 80.52 19.48 1 stress
04:12:04 PM 1000 28250 19.48 0.00 0.00 80.12 19.48 1 stress
04:12:04 PM 1000 28251 19.48 0.00 0.00 80.52 19.48 0 stress
可以看到有 10 个stress
进程在抢 2 个 CPU。
小结
通过本文我们了解到:平均负载与 CPU 利用率没有必然联系。而当发现负载升高后,需要综合使用uptime
, mpstat
, pidstat
等工具来分析是上述哪三个场景,从而找到负载升高的来源。