剖析《Linux 平均负载:解开谜团》【转】

转自:https://blog.csdn.net/cs_tech/article/details/126563993

目录

原文翻译

历史

这3个数字

Linux Uninterruptible Tasks

寻找一个古老的 Linux 补丁

uninterruptible 的由来

今天的 uninterruptible

测量 uninterruptible tasks

分解 Linux 平均负载

理解 Linux 平均负载

什么是“好”或“坏”的平均负载?

更好的指标

结论

剖析

指数加权移动平均

什么是指数加权移动平均?

指数加权平均的作用是什么?

Linux 计算平均负载的代码怎么实现?

分解 Linux 平均负载的背后逻辑是什么?

本文是阅读 Brendan 的杰作 Linux Load Averages: Solving the Mystery 后的一些总结,欢迎大家指教。

原文翻译
负载平均数是一个行业关键指标--我的公司花费了数百万美元根据它和其他指标自动扩展云实例--但在Linux上,它有一些神秘的地方。Linux负载平均数不仅跟踪 runnable 的任务,而且还跟踪处于 uninterruptible sleep 状态的任务。为什么呢?我从来没有看到过一个解释。在这篇文章中,我将解开这个谜团,并对负载平均数进行总结,为每个试图解释它们的人提供参考。

Linux 负载平均值是“系统负载平均值”,它显示了系统上正在运行的线程(任务)需求,是正在运行的线程加上等待的线程的平均数量。这可以衡量需求,这可能大于系统当前正在处理的数量。大多数工具显示三个平均值,分别为 1、5 和 15 分钟:

:/proc # uptime
21:09:38 up 6:51, 0 users, load average: 2.58, 2.64, 2.43
:/proc # cat loadavg
2.58 2.64 2.43 3/7364 29138
一些解释:

如果平均数是0.0,那么你的系统是空闲的。

如果1分钟的平均数高于5分钟或15分钟的平均数,那么负载在增加。

如果1分钟的平均数低于5分钟或15分钟的平均数,那么负载在减少。

如果它们高于你的CPU数量,那么你可能有一个性能问题(这取决于)。

作为一个三元组,您可以判断负载是增加还是减少,这很有用。当需要单个需求值时,它们也很有用,例如云自动缩放规则。但是如果没有其他指标的帮助,很难更详细地理解它们。 23 - 25 的单个值本身没有任何意义,但如果 CPU 数量已知并且已知它是受 CPU 限制的工作负载,则可能意味着什么。

我通常不会尝试调试负载平均值,而是切换到其他指标。我将在接近结尾的“更好的指标”部分讨论这些。

历史
原始负载平均值仅显示 CPU 需求:正在运行的进程数加上等待运行的进程数。在 1973 年 8 月的 RFC 546 标题为“TENEX Load Averages”中有一个很好的描述:

[1] TENEX负载平均值是对CPU需求的一种衡量。负载平均值是指在给定时间段内可运行进程数量的平均值。例如,每小时负荷平均值为10,这意味着(对于一个单CPU系统)在该小时内的任何时候,人们可以看到1个正在运行的进程和其他9个准备运行的进程(即,没有因为 I/O 而阻塞)等待CPU。

ietf.org 上的这个版本链接到 1973 年 7 月手绘负载平均图的 PDF 扫描,显示这已经被监控了几十年:

 

 

 

 


source: https://tools.ietf.org/html/rfc546
​如今,旧操作系统的源代码也可以在网上找到。这是来自 TENEX(1970 年代初期)SCHED.MAC 的 DEC 宏程序集的一个例外:

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,它具有指数调度队列平均值。

这3个数字
这三个数字是 1、5 和 15 分钟的平均负载。除了它们不是真正的平均值,它们也不是 1、5 和 15 分钟。从上面的源代码中可以看出,1、5 和 15 分钟是方程中使用的常数,用于计算 5 秒平均值的指数加权移动平均。生成的 1、5 和 15 分钟负载平均值反映了远远超过 1、5 和 15 分钟的负载。

如果您使用一个空闲系统,然后开始一个单线程 CPU 密集型工作负载(一个循环中的一个线程),那么 60 秒后一分钟的平均负载是多少?如果它是一个普通的平均值,它将是 1.0。这是那个实验,图表:

 

 


负载平均实验以可视化指数阻尼
所谓的“一分钟平均值”,到一分钟大关才达到0.62左右。有关方程和类似实验的更多信息,Neil Gunther 博士写了一篇关于平均负载的文章:How It Works,此外 loadavg.c 中有许多 Linux 源代码块注释。

Linux Uninterruptible Tasks
当负载平均值首次出现在 Linux 中时,它们反映了 CPU 需求,与其他操作系统一样。但后来在 Linux 上将它们更改为不仅包括可运行(runnable)任务,还包括处于不可中断状态的任务(TASK_UNINTERRUPTIBLE 或 nr_uninterruptible)。此状态由希望避免信号中断的代码路径使用,其中包括在磁盘 I/O 上阻塞的任务和一些锁。您之前可能已经看到过这种状态:它在输出 ps 和 top 中显示为“D”状态。 ps(1) 手册页称其为“不间断睡眠(通常为 IO)”。

添加不间断状态意味着 Linux 平均负载可能会因磁盘(或 NFS)I/O 工作负载而增加,而不仅仅是 CPU 需求。对于熟悉其他操作系统及其 CPU 平均负载的每个人来说,包括这种状态,起初都令人深感困惑。

为什么?确切地说,为什么 Linux 会这样做?

有无数关于负载平均数的文章,其中许多指出了Linux nr_uninterruptible 的问题。但我没有看到任何一篇文章解释或甚至猜测为什么它被包括在内。我自己的猜测是,它是为了反映更广泛意义上的需求,而不仅仅是CPU需求。

寻找一个古老的 Linux 补丁
理解为什么Linux中的东西会发生变化是很容易的:你可以阅读有关文件的git提交历史,并阅读变化描述。我检查了 loadavg.c 的历史,但增加不间断状态的改动比该文件早,而该文件是用先前文件的代码创建的。我检查了另一个文件,但那条线索也很冷:代码本身已经在不同的文件中跳来跳去。为了走捷径,我转储了整个Linux github仓库的 "git log -p",它有4G字节的文本,并开始向后阅读它,看看代码何时首次出现。这也是一个死胡同。整个Linux版本中最古老的变化可以追溯到2005年,当时Linus导入了Linux 2.6.12-rc2,而这个变化早于这个时间。

有一些历史上的Linux版本(这里 和 那里),但是这些版本中也没有这个变化的描述。我试图发现,至少这个变化是什么时候发生的,我在 kernel.org上搜索了tarballs,发现它在0.99.15时发生了变化,而不是在0.99.13时--然而,0.99.14的tarball却不见了。我在其他地方找到了它,并确认该变化是在Linux 0.99 patchlevel 14,1993年11月。我希望Linus对0.99.14的发布说明能够解释这一变化,但那也是一个死胡同。

"与上一个正式版本(P13)相比,变化太多,无法提及(甚至无法记住)......" - Linus

他提到了重大变化,但没有提到平均负载变化。

根据日期,我查找了内核邮件列表档案,以找到实际的补丁,但现有的最古老的邮件是1995年6月的,当时系统管理员写道。

"当我在研究一个系统,使这些邮件档案更有效地扩展时,我不小心破坏了当前的档案集(啊呜)。"

我的搜索开始感到被诅咒了。值得庆幸的是,我找到了一些较早的Linux-devel邮件列表档案,这些档案是从服务器备份中抢救出来的,通常以文摘的tarballs形式存储。我搜索了6000多份摘要,包含98000多封邮件,其中30000封是1993年的。但不知何故,所有这些邮件中都没有它。看起来,原始的补丁描述可能永远失去了,而 "为什么 "将是一个谜。

uninterruptible 的由来
幸运的是,我终于在 oldlinux.org 上 1993 年的压缩邮箱文件中找到了变化。这里是:

From: Matthias Urlichs <[email protected]>
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: [email protected]
Schleiermacherstra_e 12 \ Unix+Linux+Mac | Phone: ...please use email.
90491 N|rnberg (Germany) \ Consulting+Networking+Programming+etc'ing 42
内核在计算平均负载时只计算“可运行”进程。我不喜欢那样;问题是正在“快速”交换或等待的进程,即不可中断的 I/O,也会消耗资源。 当您用慢速交换磁盘替换快速交换磁盘时,平均负载下降似乎有点不直观...... 无论如何,下面的补丁似乎使负载平均值更加一致 WRT 系统的主观速度。而且,最重要的是,当没有人做任何事情时,负载仍然为零。 ;-)

阅读近24年前的这一变化背后的想法,真是令人惊讶。

这证实了负载平均数被故意改变以反映对其他系统资源的需求,而不仅仅是CPU。Linux从 "CPU负载平均数 "变为人们所说的 "系统负载平均数"。

他使用较慢的交换盘的例子是有道理的:通过降低系统的性能,对系统的需求(以运行+排队的方式衡量)应该增加。然而,负载平均数下降了,因为他们只跟踪了CPU的运行状态,而不是交换状态。马蒂亚斯认为这是不直观的,确实如此,所以他修复了它。

今天的 uninterruptible
但是,Linux 平均负载是否有时会过高,超过磁盘 I/O 可以解释的程度?是的,虽然我的猜测是这是由于使用 TASK_UNINTERRUPTIBLE 的新代码路径在 1993 年不存在。在 Linux 0.99.14 中,有 13 个代码路径直接设置 TASK_UNINTERRUPTIBLE 或 TASK_SWAPPING(交换状态后来从 Linux 中删除)。如今,在 Linux 4.12 中,有近 400 个设置 TASK_UNINTERRUPTIBLE 的代码路径,包括一些锁原语。这些代码路径之一可能不应该包含在负载平均值中。下次我的平均负载似乎太高时,我会看看是否是这种情况,是否可以修复。

我(第一次)给 Matthias 发了电子邮件,询问他对将近 24 年后的平均负载变化有何看法。他在一小时内回复(正如我在推特上提到的),并写道:

““平均负载”的目的是从人类的角度得出一个与系统有多忙有关的数字。TASK_UNINTERRUPTIBLE 表示(意思是?)进程正在等待诸如磁盘读取之类的东西,这会增加系统负载。一个严重的磁盘绑定系统可能非常缓慢,但平均只有 0.1 的 TASK_RUNNING,这对任何人都没有帮助。”

(如此迅速地得到回应,甚至得到回应,真的让我很开心。谢谢!)

所以 Matthias 仍然认为这是有道理的,至少考虑到 TASK_UNINTERRUPTIBLE 过去的含义。

但是今天 TASK_UNITERRUPTIBLE 匹配更多的东西。我们是否应该将平均负载更改为 CPU 和磁盘需求?调度程序维护者 Peter Zijstra 已经给我发送了一个聪明的选项来探索这样做:在平均负载中包含 task_struct->in_iowait 而不是 TASK_UNINTERRUPTIBLE,以便它更接近地匹配磁盘 I/O。然而,这引出了另一个问题,我们真正想要的是什么?我们是想根据线程来衡量对系统的需求,还是仅仅对物理资源的需求?如果是前者,则应包括等待 uninterruptible 锁,因为系统需要这些线程。他们没有闲着。所以也许 Linux 平均负载已经按照我们想要的方式工作了。

为了更好地理解 uninterruptible 的代码路径,我想要一种在行动中测量它们的方法。然后我们可以检查不同的例子,量化他们花费的时间,看看这一切是否有意义。

测量 uninterruptible tasks
下面是一个来自生产服务器的 Off-CPU flame graph 火焰图,时间跨度为60秒,只显示了内核堆栈,其中我过滤后只包括那些处于TASK_UNINTERRUPTIBLE 状态的堆栈(SVG)。它提供了许多 uninterruptible 的代码路径的示例:

 

 

 

如果您不熟悉 off-CPU 火焰图:您可以单击帧放大,检查显示为帧塔的完整堆栈。 x 轴大小与阻塞在 off-CPU 的时间成正比,排序顺序(从左到右)没有实际意义。off-CPU 堆栈的颜色为蓝色(我对 CPU 堆栈使用暖色),饱和度具有随机变化来区分帧。

我使用来自 bcc 的 offcputime 工具(该工具需要 Linux 4.8+ 的 eBPF 功能)和我的火焰图软件生成了这个:

# ./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 将输出从微秒更改为毫秒。 offcputime "--state 2" 匹配 TASK_UNINTERRUPTIBLE(参见 sched.h),这是我刚刚为这篇文章添加的一个选项。 Facebook 的 Josef Bacik 首先用他的 kernelscope 工具做到了这一点,该工具也使用 bcc 和火焰图。在我的示例中,我只是显示内核堆栈,但 offcputime.py 也支持显示用户堆栈。

至于上面的火焰图:它显示,60秒中只有926毫秒是在 uninterruptible 的睡眠中度过的。这只是给我们的负载平均数增加了0.015。这是一些cgroup路径中的时间,但是这个服务器没有做太多的磁盘I/O。

这是一个更有趣的,这次只跨越 10 秒(SVG):

 

 

 

​右边的宽塔显示systemd-journal在 proc_pid_cmdline_read() 中(读取 /proc/PID/cmdline)被阻塞,对平均负载有 0.07 的贡献。而左边有一个更宽的页面故障塔,最后也出现在 rwsem_down_read_failed() 中(给平均负载增加0.23)。我使用火焰图的搜索功能将这些函数用洋红色标出。下面是rwsem_down_read_failed()的摘录。

/* wait to be given the lock */
while (true) {
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
if (!waiter.task)
break;
schedule();
}
这是使用 TASK_UNINTERRUPTIBLE 的锁获取代码。 Linux 具有互斥量获取函数的 uninterruptible 和 interruptible(例如 mutex_lock() 与 mutex_lock_interruptible(),以及用于信号量的 down() 和 down_interruptible())。interruptible 版本允许任务被信号中断,然后在获得锁之前唤醒处理它。uninterruptible 锁休眠的时间通常不会对平均负载增加太多,但在这种情况下,它们会增加 0.30。如果这要高得多,那么值得分析一下是否可以减少锁争用(例如,我将开始研究 systemd-journal 和 proc_pid_cmdline_read()!),这应该会提高性能并降低平均负载。

将这些代码路径包含在平均负载中是否有意义?是的,我会这么说。这些线程正在工作,并且碰巧被锁阻塞。他们没有闲着。它们是对系统的需求,尽管需要软件资源而不是硬件资源。

分解 Linux 平均负载
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
我还为不间断状态 (SVG)) 收集了一个 Off-CPU 火焰图:

 

 

 

最后一分钟的平均负载为 1.19。让我分解一下:

0.33 来自 tar 的 CPU 时间 (pidstat)

0.67是来自于tar的不间断磁盘读取,推断出来的(offcpu火焰图上的数值是0.69,我怀疑是由于它开始收集的时间稍晚,而且跨越的时间范围略有不同)。

0.04 来自其他 CPU 消费者(iostat 用户 + 系统,从 pidstat 减去 tar 的 CPU)

0.11 来自 kernel workers 不间断磁盘 I/O 时间,刷新磁盘写入(offcpu 火焰图,左侧的两个塔)

加起来是 1.15。我仍然缺少 0.04,其中一些可能是舍入和测量间隔偏移误差,但很多可能是由于负载平均值是指数加权移动平均,而我正在使用的其他平均值(pidstat,iostat)是正常平均值。在 1.19 之前,一分钟平均值为 1.25,因此其中一些仍将拖累我们走高。多少呢?从我之前的图表来看,在一分钟标记处,62% 的指标是从那一分钟开始的,其余的则更旧。所以 0.62 x 1.15 + 0.38 x 1.25 = 1.18。这与报告的 1.19 非常接近。

这是一个系统,其中一个线程(tar)加上更多线程(内核工作线程中的一些时间)正在工作,Linux 报告负载平均值为 1.19,这是有道理的。如果它正在测量“CPU 负载平均值”,系统将报告 0.37(从 mpstat 的摘要中推断),这仅适用于 CPU 资源,但隐藏了需要超过一个线程的工作价值的事实。

我希望这个例子表明这些数字确实意味着一些经过深思熟虑的事情(CPU + uninterruptible),你可以分解它们并弄清楚。

理解 Linux 平均负载
我是在平均负载意味着 CPU 平均负载的操作系统中长大的,所以 Linux 版本一直困扰着我。也许真正的问题一直是“平均负载”这个词和“I/O”一样模棱两可。哪种类型的 I/O?磁盘 I/O?文件系统 I/O?网络输入/输出? ...同样,哪个负载平均值? CPU平均负载?系统负载平均值?以这种方式澄清它让我这样理解它:

在 Linux 上,负载平均值是(或试图成为)“系统负载平均值”,对于整个系统来说,测量正在工作和等待工作(CPU、磁盘、uninterruptible 锁)的线程数。换句话说,它测量不完全空闲的线程数。优势:包括对不同资源的需求

在其他操作系统上,负载平均值是“CPU 负载平均值”,测量 CPU 正在运行的数量 + CPU 可运行线程数。优点:可以更容易理解和推理(仅适用于 CPU)。

请注意,还有另一种可能的类型:“物理资源负载平均值”,它仅包括物理资源的负载(CPU + 磁盘)。

也许有一天我们会为 Linux 添加额外的负载平均值,让用户选择他们想要使用的内容:单独的“CPU 负载平均值”、“磁盘负载平均值”、“网络负载平均值”等。或者只是使用不同的指标。

什么是“好”或“坏”的平均负载?
有些人发现似乎适用于他们的系统和工作负载的值:他们知道当负载超过 X 时,应用程序延迟很高并且客户开始抱怨。但这并没有真正的规则。

 

 

 

​对于 CPU 负载平均值,可以将该值除以 CPU 数量,然后说如果该比率超过 1.0,则您正在以饱和状态运行,这可能会导致性能问题。这有点模棱两可,因为它是一个可以隐藏变化的长期平均值(至少一分钟)。一个比率为 1.5 的系统可能运行良好,而另一个比率为 1.5 的系统在一分钟内突然爆发可能表现不佳。

我曾经管理过一个双 CPU 的电子邮件服务器,它在白天运行的 CPU 平均负载在 11 到 16 之间(比率在 5.5 到 8 之间)。延迟是可以接受的,没有人抱怨。这是一个极端的例子:大多数系统的负载/CPU 比率仅为 2。

至于 Linux 的系统负载平均值:这些更加模糊,因为它们涵盖了不同的资源类型,所以你不能只除以 CPU 数量。它对于相对比较更有用:如果您知道系统在负载为 20 时运行良好,而现在是 40,那么是时候深入研究其他指标以查看发生了什么。

更好的指标
当 Linux 平均负载增加时,您知道您对资源(CPU、磁盘和一些锁)有更高的需求,但您不确定是哪一个。您可以使用其他指标进行说明。例如,对于 CPU:

每个 CPU 的利用率:例如,使用 mpstat -P ALL 1 。

每个进程的 CPU 利用率:例如,top、pidstat 1 等。

每线程运行队列(调度程序)延迟:例如,在 /proc/PID/schedstats、delaystats、perf sched 。

CPU 运行队列延迟:例如,在 /proc/schedstat、perf sched、我的 runqlat bcc 工具。

CPU 运行队列长度:例如,使用 vmstat 1 和 'r' 列,或者我的 runqlen bcc 工具。

前两个是利用率指标,后三个是饱和度指标。利用率指标可用于工作负载表征,而饱和指标可用于识别性能问题。最佳 CPU 饱和度指标是运行队列(或调度程序)延迟的度量:任务/线程处于可运行状态但必须等待轮到它的时间。这些允许您计算性能问题的严重程度,例如,线程花费在调度程序延迟上的时间百分比。相反,测量运行队列长度可能表明存在问题,但更难以估计量级。

在Linux 4.6中,schedstats设施被列为内核可调控项(sysctl kernel.sched_schedstats),并被改为默认关闭。延迟核算暴露了相同的调度器延迟度量,它在cpustat中,我只是建议把它也添加到 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 或磁盘 I/O。对于这些策略,在扩大规模上犯错(花钱)比不扩大规模(花钱的客户)更安全,所以包括更多的信号是可取的。如果我们扩大规模太多,我们第二天就会调试出原因。

我一直在使用负载平均数的一件事是它们的历史信息。如果我被要求在云端检查一个表现不佳的实例,然后登录后发现一分钟的平均数比十五分钟的平均数低得多,这就是一个很大的线索,我可能来不及看到现场的性能问题。但我只花了几秒钟考虑负载平均数,然后就转向其他指标。

结论
1993 年,一位 Linux 工程师发现了一个不直观的负载平均值案例,并通过三行补丁将它们从“CPU 负载平均值”永久更改为人们所谓的“系统负载平均值”。他的更改包括处于 uninterruptible 状态的任务,因此负载平均值反映了对磁盘资源的需求,而不仅仅是 CPU。这些系统负载平均值计算工作和等待工作的线程数,并总结为指数加权移动平均的三元组,在方程中使用 1、5 和 15 分钟作为常数。这三组数字可以让您查看负载是增加还是减少,他们最大的价值可能是与自己的相对比较。

此后,不间断状态的使用在 Linux 内核中得到了发展,现在包括 uninterruptible 的锁原语。如果平均负载是对运行和等待线程(而不是严格意义上的需要硬件资源的线程)的需求量度,那么它们仍然按照我们希望的方式工作。

在这篇文章中,我挖掘了 1993 年的 Linux 平均负载补丁(出奇地难找到),其中包含作者的原始解释。我还在现代 Linux 系统上使用 bcc/eBPF 测量了不间断状态下的堆栈跟踪和时间,并将这次可视化为 off-CPU 火焰图。这种可视化提供了许多不间断睡眠的示例,并且可以在需要解释异常高负载平均值时生成。我还提出了可以用来更详细地了解系统负载的其他指标,而不是平均负载。

最后,我将引用调度程序维护者 Peter Zijlstra 在 Linux 源代码中 kernel/sched/loadavg.c 顶部的评论:

该文件包含计算全局 loadavg 数字所需的魔法位。 这是一个愚蠢的数字,但人们认为它很重要。 我们煞费苦心地让它在大型机器和无滴答内核上工作。

剖析
指数加权移动平均
什么是指数加权移动平均?
指数加权移动平均,又称做指数加权平均。

 

:替代 的估计值,也就是第 t 次采样的指数加权平均值

:第 t 次采样的值

: 的权重,是可调参数。

指数加权平均的作用是什么?
指数加权平均作为原数据的估计值,主要作用是:

抚平短期波动,起到了平滑的作用

还能够将长线趋势或周期趋势显现出来

计算平均值时不需要保存以往的数据

列如:我们有这样一组气温数据,图中横轴为一年中的第几天,纵轴为气温:

 

 

 

​直接看上面的数据图会发现噪音很多,这时,我们可以用指数加权平均来提取这组数据的趋势。

按照前面的公式计算,设 β = 0.9:

 

 

 

​计算后得到的值用红色线表示:

 

 

 

​可以看出,红色线比蓝色的原始数据更加光滑,少了很多噪声,并且可以表征原始数据的趋势。

根据式子:

 

 

 

​展开得到:

 

 

 

​这里可以看出:

是对每天温度的加权平均,之所以称之为指数加权,是因为加权系数是随着时间以指数形式递减的,时间越靠近,权重越大。

 

 

 

​再来看下面三种情况:

当 β = 0.9 时,指数加权平均最后的结果如图红色线所示,代表的是最近 10 天的平均温度值;

当 β = 0.98 时,指结果如图绿色线所示,代表的是最近 50 天的平均温度值;

当 β = 0.5 时,结果如下图黄色线所示,代表的是最近 2 天的平均温度值;

 

 

 

 

β 越小,噪音越多,虽然能够很快的适应温度的变化,但是更容易出现奇异值。

β 越大,得到的曲线越平坦,因为多平均了几天的温度,这个曲线的波动更小。但有个缺点是,因为只有 0.02 的权重给了当天的值,而之前的数值权重占了 0.98 ,曲线进一步右移,在温度变化时就会适应地更缓慢一些,会出现一定延迟。

通过上面的内容可知,β 也是一个很重要的超参数,不同的值有不同的效果,需要调节来达到最佳效果,一般 0.9 的效果就很好。

参考文章:

为什么在优化算法中使用指数加权平均

优化算法之指数加权平均详解

指数加权移动平均法(EWMA)

https://www.deeplearning.ai/

Linux 计算平均负载的代码怎么实现?

 

 


如上文所述,linux kernel 会分别计算最近1分钟,5分钟,15分钟的平均负载。

因为 linux kernel 每 5s 采样一次数据,所以最近 1 分钟的平均负载是最近 12 次的采样平均值,最近 5 分钟的平均负载是最近 60 次的采样平均值,最近 15 分钟的平均负载是最近 180 次的采样平均值。

1. 根据公式:

 

 

 

和示例:

 

 

 

还需要确定 的值,才能计算平均负载。

2. 根据公式 :

 

 

 

 

 

 

可得:

 

 

 

平均负载 N
最近 1 分钟 12 0.920044415
最近 5 分钟 60 0.983471454
最近 15分钟 180 0.994459848
可得最近1分钟的平均负载公式:

 

最近5分钟和15分钟的平均负载公式以此类推。

由于 linux kernel 不支持浮点数计算,因此需要把浮点数计算转换成定点数计算。linux 把这些浮点数乘以 来保留精度,此处使用二进制的好处是只需要移位即可。得到:

 


这就是下面 linux 代码中宏 EXP_1,EXP_5,EXP_15 的由来,LOAD_FREQ 代表每 5s 采样一次数据。

// include/linux/sched/loadavg.h
/*
* These are the constant used to fake the fixed-point load-average
* counting. Some notes:
* - 11 bit fractions expand to 22 bits by the multiplies: this gives
* a load-average precision of 10 bits integer + 11 bits fractional
* - if you want to count load-averages more often, you need more
* precision, or rounding will get you. With 2-second counting freq,
* the EXP_n values would be 1981, 2034 and 2043 if still using only
* 11 bit fractions.
*/
extern unsigned long avenrun[]; /* Load averages */
extern void get_avenrun(unsigned long *loads, unsigned long offset, int shift);

#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */
#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) */

#define LOAD_INT(x) ((x) >> FSHIFT)
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)

Linux 内核计算平均负载的代码实现主要有 3 部分组成:

1. 每个 CPU 每 5s 报告一次当前处于 runnable 和 uninterruptible 状态的进程数量,并记录到变量 calc_load_tasks。

相关代码:

void calc_global_load_tick(struct rq *this_rq)
2. 专门有一个由 tick_do_timer_cpu 指定的 CPU 计算平均负载,计算平均负载的时间戳至少是在 CPU 报告进程数量后的 10 个 ticks,并将结果记录到变量 avenrun。

相关代码:

void calc_global_load(void)

 

 


3. 从第 1,2 点可以看出 linux kernel 通过 tick 采样和计算平均负载。考虑到支持 NO_HZ 的系统,在CPU 进入 idle 后, tick 将被关闭,这样会导致一部分采样数据丢失,如下图所示。因此当 CPU 退出 idle 后,需要把这部分未采样和计算的数据找补回来。

相关代码:

void calc_load_nohz_start(void)
void calc_load_nohz_stop(void)
void calc_global_nohz(void)

 

 


calc_global_load_tick:

每个 CPU 每 5s 报告一次当前处于 runnable 和 uninterruptible 状态的进程数量,并记录到变量 calc_load_tasks。

void calc_global_load_tick(struct rq *this_rq)
{
long delta;

if (time_before(jiffies, this_rq->calc_load_update))
return;

delta = calc_load_fold_active(this_rq, 0);
if (delta)
atomic_long_add(delta, &calc_load_tasks);

this_rq->calc_load_update += LOAD_FREQ;
}
LOAD_FREQ :即 (5*HZ+1),定义每 5s 采样一次数据。
this_rq->calc_load_update :记录当前 CPU 下次报告进程数量的最早时间戳。
this_rq->calc_load_active:记录当前 CPU 中, 状态为 runnable 和 uninterruptible 的进程数量。
calc_load_tasks :记录当前系统中状态为 runnable 和 uninterruptible 的进程数量。
calc_global_load:

专门有一个由 tick_do_timer_cpu 指定的 CPU 计算平均负载,计算平均负载的时间至少是在 CPU 报告进程数量后的 10 个 ticks ,并将结果记录到变量 avenrun。

// kernel/sched/core.c
void __init sched_init(void) {
...
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
rq->calc_load_active = 0;
rq->calc_load_update = jiffies + LOAD_FREQ;
}
calc_load_update = jiffies + LOAD_FREQ;
...
}
calc_load_update :和 this_rq->calc_load_update 不同,这个变量记录系统下次计算平均负载的时间戳。
// kernel/sched/loadavg.c
/*
gic_handle irq
->__handle_domain_irq
-->handle_percpu_devid_1rq
--->arch_timer_handler_phys
---->tick_handle_periodic
*/

void tick_handle_periodic(struct clock_event_device *dev) {
tick_periodic(cpu) {
if (tick_do_timer_cpu == cpu) {
do_timer(1/*ticks*/) {
iffies_64 += ticks;
calc_global_load();
}
}
update_process_times(user_mode(get_irq_regs())/*int user_tick*/) {
}
}

void calc_global_load() {
// alc_load - update the avenrun load estimates 10 ticks after the CPUs have updated calc_load_tasks.
// 在 CPU 更新 calc_load_tasks 后 10 个 ticks 更新 avenrun 负载估计
sample_window = READ_ONCE(calc_load_update);
if (time_before(jiffies, sample_window + 10))
return;

delta = calc_load_nohz_read()
if (delta)
atomic_long_add(delta, &calc_load_tasks);

active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0

avenrun[0] = calc_load(avenrun[0]/*load*/, EXP_1/*exp*/, active) {
// include/linux/sched/loadavg.h
// a1 = a0 * e + a * (1 - e)
unsigned long newload;

newload = load * exp + active * (FIXED_1 - exp);
//如果新增active比之前的完整还多,则newload + 1,体现趋势
if (active >= load)
newload += FIXED_1-1;

return newload / FIXED_1;
}
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);

WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);

// In case we went to NO_HZ for multiple LOAD_FREQ intervals catch up in bulk.
calc_global_nohz();
}

函数 calc_load(unsigned long load, unsigned long exp, unsigned long active) 是计算平均负载的核心,它的实现即上文所介绍的计算公式。

calc_load_nohz_start,calc_load_nohz_stop 以及 calc_global_nohz:

从第 1,2 点可以看出 linux kernel 通过 tick 采样和计算平均负载。考虑到支持 NO_HZ 的系统,在CPU 进入 idle 后,tick 将被关闭,这样会导致一部分采样数据丢失,如下图所示。因此当 CPU 退出 idle 后,需要把这部分未采样和计算的数据找补回。

CPU 进入 NO_HZ 时:

通过函数 calc_load_fold_active 将当前处于 runnable 和 uninterruptible 状态的进程数量记录到 calc_load_nohz[idx]。

void calc_load_nohz_start(void)
{
/*
* We're going into NO_HZ mode, if there's any pending delta, fold it
* into the pending NO_HZ delta.
*/
calc_load_nohz_fold(this_rq());
}

static void calc_load_nohz_fold(struct rq *rq)
{
long delta;

delta = calc_load_fold_active(rq, 0);
if (delta) {
int idx = calc_load_write_idx() {
int idx = calc_load_idx;

/*
* See calc_global_nohz(), if we observe the new index, we also
* need to observe the new update time.
*/
smp_rmb();

/*
* If the folding window started, make sure we start writing in the
* next NO_HZ-delta.
*/
if (!time_before(jiffies, READ_ONCE(calc_load_update)))
idx++;

return idx & 1;
}

atomic_long_add(delta, &calc_load_nohz[idx]);
}
}
CPU 退出 NO_HZ 时:

如果当前时间戳 jiffies 还未到达 CPU 报告时间戳 this_rq->calc_load_update 时,不做任何处理。
如果已经到达或者过了 CPU 报告时间戳 this_rq->calc_load_update 时, 那需要更新当前 CPU 下次报告时间戳 this_rq->calc_load_update,这是因为原本负责更新这个值的函数 calc_global_load_tick 已经错过了。
void calc_load_nohz_stop(void)
{
struct rq *this_rq = this_rq();

/*
* If we're still before the pending sample window, we're done.
*/
this_rq->calc_load_update = READ_ONCE(calc_load_update);
if (time_before(jiffies, this_rq->calc_load_update))
return;

/*
* We woke inside or after the sample window, this means we're already
* accounted through the nohz accounting, so skip the entire deal and
* sync up for the next window.
*/
if (time_before(jiffies, this_rq->calc_load_update + 10))
this_rq->calc_load_update += LOAD_FREQ;
}
接下来到计算平均负载的时间戳 calc_load_update 时,call void calc_global_load(void) :

void calc_global_load(void)
{
unsigned long sample_window;
long active, delta;

sample_window = READ_ONCE(calc_load_update);
if (time_before(jiffies, sample_window + 10))
return;

/*
* Fold the 'old' NO_HZ-delta to include all NO_HZ CPUs.
*/
delta = calc_load_nohz_read();
if (delta)
atomic_long_add(delta, &calc_load_tasks);

active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;

avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);

WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);

/*
* In case we went to NO_HZ for multiple LOAD_FREQ intervals
* catch up in bulk.
*/
calc_global_nohz();
}

static long calc_load_nohz_read(void)
{
int idx = calc_load_read_idx();
long delta = 0;

if (atomic_long_read(&calc_load_nohz[idx]))
delta = atomic_long_xchg(&calc_load_nohz[idx], 0);

return delta;
}
当前时间戳如果未到达计算平均负载时间戳 (calc_load_update + 10)时则跳过。
如果 CPU 因为 idle 而错过报告状态为 runnable 和 uninterruptible 的进程数量时,即 this_rq->calc_load_update 时间戳出现在 CPU idle 期间,导致 CPU 无法通过 calc_global_load_tick 报告进程的数量。那可以通过函数 calc_load_nohz_read 获取当时 runnable 和 uninterruptible 的进程数量(CPU 进入 idle 后,这两种状态的进程数量将保持不变。如果 CPU 未 idle,则 calc_load_nohz_read 返回 0。
接着 call 函数 calc_global_nohz:

如果当前时间戳已经超过计算平均负载时间戳 (calc_load_update + 10)时,则说在 CPU idle 期间,至少有一次或者多次需要计算平均负载的时间戳,因此这里需要把这些错过的采样点补回来,代码如下所示:

static void calc_global_nohz(void)
{
unsigned long sample_window;
long delta, active, n;

sample_window = READ_ONCE(calc_load_update);
if (!time_before(jiffies, sample_window + 10)) {
/*
* Catch-up, fold however many we are behind still
*/
delta = jiffies - sample_window - 10;
n = 1 + (delta / LOAD_FREQ);

active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;

avenrun[0] = calc_load_n(avenrun[0], EXP_1, active, n);
avenrun[1] = calc_load_n(avenrun[1], EXP_5, active, n);
avenrun[2] = calc_load_n(avenrun[2], EXP_15, active, n);

WRITE_ONCE(calc_load_update, sample_window + n * LOAD_FREQ);
}

/*
* Flip the NO_HZ index...
*
* Make sure we first write the new time then flip the index, so that
* calc_load_write_idx() will see the new time when it reads the new
* index, this avoids a double flip messing things up.
*/
smp_wmb();
calc_load_idx++;
}

这种情况出现时 CPU Idle 期间,这个期间每个采样点的 runnable 和 uninterruptible 的进程数量没有变化,所以 calc_load_n 简化计算方式。

/*
* a1 = a0 * e + a * (1 - e)
*
* a2 = a1 * e + a * (1 - e)
* = (a0 * e + a * (1 - e)) * e + a * (1 - e)
* = a0 * e^2 + a * (1 - e) * (1 + e)
*
* a3 = a2 * e + a * (1 - e)
* = (a0 * e^2 + a * (1 - e) * (1 + e)) * e + a * (1 - e)
* = a0 * e^3 + a * (1 - e) * (1 + e + e^2)
*
* ...
*
* an = a0 * e^n + a * (1 - e) * (1 + e + ... + e^n-1) [1]
* = a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e)
* = a0 * e^n + a * (1 - e^n)
*
* [1] application of the geometric series:
*
* n 1 - x^(n+1)
* S_n := \Sum x^i = -------------
* i=0 1 - x
*/
unsigned long
calc_load_n(unsigned long load, unsigned long exp,
unsigned long active, unsigned int n)
{
return calc_load(load, fixed_power_int(exp, FSHIFT, n), active);
}
分解 Linux 平均负载的背后逻辑是什么?
在原文里面介绍多个工具组合分解平均负载分布情况,这里我们解释一下这些数字背后的逻辑。

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
我还为不间断状态 (SVG)) 收集了一个 Off-CPU 火焰图:

 

 

 

最后一分钟的平均负载为 1.19。让我分解一下:

0.33 来自 tar 的 CPU 时间 (pidstat)

0.67是来自于tar的不间断磁盘读取,推断出来的(offcpu火焰图上的数值是0.69,我怀疑是由于它开始收集的时间稍晚,而且跨越的时间范围略有不同)。

0.04 来自其他 CPU 消费者(iostat 用户 + 系统,从 pidstat 减去 tar 的 CPU)

0.11 来自 kernel workers 不间断磁盘 I/O 时间,刷新磁盘写入(offcpu 火焰图,左侧的两个塔)

加起来是 1.15。我仍然缺少 0.04,其中一些可能是舍入和测量间隔偏移误差,但很多可能是由于负载平均值是指数加权移动平均,而我正在使用的其他平均值(pidstat,iostat)是正常平均值。在 1.19 之前,一分钟平均值为 1.25,因此其中一些仍将拖累我们走高。多少呢?从我之前的图表来看,在一分钟标记处,62% 的指标是从那一分钟开始的,其余的则更旧。所以 0.62 x 1.15 + 0.38 x 1.25 = 1.18。这与报告的 1.19 非常接近。

这是一个系统,其中一个线程(tar)加上更多线程(内核工作线程中的一些时间)正在工作,Linux 报告负载平均值为 1.19,这是有道理的。如果它正在测量“CPU 负载平均值”,系统将报告 0.37(从 mpstat 的摘要中推断),这仅适用于 CPU 资源,但隐藏了需要超过一个线程的工作价值的事实。

我希望这个例子表明这些数字确实意味着一些经过深思熟虑的事情(CPU + uninterruptible),你可以分解它们并弄清楚。

Linux 平均负载是一个数量的概念,即状态为 runnable 和 uninterruptible 的进程数量的指数加权平均。而文中作者通过 pidstat,iostat 和 perf cpu-off 这些时间概念的工具,把平均负载分解到各个应用程序不同的行为,看似牛马不相及关系,这样做的逻辑是:

1.19 :在最近 60s 内,linux kernel 每 5s 统计一次状态为 runnable 和 uninterruptible 的进程数量的指数加权平均值。
0.33 :最近 60s 内 ,tar 在 CPU 3 上运行时间比例 0.3262 的近似值。这个是 runnable 状态下的 tar 程序贡献的负载正常平均值(与指数加权平均值区分),虽然是时间上的概念,但可以理解为每 1ns 采样一次 runnable 进程的数量,一共采样 次,即 60 s,最后按照正常平均值计算的平均负载,这就很容易理解,这指标确实可以作为 linux 平均负载值的一部分。当然这只是 runnable 状态下的 tar 程序贡献的负载。
0.67:0.33 是 tar 程序处于 runnable 状态下的贡献的平均负载,那么因为 io 阻塞进程被切到 uninterruptible 状态贡献的负载呢?0.67(1 - 0.33),即 tar 程序在 runnable 和 uninterruptible 两种状态之间切换。但是通过 perf cpu-off 火焰图也可以得到这个数据,由于读操作阻塞 CPU,导致 CPU idle 的时间占比 0.69 (41.164/60)。

 

 


0.04:来自其他 CPU runnable 状态进程贡献的平均负载,(0.54%+4.03%)*8 - 32.62% = 0.04。
0.11 :来自 kernel workers 不间断磁盘 I/O 时间,刷新磁盘写入(offcpu 火焰图,左侧的两个塔),​​​​​​​(3.684+3.102)/60 = 0.11。即除了 tar 以外,其他所有 uninterruptible 进程贡献的负载。
最终总平均负载:0.33+0.67+0.04+0.11=1.15,和 linux 平均负载值 1.19 存在一定的差距。上文作者已经有解释,作者根据公式 ​​​​​​​0.62*1.15 + 0.38*1.25 = 1.18 进一步缩短差距。0.62 来源于下面的解释(感觉这里在凑数字,不太理解!!!)。

如果您使用一个空闲系统,然后开始一个单线程 CPU 密集型工作负载(一个循环中的一个线程),那么 60 秒后一分钟的平均负载是多少?如果它是一个普通的平均值,它将是 1.0。这是那个实验,图表:

 

 


负载平均实验以可视化指数阻尼
所谓的“一分钟平均值”,到一分钟大关才达到0.62左右。有关方程和类似实验的更多信息,Neil Gunther 博士写了一篇关于平均负载的文章:How It Works, 此外 loadavg.c 中有许多 Linux 源代码块注释。
————————————————
版权声明:本文为CSDN博主「zs.w」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cs_tech/article/details/126563993

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章