一、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應用導致的僵屍進程。進一步查看並修復。