一、CPU使用率過高
1,CPU使用率
a>節拍率
為了維護CPU時間,Linux通過事先定義的節拍率(核心中表示為HZ),觸發時間中斷,並使用全域性變數Jiffies記錄開機以來的節拍數。每發生一次時間中斷,Jiffies的值就加1
節拍率HZ是核心的可配置選項
#檢視當前系統的節拍率為每秒鐘250次時間中斷 grep 'CONFIG_HZ=' /boot/config-$(uname -r) CONFIG_HZ=250
同時核心還提供了一個使用者空間節拍率USER_HZ,固定值為100,也就是1/100秒
b>/proc虛擬檔案系統
cpu 2032004 102648 238344 167130733 758440 15159 17878 0 cpu0 1022597 63462 141826 83528451 366530 9362 15386 0 cpu1 1009407 39185 96518 83602282 391909 5796 2492 0 intr 303194010 212852371 3 0 0 11 0 0 2 1 1 0 0 3 0 11097365 0 72615114 6628960 0 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 236095529 btime 1195210746 processes 401389 procs_running 1 procs_blocked 0
第一行的數值表示的是CPU總的使用情況,所以我們只要用第一行的數字計算就可以了。下表解析第一行各數值的含義:
引數 解析(單位:jiffies) user(2032004) 從系統啟動開始累計到當前時刻,使用者態的CPU時間,不包含 nice值為負程式。 nice(102648) 從系統啟動開始累計到當前時刻,nice值為負的程式所佔用的CPU時間 system (238344) 從系統啟動開始累計到當前時刻,核心時間 idle (167130733) 從系統啟動開始累計到當前時刻,除IO等待時間以外其它等待時間 iowait (758440) 從系統啟動開始累計到當前時刻,IO等待時間 irq (15159) 從系統啟動開始累計到當前時刻,硬中斷時間 softirq (17878) 從系統啟動開始累計到當前時刻,軟中斷時間
c>CPU使用率
CPU在t1到t2時間段即時利用率 = 1 - CPU空閒使用時間 / CPU總的使用時間
CPU在t1到t2時間段空閒使用時間 = (idle2 - idle1)
CPU在t1到t2時間段總的使用時間 = ( user2+ nice2+ system2+ idle2+ iowait2+ irq2+ softirq2) - ( user1+ nice1+ system1+ idle1+ iowait1+ irq1+ softirq1)
2,如何檢視CPU使用率
a>top
top預設使用3秒時間間隔,它顯示了系統總體的CPU和記憶體使用情況,以及各個程式的資源使用情況
top - 09:52:06 up 2 days, 53 min, 1 user, load average: 0.38, 0.69, 0.88 任務: 445 total, 1 running, 353 sleeping, 0 stopped, 4 zombie %Cpu(s): 2.3 us, 1.4 sy, 0.0 ni, 95.8 id, 0.0 wa, 0.0 hi, 0.5 si, 0.0 st KiB Mem : 16163124 total, 588480 free, 11530672 used, 4043972 buff/cache KiB Swap: 2097148 total, 1243900 free, 853248 used. 2415292 avail Mem
PID USER PR NI VIRT RES SHR � %CPU %MEM TIME+ COMMAND 11775 mi 20 0 4872384 499200 134812 S 11.5 3.1 42:41.12 gnome-shell 11318 mi 20 0 1355336 261508 226128 S 7.6 1.6 53:12.32 Xorg 21215 mi 20 0 3407932 604084 27340 S 6.6 3.7 135:50.48 WeChat.exe 21220 mi 20 0 10068 6080 684 S 6.2 0.0 120:01.52 wineserver.real 9586 mi 20 0 693000 36112 21156 S 4.6 0.2 0:19.49 gnome-terminal- 5907 mi 20 0 13.246g 3.962g 29032 S 3.9 25.7 632:20.02 java
第三行%Cpu就是系統的CPU使用率,具體每列的含義與/proc類似
b>ps
ps (英文全拼:process status)命令用於顯示當前程式的狀態,程式的整個生命週期
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 225968 8288 ? Ss 2月07 1:57 /lib/systemd/systemd --system --deserialize 19 root 2 0.0 0.0 0 0 ? S 2月07 0:00 [kthreadd]
具體含義
USER: 行程擁有者 PID: pid %CPU: 佔用的 CPU 使用率 %MEM: 佔用的記憶體使用率 VSZ: 佔用的虛擬記憶體大小 RSS: 佔用的記憶體大小 TTY: 終端的次要裝置號碼 (minor device number of tty) STAT: 該行程的狀態:D: 無法中斷的休眠狀態 (通常 IO 的程式); R: 正在執行中; S: 靜止狀態; T: 暫停執行; Z: 不存在但暫時無法消除; W: 沒有足夠的記憶體分頁可分配; <: 高優先序的行程; N: 低優先序的行程; L: 有記憶體分頁分配並鎖在記憶體內 (實時系統或捱A I/O) START: 行程開始時間 TIME: 執行的時間 COMMAND:所執行的指令
c>pidstat
pidstat [ 選項 ] [ <時間間隔> ] [ <次數> ]
可以指定時間間隔來輸出CPU使用率
#每隔 1 秒輸出一組資料,共輸出 5 組 mi@mi:~$ pidstat 1 5 10時08分51秒 UID PID %usr %system %guest %wait %CPU CPU Command 10時08分52秒 1000 641 0.99 0.00 0.00 0.00 0.99 9 chrome 10時08分52秒 1000 996 0.00 0.99 0.00 0.00 0.99 4 chrome 10時08分52秒 1000 4387 0.99 0.99 0.00 0.00 1.98 0 pidstat 10時08分52秒 1000 5907 3.96 0.00 0.00 0.00 3.96 2 java ... 平均時間: UID PID %usr %system %guest %wait %CPU CPU Command 平均時間: 0 11 0.00 0.20 0.00 0.00 0.20 - rcu_sched 平均時間: 1000 548 0.20 0.20 0.00 0.00 0.40 - chrome 平均時間: 0 1745 0.00 0.20 0.00 0.00 0.20 - kworker/u24:4-events_unbound 平均時間: 0 2568 0.00 0.20 0.00 0.00 0.20 - kworker/u24:2-i915 平均時間: 0 3545 0.00 0.20 0.00 0.00 0.20 - kworker/9:2-events
-
%usr 使用者態CPU使用率
-
%system 核心態CPU使用率
-
%guest 執行虛擬機器CPU使用率
-
%wait 等待CPU使用率
-
%CPU 總的CPU使用率
-
最後的Average,則為計算了5組資料的平均值
3,CPU使用率過高怎麼辦?
a>採用top、ps、pidstat等工具,找到CPU使用率較高的程式
b>使用perf top 展示熱點函式
輸出結果中,第一行包含三組資料,分別是取樣數(Samples)、事件型別(event)和事件總數量(Event count)。比如當前perf採集了833個CPU時鐘事件,總事件數為97742399。
取樣數需要儘可能的多,這樣才有參考價值。解析表格樣式的資料為:
- Overhead,是該符號的效能事件在所有采樣中的比例,用百分比來表示
- Shared,是該函式或指令所在的動態共享物件(Dynamic Shared Object),如核心、程式名、動態連結庫名、核心模組名等。
- Object,是動態共享物件的型別。比如 [.] 表示使用者空間的可執行程式、或者動態連結庫,而 [k] 則表示核心空間
- Symbol,函式名。當函式名未知時,用十六進位制的地址來表示
從上圖可以看出CPU時鐘最多的是perf工具自身,其比例也只有7.28%,說明系統並沒有CPU效能問題。
c>perf record 和 perf report
perf top 雖然實時展示了系統的效能資訊,但他的缺點是並不儲存資料,無法用於離線或後續的分析
perf record 則提供了儲存資料的功能,儲存後的資料,需要用perf report解析展示
//按 ctrl + c 終止取樣 root@xc:~# perf record [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 3.679 MB perf.data (26683 samples) ] //展示類似於 perf top的報告 root@xc:~# perf report
在實際使用中,我們經常使用perf top 和 perf record 加上 -g 引數,開啟呼叫關係的取樣,方便呼叫鏈的分析
4,案例一
使用兩臺虛擬機器,其中一臺用作Web伺服器,來模擬效能問題;另一臺用作Web伺服器的客戶端,來給Web伺服器增加壓力請求。使用兩臺虛擬機器是為了相互隔離,避免“交叉感染”。
VM1上啟動應用:
VM2上測試並壓測:
啟動成功,進行效能壓測
從ab輸出結果可以看到,Nginx能承受的每秒平均請求數只有11.63。為了方便分析,我們將請求數增到10000,繼續壓測
VM1上分析原因
top,可以看到php-fpm的CPU使用率加起來將近200%;每個CPU的使用率均已超過98%
perf top ,使用-g 開啟呼叫關係分析, -p 指定 php-fpm的程式號 21515
發現呼叫關係最終到sqrt和add_function出現佔用的CPU時鐘較高。
5,案例二(定位CPU升高的應用)
使用兩臺虛擬機器,其中一臺用作Web伺服器,來模擬效能問題;另一臺用作Web伺服器的客戶端
VM1上啟動應用
VM2上測試並壓測:
啟動成功,進行壓測
從ab結果可以看到,Nginx能承受的每秒平均請求數,只有87左右。接下來,將併發請求數改成5,延長請求時長為10分鐘,方便vm1定位分析問題
VM1上分析原因
top分析,發現CPU使用最高的程式才2.7%,並不特別高。然而在CPU使用率(%Cpu),可以看到使用者CPU使用率為80%,系統CPU為15.1%,而空閒CPU為2.8%
pidstat,觀察發現CPU的使用率也都不高
再次採用top觀察,發現Tasks中有個6個Running狀態的程式,而觀察我們們啟動的php-fpm確都處於Sleep(S)狀態,真正處於Running狀態的時stress程式
採用pidstat進一步觀察其中的一個stress程式,發現無輸出
採用ps檢視這個stress程式,任然無輸出。
再次採用top命令檢視,發現stress程式的程式號都已經發生變化
分析原因:
- 程式在不斷地崩潰重啟,比如因為配置錯誤等,程式在退出後可能又被監控系統自動重啟
- 程式為短時程式,也就是在其他應用內部通過exec呼叫的外部命令。這些命令一般都只執行很短的時間就會結束,也很難通過top這種間隔時間較長的工具發現
通過pstree查詢真實的stress程式的父程式,這裡可以看到stress是被php-fpm呼叫的子程式,並且程式數量不止一個(這裡是3個)
採用perf record -g命令,等待一會兒之後按Ctrl+C退出,再用perf report檢視報告
發現 stress佔用了CPU時鐘事件的77%,而stress呼叫棧中比例較高的為函式random(),再進行具體的優化,就可以解決。
6,execsnoop
execsnoop-專門用於為追蹤短時程式(瞬時程式)設計的工具;它通過 ftrace 實時監控程式的 exec() 行為,並輸出短時程式的基本資訊,包括程式 PID、父程式 PID、命令列引數以及執行的結果。
github地址: https://github.com/brendangregg/perf-tools/blob/master/execsnoop
如何安裝使用:將上面的github的內容複製,然後寫入execsnoop檔案,並且加上x許可權即可;
可以發現大量的stress程式在不停啟動
二、殭屍程式
1,程式的狀態
以top為例,在top中有一列為S列(也就是Status列),可以檢視到有T、D、Z、S、I等幾個狀態
- R是Running或Runnable的縮寫,表示程式在CPU的就緒佇列中,正在執行或者正在等待執行
- D是Disk Sleep的縮寫,也就是不可中斷狀態睡眠(Uninterruptible Sleep),一般表示程式正在跟硬體互動,並且互動過程不允許被其他程式中斷
- Z是Zombie的縮寫,它表示殭屍程式,也就是程式實際上已經結束了,但是父程式還沒有回收它的資源(比如程式的描述符、PID等)
- S是Interruptible Sleep的縮寫,也就是可中斷狀態睡眠,表示程式因為等待某個事件而被系統掛起。當程式等待的事件發生時,它會被喚醒並進入R狀態
- I是Idle的縮寫,也就是空閒狀態,用在不可中斷睡眠的核心執行緒上。前面說了,硬體互動導致的不可中斷程式用D表示,但對某些核心執行緒來說,它們有可能實際上並沒有任何負載,用Idle正是為了區分這種情況。因為,D狀態的程式會導致平均負載升高,I狀態的程式卻不會
- T(t)是Stopped或Traced的縮寫,表示程式處於暫停或跟蹤狀態。
- X是Dead的縮寫,表示程式已經消亡,不能在top或ps中看到
當一個程式建立了子程式後,它應該通過系統呼叫wait()或waitpid()等待子程式結束,回收子程式的資源;而子程式在結束時,會向它的父程式傳送SIGCHLD訊號,所以父程式也可以註冊SIGCHLD訊號的處理函式,非同步回收資源。
如果父程式沒這麼做,或子程式執行太快,父程式還沒有來得及處理子程式狀態,子程式就已經提前退出,那麼這時的子程式就會變成殭屍程式。通常,殭屍程式持續的時間都比較短,在父程式回收它的資源後就會消亡;或者在父程式退出後,由init程式回收後也會消亡。
一旦父程式沒有處理子程式的終止,還一直保持執行狀態,那麼子程式就會處於殭屍狀態。大量的殭屍程式會用盡PID程式號,導致新程式不能建立,所以這種情況需要避免。
2,案例分析
準備Ubuntu 18.04 機器配置 2CPU,8GB記憶體,預先安裝docker、sysstat、dstat等工具
啟動應用
ps檢視應用為正常啟動
Ss+和D+。其中S表示可中斷睡眠,D表示不可中斷睡眠。s表示這個程式是一個會話的領導程式,+表示前臺程式組
top檢視
- 第一行看平均負載(load average),過去1分鐘、5分鐘和15分鐘的平均負載在依次縮小,說明平均負載在升高。1分鐘內的平均負載為2,說明有可能已經達到效能瓶頸
- 第二行看Tasks,有1個正在執行的程式,但是發現zombie殭屍程式比較多,而且在不停的增加,說明有子程式在退出時未被清理
- 檢視CPU的使用情況,發現使用者CPU和系統CPU都不高,但是iowait分別時60.5%和94.6%
dstat分析iowait
可以看到當iowait(wai)升高時,磁碟的讀請求(read)都會很大。這說明iowait的升高跟磁碟的讀請求有關,很可能就是磁碟讀導致的。接下來需要進一步分析具體的哪個程式讀磁碟呢?
pidstat採用-d分析I/O和程式之間的情況
可以發現app程式每秒讀的資料為32MB,基本可以斷定是app應用導致。接下來繼續追蹤app程式在執行什麼I/O操作?
strace追蹤程式6082
ps檢視6082程式情況,發現6082已經是殭屍程式。
目前iowait還在繼續,但是top、pidstat這類工具已經不能給予幫助了,接下來採用perf檢視
其中swapper是核心中的排程程式,可以先忽略。可以看到在app中通過系統呼叫sys_read()讀取資料。並且從new_sync_read和blkdev_read_iter能看出,程式正在對磁碟進行直接讀,也就是繞過了系統快取,每個讀請求都會直接從磁碟讀取,導致iowait升高。 定位到原因後就可以開啟原始碼發現在app.c的檔案出現了O_DIRECT選項直接開啟磁碟,刪除這個選項
重啟後,再使用top檢視,發現iowait已經很低了只有0.3%。但是檢視殭屍程式的數量還是在不斷的增加
採用pstree定位父程式,發現還是app應用導致的殭屍程式。進一步檢視並修復。