Linux Load Average: Solving the Mystery

Hotlink發表於2022-04-29

本篇部落格翻譯自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。但實際實驗結果如下圖所示:

Load Average

被稱作“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,這一分鐘的效能表現可能就會很糟糕。

Load averages measured in a modern tool

我曾經管理過一臺雙核郵件伺服器,平常執行時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.

相關文章