关于linux:Linux-Load-Average-Solving-the-Mystery

50次阅读

共计 15588 个字符,预计需要花费 39 分钟才能阅读完成。

本篇博客翻译自 Brendan Gregg 的技术考古文章:Linux Load Average: Solving the Mystery。翻阅这篇文章的起因是我在应用 Prometheus 做零碎 CPU 使用量告警时,一个 system_load 的指标和本人预期的不太相符:总是在 CPU 余量还很大的状况下达到告警线。为此钻研了一下 Linux 的 Load Average 指标。

以下为原文翻译:

Load Average(以下译为均匀负载)是工程中一个很重要的指标,我的公司应用该指标以及一些其它指标维持着数以百万计的云端实例进行主动扩容。但围绕着这个 Linux 指标始终以来都有一些谜团,比方这个指标不仅追踪正在运行的工作,也追踪处于 uninterruptible sleep 状态(通常是在期待 IO)的工作。这到底是为什么呢?我之前素来没有找到过任何解释。因而这篇文章我将解决这个谜题,对均匀负载指标做一些总结,供所有尝试了解这一指标的人作为参考。

Linux 的均匀负载指标,也即“system load average”,指的是零碎一段时间内须要执行的线程(工作),也即正在运行加正在期待的线程数的平均数。这个指标度量的是零碎须要解决的任务量,能够大于零碎理论正在解决的线程数。大部分工具会展现 1 分钟,5 分钟和 15 分钟的平均值。

$ uptime
 16:48:24 up  4:11,  1 user,  load average: 25.25, 23.40, 23.46

top - 16:48:42 up  4:12,  1 user,  load average: 25.25, 23.14, 23.37

$ cat /proc/loadavg 
25.72 23.19 23.35 42/3411 43603

简略做一些解释:

  • 如果 averages 是 0,示意你的零碎处于闲暇状态。
  • 如果 1 分钟的数值高于 5 分钟或 15 分钟的数值,示意零碎负载正在回升。
  • 如果 1 分钟的数值低于 5 分钟或 15 分钟的数值,示意零碎负载正在降落。
  • 如果这些数值高于 CPU 数量,那么你可能面临着一个性能问题。(当然也要看具体情况)

通过一组三个数值,你能够看出零碎负载是在回升还是降落,这对于你监测零碎情况十分有用。而作为独立数值,这项指标也能够用作制订云端服务主动扩容的规定。但如果想要更粗疏地了解这些数值的含意,你还须要一些其它指标的帮忙。一个独自的值,比方 23-25,自身是没有任何意义的。但如果晓得 CPU 的数量,这个值就能代表一个 CPU-bound 工作负载。

与其尝试对均匀负载进行排错,我更习惯于察看其它几个指标。这些指标将在前面的“更好的指标(Better Metrics)”一章介绍。

历史

最后的均匀负载指标只显示对 CPU 的需要:也即正在运行的程序数量加期待运行的程序数量。在 1973 年 8 月发表的名为“TENEX Load Average”RFC546 文档中有很好的形容:

[1] The TENEX load average is a measure of CPU demand.
The load average is an average of the number of runnable processes over a given time period.
For example, an hourly load average of 10 would mean that (for a single CPU system) at any time during that hour one could expect to see 1 process running and 9 others ready to run (i.e., not blocked for I/O) waiting for the CPU.

这篇文章还链向了一篇 PDF 文档,展现了一幅 1973 年 7 月手绘的均匀负载图(如下所示),表明这个指标曾经被应用了几十年。

现在,这些古老的操作系统源码依然能在网上找到,以下代码片段节选自 TENEX(1970 年代晚期)SCHED.MAC 的宏观汇编程序:

NRJAVS==3               ;NUMBER OF LOAD AVERAGES WE MAINTAIN
GS RJAV,NRJAVS          ;EXPONENTIAL AVERAGES OF NUMBER OF ACTIVE PROCESSES
[...]
;UPDATE RUNNABLE JOB AVERAGES

DORJAV: MOVEI 2,^D5000
        MOVEM 2,RJATIM          ;SET TIME OF NEXT UPDATE
        MOVE 4,RJTSUM           ;CURRENT INTEGRAL OF NBPROC+NGPROC
        SUBM 4,RJAVS1           ;DIFFERENCE FROM LAST UPDATE
        EXCH 4,RJAVS1
        FSC 4,233               ;FLOAT IT
        FDVR 4,[5000.0]         ;AVERAGE OVER LAST 5000 MS
[...]
;TABLE OF EXP(-T/C) FOR T = 5 SEC.

EXPFF:  EXP 0.920043902 ;C = 1 MIN
        EXP 0.983471344 ;C = 5 MIN
        EXP 0.994459811 ;C = 15 MIN

以下是当今 Linux 源码的一个片段(include/linux/sched/loadavg.h):

#define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5           2014            /* 1/exp(5sec/5min) */
#define EXP_15          2037            /* 1/exp(5sec/15min) */

Linux 也硬编码了 1,5,15 分钟这三个常量。

在更老的零碎中也有相似的均匀负载比方 Multics 就有一个指数调度队列平均值(exponential scheduling queue average)。

三个数字

题目的三个数字指的是 1 分钟,5 分钟,15 分钟的均匀负载。但要留神的是这三个数字并不是真正的“均匀”,统计工夫也不是真正的 1 分钟,5 分钟和 15 分钟。从之前的汇编代码能够看出,1,5,15 是等式中的一个常量,而这个等式理论计算的是均匀每 5s 的指数衰减挪动和(exponentially-damped moving sums)(译者:如果你和我一样对这个名词和公式一头雾水,本节随后有相干文章和代码链接)。这样计算出来的 1,5,15 分钟数值能更好地反馈均匀负载。

如果你拿一台闲暇的机器,而后开启一个单线程 CPU-bound 的程序(例如一个单线程循环),那么 60s 后 1min 均匀负载的值应该是多少?如果只是单纯的均匀,那么这个值应该是 1.0。但理论试验后果如下图所示:

被称作“1 分钟均匀负载”的值在 1 分钟的点只达到了 0.62。如果想要理解更多对于这个等式和相似的试验,Neil Gunther 博士写了一篇文章:How It Works,而 loadavg.c 这段 linux 源码也有很多相干计算的正文。

Linux 不可中断工作(Uninterruptible Tasks)

当均匀负载指标第一次呈现在 linux 中时,它们和其它操作系统一样,反映了对 CPU 的需要。但随后,Linux 对它们做了批改,不仅蕴含了可运行的工作,也蕴含了处在不可中断(TASK_UNINTERRUPTIBLE or nr_uninterruptible)状态的工作。这个状态示意程序不想被信号量打断,例如正处于磁盘 I / O 或某些锁中的工作。你以前可能也通过 ps 或者 top 命令察看到过这些工作,它们的状态被标记为“D”。ps 指令的 man page 对此这么解释:“uninterrupible sleep(usually IO)”。

退出了不可中断状态,意味着 Linux 的均匀负载不仅会因为 CPU 应用回升,也会因为一次磁盘(或者 NFS)负载而回升。如果你相熟其它操作系统以及它们的 CPU 均匀负载概念,那么蕴含不可中断状态的 Linux 的均匀负载在一开始会让人难以了解。

为什么呢?为什么 Linux 要这么做?

有有数的对于均匀负载的文章指出 Linux 退出了 nr_uninterruptible,但我没有见过任何一篇解释过这么做的起因,甚至连对起因的大胆猜想都没有。我集体猜想这是为了让指标示意更狭义的对资源的需要的概念,而不仅仅是对 CPU 资源的需要。

搜查一个古老的 Linux 补丁

想理解 Linux 中一个货色为什么被扭转了很简略:你能够带着问题找到这个文件的 git 提交历史,读一读它的改变阐明。我查看了 loadavg.c 的改变历史,但增加不可中断状态的代码是从一个更早的文件拷贝过去的。我又查看了那个更早的文件,但这条路也走不通:这段代码穿插在几个不同的文件中。我心愿找到一条捷径,就应用 git log - p 下载了整个蕴含了 4G 文本文件的 Linux github 库,想回溯看这段代码第一次呈现在什么时候,但这也是条绝路:在整个 Linux 工程中,最老的改变要追溯到 2005 年,Linux 引入了 2.6.12-rc2 版本的时候,但这个批改此时曾经存在了。

在网上还有 Linux 的历史版本库(这里和这里),但在这些库中也没有对于这个改变的形容。为了最起码找到这个改变是什么时候产生的,我在 kernel.org 搜寻了源码,发现这个改变在 0.99.15 曾经有了,而 0.99.13 还没有,然而 0.99.14 版本失落了。我又在其它中央找到了这个版本,并且确认改变是在 1993 年 11 月在 Linux 0.99 patchlevel 14 上实现的。寄希望于 Linus 在 0.99.14 的公布形容中会解释为什么改变,但后果也是死胡同:

“Changes to the last official release (p13) are too numerous to mention (or even to remember)…” – Linus

他提到了很多次要改变,但并没有解释均匀负载的批改。

基于这个工夫点,我想在要害邮件列表存档中查找真正的补丁源头,但最老的一封邮件工夫是 1995 年 6 月,系统管理员写下:

“While working on a system to make these mailing archives scale more effecitvely I accidently destroyed the current set of archives (ah whoops).”

我的探寻之路好像被咒骂了。侥幸的是,我找到了一些更老的从备份服务器中复原进去的 linux-devel 邮件列表存档,它们用 tar 压缩包的模式存储着摘要。我搜寻了 6000 个摘要,蕴含了 98000 封邮件,其中 30000 封来自 1993 年。但不知为何,这些邮件都曾经遗失了。看来原始补丁的形容很可能曾经永恒失落了,为什么这么做依然是一个谜。

“不可中断”的起源

谢天谢地,我最终在 oldlinux.org 网站上一个来自于 1993 年的邮箱压缩文件中找到了这个改变,内容如下:

From: Matthias Urlichs <urlichs@smurf.sub.org>
Subject: Load average broken ?
Date: Fri, 29 Oct 1993 11:37:23 +0200


The kernel only counts "runnable" processes when computing the load average.
I don't like that; the problem is that processes which are swapping or
waiting on "fast", i.e. noninterruptible, I/O, also consume resources.

It seems somewhat nonintuitive that the load average goes down when you
replace your fast swap disk with a slow swap disk...

Anyway, the following patch seems to make the load average much more
consistent WRT the subjective speed of the system. And, most important, the
load is still zero when nobody is doing anything. ;-)

--- kernel/sched.c.orig Fri Oct 29 10:31:11 1993
+++ kernel/sched.c  Fri Oct 29 10:32:51 1993
@@ -414,7 +414,9 @@
    unsigned long nr = 0;

    for(p = &LAST_TASK; p > &FIRST_TASK; --p)
-       if (*p && (*p)->state == TASK_RUNNING)
+       if (*p && ((*p)->state == TASK_RUNNING) ||
+                  (*p)->state == TASK_UNINTERRUPTIBLE) ||
+                  (*p)->state == TASK_SWAPPING))
            nr += FIXED_1;
    return nr;
 }
--
Matthias Urlichs        \ XLink-POP N|rnberg   | EMail: urlichs@smurf.sub.org
Schleiermacherstra_e 12  \  Unix+Linux+Mac     | Phone: ...please use email.
90491 N|rnberg (Germany)  \   Consulting+Networking+Programming+etc'ing      42

这种浏览 24 年前一个改变背地想法的感觉很微妙。

这证实了对于均匀负载的改变是无意为之,目标是为了反映对 CPU 以及对其它系统资源的需要。Linux 的这项指标从“CPU load average”变为了“system load average”。

邮件中举的应用更慢的磁盘的例子很有情理:通过升高零碎性能,对系统资源的需要应该减少。但当应用了更慢的磁盘时,均匀负载指标实际上升高了。因为这些指标只跟踪了处在 CPU 运行状态的工作,没有思考处在磁盘替换状态的工作。Matthias 认为这不合乎直觉,因而他做了相应的批改。

“不可中断”的今日

一个问题是,明天如果你发现有时零碎的均匀负载过高,光靠 disk I/ O 是否曾经不足以解释?答案是必定的,因为我会猜想 Linux 代码中退出了 1993 年时并不存在的设置 TASK_UNINTERRUPTIBLE 分支,进而导致均匀负载过高。在 Linux 0.99.14 中,有 13 条代码门路将工作状态设置为 TASK_UNINTERRUPIBLE 或是 TASK_SWAPPING(之后这个状态被从 Linux 中移除了)。时至今日,在 Linux 4.12 中,有靠近 400 条代码分支设置了 TASK_INTERRUPTIBLE 状态,包含了一些加锁机制。很有可能其中的一些分支不应该被包含在均匀负载统计中。下次如果我发现均匀负载很高的状况,我会查看是否进入了不应被蕴含的分支,并看一下是否能进行一些修改。

我为此第一次给 Matthias 发了邮件,来问一问他当下对 24 年前改变的认识。他在一个小时内(就像我在 twitter 中说的一样)就回复了我,内容如下:

“The point of “load average” is to arrive at a number relating how busy the system is from a human point of view. TASK_UNINTERRUPTIBLE means (meant?) that the process is waiting for something like a disk read which contributes to system load. A heavily disk-bound system might be extremely sluggish but only have a TASK_RUNNING average of 0.1, which doesn’t help anybody.”

(能这么快地收到回复,其实光是收到回复,就曾经让我兴奋不已了,感激!)

所以 Matthias 依然认为这个指标是正当的,至多给出了本来 TASK_UNINTERRUPTIBLE 的含意。

但 Linux 衍化至今,TASK_UNINTERRUPIBLE 代表了更多货色。咱们是否应该把均匀负载指标变为仅仅表征 CPU 和 disk 需要的指标呢?Scheduler 的维护者 Peter Zijstra 曾经给我发了一个取巧的形式:在均匀负载中应用 task_struct->in_iowait 来代替 TASK_UNINTERRUPTIBLE,用以更严密地匹配磁盘 I /O。这就引出了另外一个问题:到底什么才是咱们想要的呢?咱们想要的是度量零碎的线程需要,还是想要剖析零碎的物理资源需要?如果是前者,那么期待不可中断锁的工作也应该包含在内,它们并不是闲暇的。从这个角度思考,均匀负载指标以后的工作形式可能恰是咱们所冀望的。

为了更好地了解“不可中断”的代码分支,我更乐于做一些理论剖析。咱们能够检测不同的例子,量化执行工夫,来看一下均匀负载指标是否正当。

度量不可中断的工作

上面是一台生产环境服务器的 Off-CPU 火焰图,我过滤出了 60 秒内的内核栈中处于 TASK_UNINTERRUPTIBLE 状态的工作,这能够提供很多指向了 uninterruptible 代码分支的例子:

<embed src=”http://www.brendangregg.com/blog/images/2017/out.offcputime_unint02.svg” />

如果你不相熟 Off-CPU 火焰图:每一列是一个工作的残缺塔形栈,它们组成了火焰的样子。你能够点击每个框来放大察看残缺的栈。x 轴大小与工作破费在 off-CPU 上的工夫成正比,而从左到右的排序没有什么理论含意。off-CPU 栈的色彩我应用蓝色(在 on-CPU 图上我应用寒色),色彩的饱和度是随机生成的,用以辨别不同的框。

我应用我在 bcc 工程下的 offcputime 工具来生成这张图,指令如下:

# ./bcc/tools/offcputime.py -K --state 2 -f 60 > out.stacks
# awk '{print $1, $2 / 1000}' out.stacks | ./FlameGraph/flamegraph.pl --color=io --countname=ms > out.offcpu.svgb>

awk 命令将微秒输入为毫秒,–state 2 示意 TASK_UNINTERRUPTIBLE(参见 sched.h 文件),是我为这篇文章增加的一个可选参数。第一次这么做的人是 Facebook 的 Josef Bacik,应用他的 kernelscope 工具,这个工具也应用了 bcc 和火焰图。在我的例子中,我只展现了内核栈,而 offcputime.py 也反对展现用户栈。

这幅图显示,在 60s 中 uninterruptible 睡眠只破费了 926ms,这只让咱们的均匀负载减少了 0.015。这些工夫大部分花在 cgroup 相干代码上,disk I/ O 并没有花太多工夫。

上面是一张更乏味的图,只笼罩了 10s 的工夫:

<embed src=”http://www.brendangregg.com/blog/images/2017/out.offcputime_unint01.svg” />

图形右侧比拟宽的工作示意 proc_pid_cmdline_read()(参考 /proc/PID/cmdline)中的 systemd-journal 工作,被阻塞并对均匀负载奉献了 0.07。而左侧更宽的图形示意一个 page_fault,同样以 rwsem_down_read_failed()完结,对均匀负载奉献了 0.23。联合火焰图的搜寻个性,我曾经应用品红高亮了相干函数,这个函数的源码片段如下:

    /* wait to be given the lock */
    while (true) {set_task_state(tsk, TASK_UNINTERRUPTIBLE);
        if (!waiter.task)
            break;
        schedule();}

这是一段应用 TASK_UNINTERRUPTIBLE 获取锁的代码。Linux 对于互斥锁的获取有可中断和不可中断的实现形式(例如 mutex_lock()和 mutex_lock_interruptible(),以及对信号量的 down()和 down_interruptible()),可中断版本容许工作被信号中断,唤醒后持续解决。处在不可中断的锁中睡眠的工夫通常不会对均匀负载造成很大的影响。但在这个例子中,这类工作减少了 0.30 的均匀负载。如果这个值再大一些,就值得剖析是否有必要缩小锁的竞争来优化性能,升高均匀负载(例如我将开始钻研 systemd-journal 和 proc_pid_cmdline_read())。

那么这些代码门路应该被蕴含在均匀负载统计中吗?我认为应该。这些线程处在执行过程中,而后被锁阻塞。它们并不是闲暇的,它们对系统有须要,只管需要的是软件资源而非硬件资源。

分拆 Linux Load Averages

那么 Linux 均匀负载是否被齐全拆成几个部件呢?上面是一个例子:在一台闲暇的有 8 个 CPU 的零碎上,我调用 tar 来打包一些未缓存的文件。这个过程会破费几分钟,大部分工夫被阻塞在读磁盘上。上面是从三个终端窗口收集的数据:

terma$ pidstat -p `pgrep -x tar` 60
Linux 4.9.0-rc5-virtual (bgregg-xenial-bpf-i-0b7296777a2585be1)     08/01/2017  _x86_64_    (8 CPU)

10:15:51 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
10:16:51 PM     0     18468    2.85   29.77    0.00   32.62     3  tar

termb$ iostat -x 60
[...]
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.54    0.00    4.03    8.24    0.09   87.10

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
xvdap1            0.00     0.05   30.83    0.18   638.33     0.93    41.22     0.06    1.84    1.83    3.64   0.39   1.21
xvdb            958.18  1333.83 2045.30  499.38 60965.27 63721.67    98.00     3.97    1.56    0.31    6.67   0.24  60.47
xvdc            957.63  1333.78 2054.55  499.38 61018.87 63722.13    97.69     4.21    1.65    0.33    7.08   0.24  61.65
md0               0.00     0.00 4383.73 1991.63 121984.13 127443.80    78.25     0.00    0.00    0.00    0.00   0.00   0.00

termc$ uptime
 22:15:50 up 154 days, 23:20,  5 users,  load average: 1.25, 1.19, 1.05
[...]
termc$ uptime
 22:17:14 up 154 days, 23:21,  5 users,  load average: 1.19, 1.17, 1.06

我也同样为不可中断状态的工作收集了 Off-CPU 火焰图:

<embed src=”http://www.brendangregg.com/blog/images/2017/out.offcputime_unint08.svg” />

最初一分钟的均匀负载是 1.19,让咱们来合成一下:

  • 0.33 来自于 tar 的 CPU 工夫(pidstat)
  • 0.67 来自于不可中断的磁盘读(off-CPU 火焰图中显示的是 0.69,我狐疑是因为脚本收集数据稍晚了一些,造成了工夫上的一些渺小的误差)
  • 0.04 来自于其它 CPU 消费者(iostat user + system,减去 pidstat 中 tar 的 CPU 工夫)
  • 0.11 来自于内核态解决不可中断的 disk I/ O 的工夫,向磁盘写入数据(通过 off-CPU 火焰图,左侧的两个塔)

这些加起来是 1.15,还少了 0.04。一部分可能源自于四舍五入,以及测量距离的偏移造成的误差,但大部分应该还是因为均匀负载应用的是“指数衰减偏移和”,而其它的平均数(pidstat,iostat)就是一般的均匀。在 1.19 之前一分钟的均匀负载是 1.25,因而这一分钟的值会拉高下一分钟的均匀负载。会拉高多少呢?依据之前的图,在咱们统计的一分钟里,有 62% 来自于以后的一分钟工夫。所以 0.62 1.15 + 0.38 1.25 = 1.18,和报告中的 1.19 很靠近了。

这个例子中,零碎里有一个线程 (tar) 加上一小部分其它线程(也有一些内核态工作线程)在工作,因而 Linux 报告均匀负载 1.19 是说得通的。如果只显示“CPU 均匀负载”,那么值会是 0.37(依据 mpstat 的报告),这个值只针对 CPU 资源正确,但暗藏了零碎上理论有超过一个线程须要维持工作的事实。

通过这个例子我想阐明的是均匀负载统计的数字(CPU+ 不可中断)确实是有意义的,而且你能够合成并计算出各个组成部分。

(作者在原文评论中阐明了计算这些数值的形式:)

tar: the off-CPU flame graph has 41,164 ms, and that’s a sum over a 60 second trace. Normalizing that to 1 second = 41.164 / 60 = 0.69. The pidstat output has tar taking 32.62% average CPU (not a sum), and I know all its off-CPU time is in uninterruptible (by generating off-CPU graphs for the other states), so I can infer that 67.38% of its time is in uninterruptible. 0.67. I used that number instead, as the pidstat interval closely matched the other tools I was running.
by mpstat I meant iostat sorry (I updated the text), but it’s the same CPU summary. It’s 0.54 + 4.03% for user + sys. That’s 4.57% average across 8 CPUs, 4.57 x 8 = 36.56% in terms of one CPU. pidstat says that tar consumed 32.62%, so the remander is 36.56% – 32.62% = 3.94% of one CPU, which was used by things that weren’t tar (other processes). That’s the 0.04 added to load average.

了解 Linux Load Averages

我成长在均匀负载只表白 CPU 负载的操作系统环境中,因而 Linux 版的均匀负载常常让我很困扰。或者根本原因是词语“均匀负载”就像“I/O”一样意义不明:到底是什么 I / O 呢?磁盘 I /O?文件系统 I /O?网络 I /O?…,同样的,到底是哪些负载呢?CPU 负载?还是零碎负载?用上面的形式解释能让我了解均匀负载这个指标:

  • 在 Linux 零碎中,均匀负载是(或心愿是)“零碎均匀负载”,将零碎作为一个整体,来度量所有工作中或期待(CPU,disk,不可中断锁)中的线程数量。换句话说,指标掂量的是所有不齐全处在 idle 状态的线程数量。长处:囊括了不同品种资源的需要。
  • 在其它操作系统中:均匀负载是“CPU 均匀负载”,度量的是占用 CPU 运行中或期待 CPU 的线程数量。长处:了解起来,解释起来都很简略(因为只须要思考 CPU)。

请留神,还有另一种可能的均匀负载,即“物理资源均匀负载”,只囊括物理资源(CPU + disk)

或者有一天咱们会为 Linux 增加不同的均匀负载,让用户来抉择应用哪一个:一个独立的“CPU 均匀负载”,“磁盘均匀负载”和“网络均匀负载”等等。或者简略的把所有不同指标都列举进去。

什么是一个好的或坏的均匀负载?

一些人找到了对他们的零碎及工作负载有意义的值:当均匀负载超过这个值 X 时,利用时延飙高,且用户会开始投诉。但如何失去这个值实际上并没有什么法则。

如果应用 CPU 均匀负载,人们能够用数值除以 CPU 核数,而后说如果比值超过 1.0,你的零碎就处于饱和状态,可能会引起性能问题。但这也很不置可否,因为一个长期的平均值(至多 1 分钟)也可能暗藏掉一些变动。比方比值 1.5 对于一个零碎来说,可能工作地还不错,但对另一个零碎而言,比值突升到 1.5,这一分钟的性能体现可能就会很蹩脚。

我已经治理过一台双核邮件服务器,平时运行时 CPU 均匀负载在 11 到 16 之间(比值就是 5.5 到 8),时延还是能够承受的,也没有人埋怨。但这是一个极其的例子,大部分零碎可能比值超过 2 对服务性能就有很大影响了。

而对于 Linux 的零碎均匀负载,状况就更简单更含糊了,因为这个指标蕴含了各种不同的资源类型,因而你不能单纯地间接除以 CPU 核数。此时应用相对值比拟更无效:如果你晓得零碎在均匀负载为 20 时工作地很好,而当初均匀负载曾经达到 40 了,那么你就该联合其它指标看一看到底产生了什么。

更好的指标

当 Linux 的均匀负载指标上升时,你晓得你的零碎须要更好的资源(CPU,disk 以及一些锁),但你其实并不确定须要哪一个。那么你就能够用一些其它指标来辨别。例如,对于 CPU:

  • per-CPU utilization:应用 mpstat -P ALL 1;
  • per-process CPU utilization:应用 top,pidstat 1 等等;
  • per-thread run queue(scheduler) latency:应用 in /proc/PID/schedstats,delaystats,pref sched;
  • CPU run queue latency:应用 in /proc/schedstat,perf sched,我的 runqlat bcc 工具;
  • CPU run queue length:应用 vmstat 1,察看 ’r’ 列,或者应用我的 runqlen bcc 工具。

前两个指标评估的是利用率,后三个是饱和度指标。利用率指标用来形容工作负载,而饱和度指标则用来甄别性能问题。示意 CPU 饱和度的最佳指标是 run queue(或者 scheduler)latency:工作或线程处在可运行状态但须要期待运行的工夫。这些指标能够帮忙你度量性能问题的重大水平,例如一个工作处在等待时间的百分比。度量 run queue 的长度也能够发现问题,不过难以度量重大水平。

schedstats 组件在 Linux 4.6 中被设置成了内核可调整,并且改为了默认敞开。cpustat 的提早统计同样统计了 scheduler latency 指标,我也刚刚倡议把它加到 htop 中去,这样能够大大简化大家的应用,要比从 /proc/sched_debug 的输入中抓取等待时间指标简略。

$ awk 'NF > 7 {if ($1 =="task") {if (h == 0) {print; h=1} } else {print} }' /proc/sched_debug
            task   PID         tree-key  switches  prio     wait-time             sum-exec        sum-sleep
         systemd     1      5028.684564    306666   120        43.133899     48840.448980   2106893.162610 0 0 /init.scope
     ksoftirqd/0     3 99071232057.573051   1109494   120         5.682347     21846.967164   2096704.183312 0 0 /
    kworker/0:0H     5 99062732253.878471         9   100         0.014976         0.037737         0.000000 0 0 /
     migration/0     9         0.000000   1995690     0         0.000000     25020.580993         0.000000 0 0 /
   lru-add-drain    10        28.548203         2   100         0.000000         0.002620         0.000000 0 0 /
      watchdog/0    11         0.000000   3368570     0         0.000000     23989.957382         0.000000 0 0 /
         cpuhp/0    12      1216.569504         6   120         0.000000         0.010958         0.000000 0 0 /
          xenbus    58  72026342.961752       343   120         0.000000         1.471102         0.000000 0 0 /
      khungtaskd    59 99071124375.968195    111514   120         0.048912      5708.875023   2054143.190593 0 0 /
[...]
         dockerd 16014    247832.821522   2020884   120        95.016057    131987.990617   2298828.078531 0 0 /system.slice/docker.service
         dockerd 16015    106611.777737   2961407   120         0.000000    160704.014444         0.000000 0 0 /system.slice/docker.service
         dockerd 16024       101.600644        16   120         0.000000         0.915798         0.000000 0 0 /system.slice/
[...]

除了 CPU 指标,你也能够找到度量磁盘设施使用率和饱和度的指标。我次要应用 USE method 中的指标,并且会参考它们的 Linux Checklist。

只管有很多更加具体的指标,但这并不意味着均匀负载指标没用。均匀负载配合其它指标能够胜利利用在云计算微服务的主动扩容策略中,可能帮忙微服务应答 CPU、磁盘等不同起因造成的负载回升。有了主动扩容策略,即便造成了谬误的扩容(烧钱)也比不扩容(影响用户)要平安,因而人们会偏向于在主动扩容中退出更多的信号。如果某次主动扩容扩了太多,咱们也可能到第二天进行 debug。

促使我持续应用均匀负载指标的另一个起因是它们(三个数字)能示意历史信息。如果我须要去查看云端一台实例为什么体现很差,而后登录到那台机器上,看到 1 分钟均匀负载曾经大大低于 15 分钟均匀负载,那我就晓得我错过了之前产生的性能问题。我只需几秒钟思考均匀负载数值就能失去这个论断,而不须要去钻研其它指标。

总结

在 1993 年,一位 Linux 工程师发现了一个均匀负载体现不直观的问题,而后应用一个三行代码的补丁永恒地把 Load Average 指标从“CPU 均匀负载”变成了,可能叫做“零碎均匀负载”更适合的指标。他的改变蕴含了处在不可中断状态的工作,因而均匀负载反映了工作对 CPU 以及磁盘的需要。零碎负载平衡指标计算了工作中以及期待工作中的线程数量,并且应用 1,5,15 这三个常数,通过一个非凡公式计算出了三个“指数衰减偏移和”。这三个数字让你理解你的零碎负载是在减少还是缩小,它们的最大值可能能够用来做绝对比拟,以确定零碎是否有性能问题。

在 Linux 内核代码中,不可中断状态的状况越来越多,到明天不可中断状态也蕴含了获取锁的状态。如果均匀负载是一个用来计算正在运行以及正在期待的线程数量(而不是严格示意线程期待硬件资源)的指标,那么它们的数值依然合乎预期。

在这篇文章中,我开掘了这个来自 1993 年的补丁——寻找过程出其不意地艰难——并看到了作者最后的解释。我也在古代 Linux 零碎上通过 bcc/eBPF 钻研了处在不可中断状态的工作的堆栈和破费工夫,并且把这些示意成了一幅 off-CPU 火焰图。在图中提供了不少处在不可中断状态的例子,能够随时用来解释为什么均匀负载的值飙高。同时我也提出了一些其它指标来帮忙你理解零碎负载细节。

我会援用 Linux 源码中 scheduler 维护者 Peter Zijlstra 写在 kernel/sched/loadavg.c 顶部的正文来完结这篇文章:

  * This file contains the magic bits required to compute the global loadavg
  * figure. Its a silly number but people think its important. We go through
  * great pains to make it work on big machines and tickless kernels.

参考资料

[1] Saltzer, J., and J. Gintell.“The Instrumentation of Multics,”CACM, August 1970(解释了指数)
[2] Multics system_performance_graph command reference(提到了 1 分钟均匀负载)
[3] TENEX source code.(CHED.MAC 零碎中的均匀负载代码)
[4] RFC 546 “TENEX Load Averages for July 1973”.(解释了对 CPU 需要的度量)
[5] Bobrow, D., et al.“TENEX: A Paged Time Sharing System for the PDP-10,”Communications of the ACM, March 1972.(解释了三重均匀负载)
[6] Gunther, N. “UNIX Load Average Part 1: How It Works” PDF.(解释了指数计算公式)
[7] Linus’s email about Linux 0.99 patchlevel 14.
[8] The load average change email is on oldlinux.org.(在 alan-old-funet-lists/kernel.1993.gz 压缩包中,不在我一开始搜寻的 linux 目录下)
[9] The Linux kernel/sched.c source before and after the load average change: 0.99.13, 0.99.14.
[10] Tarballs for Linux 0.99 releases are on kernel.org.
[11] The current Linux load average code: loadavg.c, loadavg.h
[12] The bcc analysis tools includes my offcputime, used for tracing TASK_UNINTERRUPTIBLE.
[13] Flame Graphs were used for visualizing uninterruptible paths.

正文完
 0