Linux 的資源控制監測 - PSI [下]

語言: CN / TW / HK

Linux 的資源控制監測 - PSI [上]

如何開啟

PSI 的功能是一個可選項,在核心配置中,設定 CONFIG_PSI=y ,則 PSI 相關的部分會被編譯進核心。但這只是表明系統具備了 PSI 的能力,由於預設配置通常為 CONFIG_PSI_DEFAULT_DISABLED=y ,所以實際並沒有開啟這項能力。

#ifdef CONFIG_PSI_DEFAULT_DISABLED
static bool psi_enable;
#else
static bool psi_enable = true;
#endif

static int __init setup_psi(char *str)
{
    return kstrtobool(str, ψ_enable) == 0;
}
__setup("psi=", setup_psi);

根據以上的程式碼不難看出(實現在 "kernel/sched/psi.c"),PSI 的 enable 被做成了一個核心啟動引數,如果藉助 grubby 的話,可以這樣使能:

grubby --args="psi=1" --update-kernel=/boot/vmlinuz-$(uname -r)

筆者在 5.4 核心上嘗試使用 PSI 時,發現無需傳入 "psi=1",也能在 procfs 中看到 PSI 的相關介面:

# ls /proc/pressure/
cpu  io  memory

但是對這些介面無論讀寫,都提示 "Operation not supported"。百思不得其解,網路搜尋也無果的情況下,最終通過閱讀原始碼,才發現原因是 5.4 版本的核心在 "psi_proc_init" 函式中沒有進行 "psi_enable" 的判斷,就直接建立了 "/proc" 下的這些檔案,導致可以看到而不能使用。

這裡 是筆者對該問題的回答,由此也可見開源的好處,雖然多花費些時間,但終究能在原始碼裡找到答案。此問題已在 5.6 版本的 這個 commit 裡修復了。

如何計量

還是以記憶體資源為例,為了模擬資源緊缺的情況,需要不斷地分配記憶體來觸發 throttlen,但這可能導致系統 hung 住,所以決定基於 cgroup 來進行實驗。啟動一個 stress-ng 的壓測容器,記憶體限制為 1G:

# docker run --memory 1G -it --rm alexeiled/stress-ng --brk 2 --stack 2 --bigheap 2

然後讀取容器對應 cgroup 路徑中的 "memory.pressure" 檔案:

"some" 說明(至少)有一部分任務因為等待記憶體資源而 stall 了(在 cgroup v2 中就是記憶體使用超過了 memory.high),而 "full" 則代表所有 non-idle 的任務都同時 stall 了。total 是總的 stall 時間,單位以「微秒」計。

圖片來源見文末第一個連結

而 10, 60, 300 分別表示在過去的 10 秒(短期),60 秒(中期),300 秒(長期)內的統計情況(跟 load average 選用 1 分鐘,5 分鐘,15 分鐘作為時間視窗類似)。如上圖所示,在 60 秒內,Task B 進入 stall 的時間是 30 秒,兩個 task 都 stall 的時間為 10 秒,所以 "some" 的結果是 50%,而 "full" 是 16.66%【注-1】。

接下來思考兩個問題。 一是設計上為什麼要做 some 和 full 的區分

試想一下,如果所有任務都因為 memory 或者 I/O 止步不前了,這時 CPU 的 cycle 是不是被白白地浪費了,而如果只有部分任務等待,還可以通過睡眠,將 CPU 資源讓渡給那些不會因 memory 或者 I/O 等待的任務,還有 running task,意味著系統還算是 productive 的。

所以如果出現了 full,將影響到整體任務執行的 throughput,應該特別的關注。那怎麼關注?比如你覺得 1 秒內出現 50 ms 的 full stall 就很嚴重了,可以通過填入 目標閾值 來進行 poll 監聽:

echo 'full 50000 1000000' > /proc/pressure/io

對應的程式碼實現大致是:

static const struct file_operations psi_cpu_fops = {
	...
	.poll = psi_fop_poll,
};
psi_fop_poll --> psi_trigger_poll

第二個問題 :這種 sliding tracking window 是如何對一段 滑動區間 內的數值進行測量和歸總的?

首先,需要就進入和退出 stall 狀態進行判斷,對於記憶體和 CPU,相關的函式如下:

memory CPU
psi_memstall_enter
psi_memstall_exit
enqueue_task --> psi_enqueue
dequeue_task --> psi_dequeue
try_to_wake_up --> psi_ttwu_dequeue

然後,需要就過往一段時間的 "some/full" 資訊進行儲存,比如要在 E 點計算過去 60 秒的 "some" 值,從圖上看是一目瞭然的,就是 A 到 B,以及 C 到 D 之間的時間,如果是在 F 點計算過去 60 秒呢?大家可以想想,如果由你來設計,你會怎樣實現。

注-1:

此處舉的是記憶體的例子,記憶體不足時所有任務都被迫等待是正常的("full" 的標準不包括正在進行記憶體回收的 reclaimer)。在 cgroup 的層面,因quota 耗盡導致 cgroup 中的所有任務都等待也是合理的,但在整個系統層面,不可能出現所有任務都等待 CPU 的情況(一個 CPU 都不可用),因此 system level 的 CPU 只應有 "some",而不應有 "full"。

但是自 5.13 核心之後,你會發現代表全域性的 "/proc/pressure/cpu" 也有 "full" 這一行(來自 這個 commit ), 並且裡面的 total 值還不為 0,著實讓人困惑,後經和 commit 作者團隊的交流,得知此問題已經被 fix了,之後 "full" 所在行的內容將為全 0。

廣告時間

喜歡花花的朋友,歡迎來我家自己開的小店—— 毛豆花園

每天關照植物,觀察花朵、樹葉與季節的消長與迴圈,讓其成為生活裡的一種意義。

參考

原創文章,轉載請註明出處。