本文主要依据BrendanGregg前辈的一篇博客改编而至,原文链接如下。

导读

本文主要是对平均负载(loadaverage)的涵义进行分析,并对linux上的实现做了一些考证工作。loadavg的wikipedia解释为:系统须要进行处理的工作量,抽象则可以叙述为系统上正在运行(running)和早已ready且等待调度(runnable)的进程数之和。有经验的读者肯定发觉,该描述并不符合linux的实现,linux还包含了处于不可中断睡眠(uninterruptiblesleep,即我们平时所说的D状态)进程。这么,Linux的loadavg为什么包含不可中断睡眠进程?这样统计会带来什么影响?本文尝试来解答这个问题。

正文

loadavg是业界常用的一个关键指标,其用途广泛,但我们真的完全理解它(尤其是在Linux系统中)么?linuxloadavg是systemloadavg(可暂时理解为loagavg的一种特化子类型,具体缘由后文会解释),它表示系统上正在以及等待运行的线程对资源的需求。统计平均负载的工具通常还会有三个值,分别表征过去1/5/15分钟内的平均负载:

$ uptime16:48:24 up 4:11, 1 user, load average: 25.25, 23.40, 23.46top - 16:48:42 up 4:12, 1 user, load average: 25.25, 23.14, 23.37$ cat /proc/loadavg25.72 23.19 23.35 42/3411 43603

一些简单的使用规则:

通过上述规则,我们可以评估一个系统的负载是在增减或则降低。但有时侯仅凭负载值是难以得到具体的推论,例如我们说一个系统的负载为23-25,在不确定CPU数量时,我们难以判定系统的负载是否合理。

历史

loadavg最早定义为对cpu资源的需求:系统上正在运行或等待运行的进程数。RDC546页脚里有如下一段话:“TENEX平均负载是对CPU需求的测度,是在给定时间段内系统上可运行的进程数平均值。举个事例,单cpu系统,某一小时的平均负载为10,则表示在该小时内的任意时刻,我们将期望听到有1个进程在运行,而另外有9个进程在等待运行(没有被I/O阻塞)”。下边的代码是loadavg的实现,源码摘至SCHED.MAC文件:

NRJAVS==3 ;NUMBER OF LOAD AVERAGES WE MAINTAINGS RJAV,NRJAVS ;EXPONENTIAL AVERAGES OF NUMBER OF ACTIVE PROCESSES[...];UPDATE RUNNABLE JOB AVERAGESDORJAV: MOVEI 2,^D5000MOVEM 2,RJATIM ;SET TIME OF NEXT UPDATEMOVE 4,RJTSUM ;CURRENT INTEGRAL OF NBPROC+NGPROCSUBM 4,RJAVS1 ;DIFFERENCE FROM LAST UPDATEEXCH 4,RJAVS1FSC 4,233 ;FLOAT ITFDVR 4,[5000.0] ;AVERAGE OVER LAST 5000 MS[...];TABLE OF EXP(-T/C) FOR T = 5 SEC.EXPFF: EXP 0.920043902 ;C = 1 MINEXP 0.983471344 ;C = 5 MINEXP 0.994459811 ;C = 15 MIN

loadavg在linux实现参考

#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) */

可以看见,都是硬编码为1/5/15分钟的统计。在一些较老的其他系统中,我们也能见到类似的实现,例如Multics。

三个数字

三个数字是指1,5,15分钟的平均负载,这不是我们常用的算术平均,也不仅仅是指近来的1,5,15分钟时间段内的统计。里面的源码是每5秒分别更新一次1,5,15分钟的指数衰减滑动平均。因为是指数衰减,所以统计值对应的时间段似乎不止近来的1,5,15分钟。假定存在一个初始时完全空闲的系统,之后开始运行一个单线程的CPU-bound型的工作负载,这么在60秒后,1分钟的平均负载应当是多少呢?假定采用算术平均,其实平均负载等于1,而指数衰减滑动平均则如右图所示:

可以看见,1分钟平均负载在程序持续运行1分钟后仅为0.62。更多的细节可参考Dr.NeilGunther的”HowItWorks“以及linux源码kernel/sched/loadavg.c中的注释。

Linux不可中断任务

与其他系统一样,平均负载一开始在linux系统上实现时,表征的也是CPU资源的测度。但后来,linux上的统计算法发生了变化,不仅仅包含可运行的任务,还包含不可中断状态的进程(TASK_UNINTERRUPTIBLE或nr_uninterruptible)。进程步入不可中断睡眠状态后,不会被讯号中断,比如任务被I/O或则锁(例如内核讯号量)阻塞。读者可能早已遇到过这类任务:ps或则top命令输出的D状态进程。ps的man指南称其为“不可中断睡眠(通常是由于I/O)”。

内核信号量跟踪_linux内核信号量_linux内核信息

平均负载包含不可中断睡眠的进程,意味着linux的loadavg可能由于c盘(或NFS文件系统)I/O负载而降低。读者假如熟悉其他系统的cpuloadavg统计方法,再与linux来对比,可能会倍感诧异:为什么包含不可中断睡眠?目前已有特别多的文章来解释loadavg,其中大部份也能强调Linux包含了不可中断睡眠,并且无人给出解释甚至推测:为何会包含它呢?brendangregg觉得,linux的统计不仅仅是cpu维度的测度,而是多维度的统计,下边来详尽剖析。

Patch溯源

【译者注:本段文字主要记录了brendangregg的探求历程,假如有了解linux代码历史记录的需求,可以参考该技巧,假如只对结果感兴趣,请直接跳至下一段文字。】brendangregg朋友开始刨根究底,一开始期望通过linux的gitcommit历史,看能够发觉哪些蛛丝马迹。首先检测kernel/sched/loadavg.c的递交历史,发觉uninterruptiblestate比loadavg.c文件还要早出现,所以肯定是先在其他文件中出现。接着他又检测了另外一个文件,一无所获,线索又指向了更老的文件。brendangregg不得已采取了暴力破解法,通过对linux整个repo使用gitlog-p命令,输出了4G字节的文本文件,之后开始渐渐回溯…但是很不幸,这还是迈向了死南街。linuxrepo最早的记录是2005年,版本是2.6.12-rc2,而该版本已包含不可中断睡眠的统计。brendangregg没有舍弃,转而使用更老repo记录,但依然没有得到答案。brendangregg又寄希望于找到何时引入了该更改,通过直接对比LINUXv0.99上的tar包,发觉在在0.99.15中已有该项更改红旗 linux,而在0.99.13中还未出现,并且…0.99.14的tar包没能在没能找到。

庆幸,他在其他地方找到了这个tar包,可以确认的是该项更改于1993年11月,在0.99的patchlevel14中引入。遗憾的是,0.99.14的发布描述里,没有任何与此项更改相关的解释,又一个死西街:“Changestothelastofficialrelease(p13)aretoonumeroustomention(oreventoremember)…”–Linus。Linus在发布描述里只提及了主要更改,对loadavg这类小更改一概而过。通过版本的发布日期,brendangregg在内核电邮列表档案里找到了那种patch,然而这儿最老的短信是1995年6月份开始,系统管理员在上面写了一句话:”为了使这种电邮档案愈发高效地工作,我不留神捣毁了现有的档案(哎呀)”。brendangregg倍感了这个世界深深的恶意,但是他还是没有舍弃。他在一些备份服务器中发觉了一部份更老的内核电邮列表tar包。他搜查了六千多份摘要,共包含九万八千多封电子电邮,其中三万份是从一九九七年起的。虽然,linuxloadavg为什么包含不可中断睡眠,将成为永远的谜…

原始Patch

皇天不负有心人,brendangregg在上找到原始patch,如下:

From: Matthias Urlichs Subject: Load average broken ?Date: Fri, 29 Oct 1993 11:37:23 +0200The kernel only counts "runnable" processes when computing the load average.I don't like that; the problem is that processes which are swapping orwaiting on "fast", i.e. noninterruptible, I/O, also consume resources.It seems somewhat nonintuitive that the load average goes down when youreplace your fast swap disk with a slow swap disk...Anyway, the following patch seems to make the load average much moreconsistent WRT the subjective speed of the system. And, most important, theload 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.orgSchleiermacherstra_e 12  Unix+Linux+Mac | Phone: ...please use email.90491 N|rnberg (Germany)  Consulting+Networking+Programming+etc'ing 42

由此,我们可以确认linuxloadavg的更改是为了反映系统资源的需求量,而不仅仅是CPU资源。打上这个patch后,linux从“cpuloadavg”统计变为“systemloadavg”统计。递交记录中提及了一个低性能的swapc盘案例:因为性能增长,造成对系统资源的需求降低(运行+排队等待的进程),没有打上这个patch时,系统的平均负载统计将会变低,Matthias觉得这样不符合直观判定,所以修正了统计。

现在的Uninterruptible

Linux的平均负载有时显得十分高,并非完全与c盘I/O压力正相关,虽然还有未知诱因。brendangregg猜想这是由于在1993年改动后,新增了许多可步入不可中断睡眠的代码路径。在linux0.99.14中,仅存在13条代码路径会直接步入TASK_UNINTERRUPTIBLE或TASK_SWAPPING(该状态后来被删掉)状态。现在的Linux4.12版本,早已有超过400条代码路径会步入TASK_UNINTERRUPTIBLE,这其中包含一些锁子句。其实,这儿面的个别代码路径不应当被统计入负载。brendangregg给Matthias发短信咨询其对loadavg含意变化的想法,回复如下:“平均负载是从人的视角来评判量系统忙碌程度的数值。TASK_UNINTERRUPTIBLE意味着(或以前意味着)被阻塞(例如读c盘)的进程对系统负载的贡献。例如一个disk-bound型的系统,其TASK_RUNNING的平均值显然是0.1,该值毫无实际意义。”所以,Matthias觉得linux上的修正是有道理的,从电邮回复中也可以看出TASK_UNINTERRUPTIBLE以前是指进程被c盘I/O阻塞。但现在TASK_UNITERRUPTIBLE的含意丰富了许多。平均负载是否应只考虑CPU或则c盘资源的需求?调度器的maintainerPeterZijstra提及了一个看法:系统负载不统计TASK_UNINTERRUPTIBLE,而是统计task_struct->in_iowait。但这是我们真正想要的么?其实我们会想统计线程对系统资源的需求,而不仅仅是化学资源?假如是基于线程统计,这么这些步入不可中断睡眠的线程也消耗了系统资源,由于她们并非idle。所以,其实目前的linuxloadavg统计其实才是我们真正想要的。接出来brendangregg给出了几个反例。

不可中断任务的检测剖析

右图是一台生产环境服务器的Off-Cpu火焰图,统计持续60秒,仅显示TASK_UNINTERRUPTIBLE状态的内核栈。通过统计图可以得到不可中断睡眠的代码路径:

linux内核信号量_linux内核信息_内核信号量跟踪

x轴代表off-CPU的时间长短,从左至右没有任何排序,色调饱和度随机没有任何含意。图是通过bcc工具(依赖于kernel4.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>

brendangregg使用awk将时间精度从us调整为ms,’–state2’表示TASK_UNINTERRUPTIBLE(参考sched.h)。offcputime.py支持用户态与内核态栈,这儿为了说明仅显示用户态栈。上图60秒内有926ms步入了不可中断睡眠,所以仅影响平均负载0.015(926ms/60s)。该服务程序并没有太多的c盘I/O,主要是cgroup路径相关。

右图是另一案例,仅统计了10秒

linux内核信号量_内核信号量跟踪_linux内核信息

图片左边耸立的平坦区域是systemd-journal调用函数proc_pid_cmdline_read()(读取/proc/PID/cmdline),对平均负载贡献了0.07。在右边的更为宽敞的平坦区域是阻塞在rwsem_down_read_failed()函数(对平均负载贡献了0.23)。下边的一段代码摘自rwsem_down_read_failed()函数:

内核信号量跟踪_linux内核信号量_linux内核信息

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

可以看见,lock路径使用了TASK_UNINTERRUPTIBLE。linux的锁子句通常支持可中断与不可中断两类API(如互斥锁mutex_lock()vsmutex_lock_interruptible(),讯号量down()vsdown_interruptible())。可中断版本可被signal打断并被唤起执行。通常而言,由于lock失败而步入不可中断睡眠不会对平均负载导致很大的影响,但本案例则影响了0.30。假如该值更高,则须要剖析锁竞争是否合理以及能够优化。

里面俩案例中的不可中断睡眠路径是否应当被统计入平均负载呢?brendangregg觉得应当被统计,理由如下:线程在处理任务的过程中,获取锁失败被阻塞,并非步入idle,这一直会消耗系统资源(主要指软件资源linux内核信号量,而不是硬件资源)。

linux平均负载分析

brendangregg尝试对linux平均负载按类型进行细分统计。他在一个8核空闲CPU的系统上使用tar打包未被pagecache缓存的文件,被c盘读阻塞了几分钟。下边是一些统计输出:

terma$ pidstat -p `pgrep -x tar` 60Linux 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 Command10:16:51 PM 0 18468 2.85 29.77 0.00 32.62 3 tartermb$ iostat -x 60[...]avg-cpu: %user %nice %system %iowait %steal %idle0.54 0.00 4.03 8.24 0.09 87.10Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilxvdap1 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.21xvdb 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.47xvdc 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.65md0 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.00termc$ uptime22:15:50 up 154 days, 23:20, 5 users, load average: 1.25, 1.19, 1.05[...]termc$ uptime22:17:14 up 154 days, 23:21, 5 users, load average: 1.19, 1.17, 1.06

Off-CPU火焰图

linux内核信息_linux内核信号量_内核信号量跟踪

一分钟loadavg=1.19,分拆如下:

linux内核信号量_linux内核信息_内核信号量跟踪

上述之和等于1.15,距实际统计值1.19相差0.04。相差可能是统计偏差引入,但更可能是由于loadavg是指数衰减滑动平均,而前面估算用到的统计都是常规的算术平均。1.19的前一分钟统计值为1.25linux操作系统界面,结合前文提及的1分钟指数衰减统计,可这么估算:0.62x1.15+0.38x1.25=1.18,与实际统计值1.19特别接近。假如仅考虑cpuloadavg,结合mpstat的输出可推测出0.37。

重新解释linuxloadavg

brendangregg觉得平均负载应做如下解释:

其实之后就会有“物理资源平均负载”,包含CPU以及c盘资源,甚至“CPU平均负载”,“磁盘平均负载”,“网络平均负载”等等。

怎样评估负载是否合理

通常的经验是当系统负载超过一个特定值X,则觉得将影响业务延后,但事实上并非这么。仅考虑CPU负载,load/CPU数目假如小于1.0则可能存在性能问题。实际上这些判定方式存在缺陷linux内核信号量,由于负载是一个平均值,并不能反映瞬时状态。例如load/CPU等于1.5时,有些系统能正常工作,而有些则存在性能问题。brendangregg举了个反例,两台电邮服务器的CPU平均负载为11-16(load/CPU比值为5.5-8),负载较高,但用户完全能接受。所以他给出了一个推论:大部份系统在load/CPU大于2时,都还能正常运行。Linux的systemloadavg更复杂,由于它不仅仅包含CPU资源,所以不能直接乘以CPU数目。但我们可以用它来做横向对比:例如系统在负载为20时能正常工作,忽然弄成了40,这时可结合其他手段来剖析具体缘由。

其他量化手段

当linux系统负载降低,意味着对系统的资源消耗降低,但并没有强调是哪一种具体的资源(例如CPU,c盘,锁等)。可以通过其他的手段来辅助剖析确认,例如CPU资源:

前两个是借助率指标,可拿来辨识工作负载的特点;后三个是饱和度指标,可拿来进行性能剖析。不仅CPU量化,还可以对c盘I/O进行量化。须要说明的是,本节提及的各类量化手段与平均负载统计并不冲突。

结语

1993年,一位linux工程师发觉负载统计存在不符合直观之处,提了三行patch将“CPUloadavg”变成了“systemloadavg”,借以反映系统对CPU与c盘资源的需求,具体实现则是降低了对uninterruptible的统计。随着linux的发展,步入uninterruptible的路径越来越多,例如lock。本文中,brendangregg分析时使用bcc/eBPF采集数据并制做出Off-CPU火焰图,还提及了各类工具,这种我们都可以在实际剖析问题时加以借助。最后,本文以linuxkernel/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.

本文原创地址:https://www.linuxprobe.com/ldlwhbhbkzds.html编辑:刘遄,审核员:暂无