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。
廣告時間
喜歡花花的朋友,歡迎來我家自己開的小店—— 毛豆花園 。
每天關照植物,觀察花朵、樹葉與季節的消長與迴圈,讓其成為生活裡的一種意義。
參考
- Getting Started with PSI · PSI
- Linux Pressure Stall Information (PSI) by Example - Unixism
- https://www. kernel.org/doc/html/lat est/accounting/psi.html
原創文章,轉載請註明出處。
- Cgroup 記憶體使用的監測手段
- Linux 的資源控制監測 - PSI [下]
- drgn - Linux 除錯的另一隻翅膀 [下]
- Linux - 如何確定記憶體洩露沒有發生
- NUMA 的平衡和排程
- Linux - 如何檢視函式的引數 [二]
- Linux - 再議記憶體回收之 swappiness
- Linux - 如何檢視函式的引數 [一]
- Linux - 核心 benchmark 測試之 MMTests
- 多核系統的負載均衡 [二]
- sysctl 引數防篡改 - 基於 ebpf 的實現 [二]
- sysctl 引數防篡改 - 基於傳統方法的實現
- 關於 eBPF 的一些粗淺理解
- Linux 的排程延遲 - 原理與觀測
- 模型與抽象 - 從 fanotify 的控制說開來
- Linux中的RCU機制[二] - GP的處理
- 由 Timer Wheel 帶來的思考
- Linux 排程 - 切換型別的劃分
- Linux 中的等待佇列機制
- Linux 中執行排程的時機