lyyyuna 的小花园

动静中之动, by

RSS

Linux 性能分析 - 如何理解 CPU 平均负载
发表于 2020-05

平均负载的定义

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% 的负载。

进程的状态

操作系统进程的基本状态

在操作系统的概念中,进程会不断改变其运行状态,其必须有以下三种基本状态:

  1. 就绪:进程所需要的资源都已经获得,但是还没有分配到 CPU 来运行
  2. 运行:进程分配到 CPU 在运行
  3. 阻塞:进程的某些资源还没有满足,比如缓冲区申请未分配,等待 I/O 完成

Linux 进程的基本状态

相比于标准的操作系统概念,Linux 中对于状态有自己的划分:

  1. D 不可中断睡眠,最常见的是正在访问硬件设备,若中断会导致磁盘数据和进程数据不一致
  2. R 运行和就绪,对,Linux 上这两个状态是被统计在一起的
  3. S 可中断睡眠,比如进程正在等待信号唤醒
  4. T 停止,是处于调试状态的进程
  5. Z 僵尸进程,即父进程未等待子进程退出

理解定义

由上述 Linux 的进程定义,所谓可运行和不可中断状态就是活跃的进程,即正在使用 CPU/等待 CPU/等待 IO 的进程,而平均负载可理解为单位时间内活跃进程的数量。

如果每个核上都正好跑着一个进程,则说明 CPU 被充分的利用。那么平均负载为 4 在一个四核处理器上就是 CPU 刚好被完全占用,而在单核处理器上,意味着有 3 个进程得不到 CPU,只能干等。

平均负载与 CPU 利用率

在未看过本文之前,你很可能将平均负载和 CPU 利用率划上等号,事实上,这是两个独立的概念。

实验

我们就上文提到的三个场景,分别做实验来模拟。

我们实验所用的系统是 ubuntu 18,机器配置为 2 CPU/16 GB。

工具介绍

工欲善其事,必先利其器。有很多现成的工具可以模拟负载,监控和分析系统性能。

  1. stress可以用来对系统施加指定类型的系统压力,它并不是一个基准测试工具
  2. mpstat是一个多核的 CPU 性能分析工具,可以统计每个 CPU 的性能
  3. 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等工具来分析是上述哪三个场景,从而找到负载升高的来源。