深入理解load averages

大蟒傳奇發表於2018-06-07

本文翻譯自 linux-load-averages

深入理解load averages

前言

經常和Linux打交道的童鞋都知道,load averages是衡量機器負載的關鍵指標,但是這個指標是怎樣定義出來的呢?

和其他系統不同,Linux上的load averages不僅追蹤可執行的任務,還追蹤處於不可中斷睡眠狀態的任務,為什麼是這樣呢?這篇文章就來聊聊這方面的知識。

Linux的load averages是系統負載平均值,這個值將正在執行執行緒(任務)對於系統的需求,作為處於執行和等待狀態的執行緒的平均數量。大多數工具會顯示1分鐘,5分鐘和15分鐘的平均值:

$ uptime
 17:30:01 up 13 days, 20:30,  3 users,  load average: 1.66, 2.03, 2.08
 
$ cat /proc/loadavg
1.48 1.98 2.06 4/3587 117385
複製程式碼

對上面的輸出資訊稍稍做些解釋

  • 如果平均值是0.0,說明系統處於空閒狀態
  • 如果1分鐘的平均值大於5分鐘或者15分鐘,說明系統負載正在增加
  • 如果1分鐘的平均值小於5分鐘或者15分鐘,說明系統負載正在減小
  • 如果這些值大於CPU的核數,說明可能遇到了效能問題

利用這三個值,我們可以判斷系統的負載是在增加還是在減小,這在實踐中很有用。這三個中的任意一個拿出來也很有用,比如為雲服務的自動伸縮設定閾值。不過,在缺少其他資訊的情況下,單看這些值是沒有意義的。比如1分鐘的load averages值在23到25之間,就沒有任何意義;但如果知道CPU核數並且知道執行的任務是計算密集型,那這個值就很有意義。

歷史

最開始的時候,load averages只顯示對系統CPU相關的需求:執行的程式數加上等待的程式數。如RFC 546描述的

TENEX load averages是衡量CPU需求的指標。這個值是給定時間內可執行程式數量的平均值。例如,對於單核CPU系統,每小時平均10次意思是在該小時內可以期望看到一個程式正在執行和另外九個等待CPU(即沒有被I/O阻塞)處於ready狀態的程式。

下圖是1973年繪製的監控圖

image

以前作業系統的程式碼還可以找到,下面是TENEX定義的一些巨集

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) */
複製程式碼

指標的三個粒度

load averages有1分鐘,5分鐘,15分鐘三個粒度的結果。不過事實上,他們並不是真正的平均值,統計的粒度也不是1,5,15分鐘。從上面的程式碼中可以看出,1,5和15都是常量,用於計算指數衰減的5秒平均移動和。由此算出的1分鐘,5分鐘和15分鐘的load averages所反應的負載遠遠超過1,5,15分鐘。

假設在一個空閒的系統上,開啟一個單執行緒來跑CPU密集任務,60秒後的load averages是多少呢?如果load averages按普通平均值來算,這個值將是1.0。下面是一個繪製成圖的實驗結果

image

在上面的實驗中,所謂的“1分鐘load averages”在一分鐘內只能達到0.62左右。

Linux不可中斷任務

Linux中剛引入load averages時,和其他系統一樣將其作為衡量CPU需求的指標,後來將其更改為不僅包含可執行任務,還包含處於不可中斷狀態的任務(TASK_UNINTERRUPTIBLE或nr_uninterruptible)。這種狀態由希望避免訊號中斷的程式碼使用,其中包括阻塞在磁碟I/O和一些鎖上的任務。在pstop的輸出中,這種狀態被標誌為“D”。ps(1)的man page將其稱為"不可中斷睡眠狀態(通常被IO阻塞)"

# man ps
.....
PROCESS STATE CODES
       Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to describe the
       state of a process:

               D    uninterruptible sleep (usually IO)

...
複製程式碼

為什麼Linux中的load averages要加入不可中斷狀態,而不是像其他系統一樣只計算CPU的需求呢?

加入不可中斷的起源

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
複製程式碼

看到這麼久之前的想法還是很令人驚歎的。

這也證明了Linux改變load averages的含義,使其不僅體現對CPU的需要,是有意的,這讓load averages從“CPU負載均衡”變成了“系統負載均衡”。

郵件中舉交換磁碟速度慢的例子是有道理的:通過降低系統效能,系統需求(執行和排隊的程式數)應該增加;但是如果僅僅根據CPU執行狀態,那麼load averages值應該會下降。Matthias認為這是不直觀的,所以修改了程式碼。

現代系統的不可中斷

但是難道不會出現磁碟I/O不能解釋Linux load averages過高的情況嗎?這種情況是會出現的,這是因為在現代Linux(4.12)版本中,有將近400處程式碼設定了TASK_UNINTERRUPTIBLE狀態,包括一些鎖原語中。其中部分程式碼可能不需要統計在load averages中。

既然TASK_UNINTERRUPTIBLE在更多的地方被用到,那麼是否應該將load averages改成只統計CPU和磁碟需求呢?Linux排程程式的維護者Peter Zijstra有一個想法:將TASK_UNINTERRUPTIBLE替換成task_struct->in_iowait,這樣load averages就更貼近磁碟I/O的需求。這樣又引入了另外一個問題,我們到底想要從load averages中得到什麼?我們是需要用執行緒對系統的需求來衡量負載,還是隻通過物理資源的使用情況來衡量負載呢?如果是前者的話,那麼應該包含等待不間斷鎖的執行緒,因為這些執行緒並沒有閒置。所以也許Linux的load averages已經按我們需要的方式工作了。

理解Linux的load averages

也許真正的問題在於“load averages”這個詞和“I/O”一樣含糊不清。到底是哪種I/O呢?是磁碟I/O?檔案系統I/O?還是網路I/O。類似的,到底是哪種load averages呢?是CPU平均負載?還是系統平均負載?下面做一個總結吧:

  • 在Linux上,load averages的真實含義是“系統平均負載”,即對整個系統,測量正在工作並等待工作的執行緒數(CPU,磁碟,不可中斷鎖)。換句話說,這種方式測量的是不完全空閒的執行緒數量。這種方式的優勢在於包括了對不同資源的需求
  • 在其他的系統上,load averages的含義是“CPU平均負載”,這組值用於測量正在佔有CPU執行權的執行緒數量加上等待CPU的執行緒數量。

還有另一種可能的型別:“物理資源負載平均值”,其中包括僅用於物理資源(CPU+磁碟)的負載。

更精確的測量資料

當Linux的load averages值增加時,可以判斷任務對系統資源(CPU,磁碟和鎖)有了更高的需求,但是到底是對哪種資源的需求增長了呢?這時可以用其他的指標來進行判斷。比如,CPU資源有如下指標:

  • 單個CPU使用率:可以用命令mpstat -P ALL 1檢視
  • 每個程式的CPU使用率:可用命令toppidstat 1檢視
  • 每個執行緒執行佇列(排程程式)延遲:可用命令perf sched檢視,也可以檢視檔案/proc/PID/schedstats
  • CPU執行佇列延遲:可用命令perf sched檢視,也可以檢視檔案/proc/schedstat
  • CPU執行佇列長度:可用vmstat 1命令檢視。

上面提供的指標中,前兩個用來衡量使用率,後三個用來度量系統飽和度。利用率指標對於衡量工作負載很有用,而飽和度指標可用來識別效能問題。衡量CPU飽和度的最佳指標是執行佇列(或排程程式)的延遲,延遲是指任務或者執行緒處於可執行狀態,但必須等待CPU的時間。通過這樣的指標可以用來衡量效能問題的嚴重程度,比如執行緒等待排程的時間在執行時間中佔的百分比。通過觀察執行佇列長度可以很方便判斷是否存在問題,但比較難定位到問題產生的原因。

schedstats功能在Linux 4.6中成為核心可調引數(sysctl.kernel.sched_schedstats),預設是關閉的。

儘管有更明確的指標,但並不意味著load averages是無用的。這組指標已經成功用於雲端計算微服務的擴充套件策略,微服務根據不同的負載值做出反應。有了這些判斷的依據,即使在自動擴容時犯錯也保險多了:擴容例項會花更多的錢,不擴容則會損失使用者。如果擴容太多,後來調查一下糾正就是了。

總結

在1993年,一位Linux工程師發現了一個非直觀的load averages情況,於是提交了三行程式碼的補丁將load averages的含義由“CPU負載平均值”變成了“系統負載平均值”。這次的變動在統計中包括了不可中斷狀態下的任務,所以load averages值不僅反映了對CPU的需求,還反映了對磁碟資源的需求。系統平均負載計算正在工作和等待工作的執行緒的數量,並且統計1分鐘,5分鐘,15分鐘指數衰減的移動總和平均值。通過這三個值,能夠知道系統的負載是在增加還是在減小。

Linux中對不可中斷狀態的使用越來越多,現在已經包括了不可中斷的鎖原語。如果需要衡量處於執行狀態和等待狀態的執行緒對於系統的需求,那麼load averages依然是很好的指標。

最後引用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.