效能分析(4)- iowait 使用率過高案例

小菠蘿測試筆記發表於2020-08-10

效能分析小案例系列,可以通過下面連結檢視哦

https://www.cnblogs.com/poloyy/category/1814570.html

 

前言

  • 前面兩個案例講的都是上下文切換導致的 CPU 使用率升高
  • 這一篇就來講講等待 I/O 導致的 CPU 使用率升高的案例

 

程式狀態

詳解程式狀態

https://www.cnblogs.com/poloyy/p/13413770.html

 

不可中斷狀態

  • 當 iowait 升高時,程式很可能因為得不到硬體的響應,而長時間處於不可中斷狀態
  • 不可中斷也是為了保護程式資料和硬體狀態一致,並且正常情況下,不可中斷狀態在很短時間內就會結束
  • 所以,短時的不可中斷程式,一般可以忽略
  • 但如果系統或硬體發生了故障,程式可能會在不可中斷狀態保持很久,甚至導致系統中出現大量不可中斷程式。這時,就得注意下,系統是不是出現了 I/O 等效能問題

 

殭屍程式

多程式引用很容易碰到的問題

 

正常情況

  • 一個程式建立了子程式後,它應該通過系統呼叫 wait() 或 waitpid() 等待子程式結束,回收子程式的資源
  • 而子程式在結束時,會向它的父程式傳送 SIGCHLD 訊號
  • 所以,父程式還可以註冊 SIGCHLD 訊號的處理函式,非同步回收資源

 

異常情況

  • 如果父程式沒有回收資源,或是子程式執行太快,父程式還沒來得及處理子程式狀態,子程式就已經提前退出,那這時的子程式就會變成殭屍程式
  • 形象比喻:父親應該一直對兒子負責, 善始善終,如果不作為或者跟不上,都會導致“問題少年”的出現

 

重點

  • 殭屍程式持續的時間都比較短,在父程式回收它的資源後就會消亡,或者在父程式退出後,由 init 程式回收後也會消亡
  • 一旦父程式沒有處理子程式的終止,還一直保持執行狀態,那麼子程式就會一直處於殭屍狀態
  • 大量的殭屍程式會用盡 PID 程式號,導致新程式不能建立

 

大量不可中斷狀態和殭屍狀態程式的案例

系統配置

  • Ubuntu 18.04, 2 CPU,2GB 記憶體
  • 前置條件:已執行案例應用

 

通過 ps 命令檢視案例程式

ps aux | grep /app

結果分析

  • 多個 app 程式已啟動
  • 狀態有 Ss+、D+、R+
  • 小s:表示這個程式是一個會話的領導程式
  • +:表示前臺程式組

 

什麼是會話和程式組

  • 它們是用來管理一組相互關聯的程式
  • 程式組:比如每個子程式都是父程式所在組的成員
  • 會話:共享同一個控制終端的一個或多個程式組

 

會話和程式組的場景類比

  • 通過 SSH 登入伺服器,就會開啟一個控制終端(TTY),這個控制終端就對應 一個會話
  • 而在終端中執行的命令以及它們的子程式,就構成了一個個的程式組
  • 後臺執行的命令,構成後臺程式組
  • 前臺執行的命令,構成前臺程式組

 

通過 top 檢視系統狀況

結果分析

  • 平均負載,過去 1min、5min、15min 的平均負載依次減少,說明平均負載正在升高
  • 而 1min 內的平均負載已經達到系統 CPU 個數,說明系統很可能存在效能瓶頸
  • 115 zombie 說明殭屍程式比較多,而且在不停增加,有子程式在退出時沒被清理
  • 使用者 CPU 和系統 CPU 都不高,但 iowait 分別是 60.5% 和 94.6%,好像有點兒不正常,導致系統的平均負載升高
  • 有兩個處於 D 狀態的 app 程式,可能在等待 I/O

 

檢視系統的殭屍程式

ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]'ps -ef | grep "defunct"

一堆 app 殭屍程式

 

iowait 分析

一提到 iowait 升高,首先會想要查詢系統的 I/O 情況

 

執行 dstat 命令,觀察 CPU 和 I/O 的使用情況

dstat 1 10

  • 當 iowait 升高(wai)時,磁碟的請求(read)都會很大(M)
  • 這說明 iowait 的升高跟磁碟的讀請求有關,很可能就是讀磁碟導致的

 

找到讀磁碟的程式

  • 通過 top 找到 D 狀態的兩個 app 程式
  • 不可中斷狀態代表程式在跟硬體進行互動,很可能就是讀磁碟

兩個 app 程式的 PID 分別是12407、12406

 

通過 pidstat 檢視 app 程式的 I/O 情況

pidstat -d -p 12407 1 5
  • -d 展示 I/O 統計資料
  • -p 指定程式號
  • 間隔 1 秒輸出 5 組資料

  • kB_rd 表示每秒的 KB 數, kB_wr 表示每秒的 KB 數,iodelay 表示 I/O 的延遲(單位是時鐘週期)
  • 它們都是 0,那就表示此時沒有任何的讀寫,說明問題不 是 12407 程式導致的,也並不是12406 程式導致的

 

通過 pidstat 檢視系統的 I/O 情況

pidstat -d 1 10

  • 能看到其實的確是 app 程式在讀,只不過每過幾秒都會有新的 app 程式在讀【pid 在不斷變化】
  • 可以確認,是 app 程式的問題

 

通過 ps 命令檢視一直變化的 app 程式狀態

前面講到讀磁碟的 app 程式 PID 一直在變化,那麼就來看看已經沒在讀磁碟的程式的程式狀態是怎麼樣的

ps aux | grep 15973

  • 這程式已經是 Z 狀態,就是殭屍程式
  • 殭屍程式都是已經退出的程式, 所以就沒法兒繼續分析它的系統呼叫
  • 關於殭屍程式的處理方法,我們一會兒再說,現在還是繼續分析 iowait 的問題

 

通過 perf 錄製效能事件

  • 系統 iowait 的問題還在繼續,但是 top、pidstat 這類工具已經不能給出更多的資訊了
  • 此時可以通過 perf 動態跟蹤效能事件
perf record -g

15s 後 ctrl+c 終止錄製

 

檢視報告,分析報告

perf report

  • app 的確在通過系統呼叫 sys_read() 讀取資料
  • 並且從 new_sync_read 和 blkdev_direct_IO 能看出,程式正在對磁碟進行直接讀,也就是繞過了系統快取,每個讀請求都會從磁碟直接讀,這就可以解釋觀察到的 iowait 升高了

 

修復原始碼之後,通過 top 命令驗證

  • iowait 已經非常低了,只有 0.3%
  • 說明修改原始碼已經成功修復了 iowait 高的問題
  • 不過,仔細觀察殭屍程式的數量,會發現,殭屍程式還在不斷的增長中

 

處理和分析殭屍程式

  • 殭屍程式是因為父程式沒有回收子程式的資源而出現的
  • 解決殭屍程式需要先找出父程式,然後在父程式裡解決

 

通過 pstree 找到某個 app 程式的父程式

pstree -aps 51780

51780 程式的父程式是 51688,也就是 app 應用

 

通過 ps 檢視所有殭屍程式的父程式

ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]'

所有殭屍程式的父程式都是 51688,從而確認 51688 就是殭屍程式的父程式

 

檢視 app 應用程式的程式碼

檢視 app 應用程式的程式碼,看看子程式結束的處理是否正確

  1. 有沒有呼叫 wait() 或 waitpid() 
  2. 或有沒有註冊 SIGCHLD 訊號的處理函式

把 wait() 放到了 for 死迴圈的外面,也就是說, wait()  函式實際上並沒被呼叫到,把它挪到 for 迴圈的裡面就可以了

 

改完原始碼,通過 top 驗證一下

殭屍程式(Z 狀態)沒有了, iowait 也是 0,問題終於全部解決了

 

總結

  • 這個案例是因為磁碟 I/O 導致了 iowait 升高
  • 不過,iowait 高並不一定代表 I/O 有效能瓶頸
  • 當系統中只有 I/O 型別的程式在執行時,iowait 也會很高,但實際上,磁碟的讀寫遠沒有達到效能瓶頸的程度

 

分析整體思路

  1. 通過 top 檢視系統資源情況
  2. 發現平均負載逐漸升高,iowait(wa)比較高,但使用者態和核心態 CPU 使用率並不算高
  3. 檢視是否有 CPU 使用率偏高的程式,發現有 D 狀態的程式,可能是在等待 I/O 中
  4. 過一陣子會變成 Z 狀態程式,且 CPU 使用率上升,然後會看到 zombie 程式數逐漸增加
  5. 可以得到兩個結論:殭屍程式過多,應該是父程式沒有清理已經結束的子程式的資源;iowait 的上升導系統平均負載上升
  6. 因為是 iowait 較高,可以通過 dstat 檢視系統的 I/O 情況,會發現每次 iowait 升高,讀磁碟請求都會很大
  7. 通過 pidstat -d 檢視 D 狀態程式的 I/O 情況,但發現並沒有有效資訊
  8. 通過 pidstat -d 直接檢視系統的 I/O 情況,可以發現不斷有新程式在進行讀磁碟操作
  9. 通過 ps 命令檢視剛剛 D 狀態程式當前的程式狀態,發現已經變成殭屍程式
  10. 通過 perf record 錄製效能事件,然後通過 perf report 檢視效能報告,可以發現 app 程式都是直接讀磁碟,而不經過系統快取
  11. 通過 pstree 找到 Z 狀態程式的父程式
  12. 通過 ps 命令確認所有殭屍程式的父程式
  13. 找到父程式原始碼,檢查 wait() / waitpid() 的是否會成功呼叫,或是 SIGCHLD 訊號處理函式的註冊就行了
  14. 修改完全部原始碼後,重新執行應用,通過 top 驗證是否還有 iowait 過高和出現 zombie 程式的情況

相關文章