前言
作為一名測試人員,難免需要維護業(yè)務線的測試環(huán)境。初時每當研發(fā)通知某某機器又掛了、某某機器太卡了,筆者完全兩眼一抹黑,無從入手。而當了解了 linux 基本性能,熟悉其基本指標時,遇到問題往往心里有底,甚至得心應手。送人玫瑰,手有余香,特此整理,與君共享。
性能指標
CPU 使用率
提起性能指標,最容易想到的是CPU使用率。linux 作為一個多任務系統,將每個 CPU 的時間劃分為很短的時間片,通過調度器輪流分配給各個任務使用,造成多任務同時運行的錯覺。而根據 CPU 上執(zhí)行任務的不同,又可以細分為用戶CPU、系統CPU、等待 I/O CPU、軟中斷CPU 和硬中斷CPU 等。
- 用戶CPU使用率(us + ni),包括用戶態(tài)CPU使用率(user)和低優(yōu)先級用戶態(tài)CPU使用率(nice),表示 CPU 在用戶狀態(tài)運行的時間百分比。用戶CPU使用率高,說明有應用程序比較繁忙。
- 系統CPU使用率(sy),表示 CPU 在內核狀態(tài)運行的時間百分比。系統CPU使用率高,說明內核比較繁忙。
- 等待 I/O 使用率(wa),也稱為 iowait,表示等待 I/O 的時間百分比。iowait 高,說明系統與硬件設備的 I/O 交互時間比較長。
- 軟中斷(si)和硬中斷(hi)的 CPU使用率,分別表示內核調用軟中斷處理程序、硬中斷處理程序的時間百分比。它們的使用率高,通常說明系統發(fā)生了大量的中斷。
CPU使用率如何計算
CPU使用率描述了非空閑時間占總 CPU 時間的百分比。用公式表示為:
可以通過以下命令查看各個 CPU 的數據:
$ cat /proc/stat | grep ^cpu
# cpu us ni sys id wa hi si st guest gnice
cpu 42849699018 205001 4396958253 10689835442 115714471 51747 397324892 0 0 0
cpu0 1448897830 9918 260556249 620723421 17589437 11620 80282872 0 0 0
cpu1 1802849978 8539 182700339 444025341 2238152 1 5138950 0 0 0
cpu2 1846754519 9857 177641895 405553744 1799005 0 4945465 0 0 0
cpu3 1854741192 10151 175660372 399834166 1748613 0 4771745 0 0 0
...
這里輸出的是一個表格,第一列為 CPU 的編號,如cpu0,而第一行沒有 CPU 編號,表示所有 CPU 的累加。其他列表示不同任務下 CPU 的時間,每一列的順序可以通過man proc 命令查看。通過這些數據就可以計算出當前機器的 CPU使用率。然而,得到的 CPU 使用率是開機以來的平均CPU使用率,一般不具有參考價值。性能工具一般會取間隔一段時間的兩次差值,計算這段時間內的平均CPU使用率,即:
如何查看CPU使用率
top和 ps 是常用的性能分析工具。top 能夠顯示系統總體的CPU和內存使用情況,以及各進程的資源使用情況。ps 顯示各進程的資源使用情況。例如,top 的輸出格式為:
$ top
top - 21:23:36 up 283 days, 10:41, 2 users, load average: 21.96, 14.46, 13.76
Tasks: 2009 total, 10 running, 1994 sleeping, 0 stopped, 5 zombie
Cpu(s): 32.5%us, 7.6%sy, 0.0%ni, 58.3%id, 0.6%wa, 0.0%hi, 0.9%si, 0.0%st
Mem: 98979248k total, 95372168k used, 3607080k free, 1479704k buffers
Swap: 0k total, 0k used, 0k free, 59440700k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9103m 6.0g 13m S 168 6.4 156503:16 mongod
31963 root 20 0 929m 882m 672 R 52 0.9 0:01.61 supervisord
...
其中,第三行 CPU 就是該機器的 CPU使用率,top 默認每 3 秒刷新一次。默認情況下顯示的是所有 CPU 的平均值,如果想了解每個 CPU 的使用情況,只需按下數字 1 即可。例如:
Cpu0 : 17.0%us, 10.2%sy, 0.0%ni, 64.9%id, 2.3%wa, 0.0%hi, 5.6%si, 0.0%st
Cpu1 : 12.1%us, 5.5%sy, 0.0%ni, 82.1%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu2 : 24.5%us, 4.0%sy, 0.0%ni, 71.2%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
...
從上我們可以發(fā)現,top 并沒有細分進程的用戶態(tài)CPU 和內核態(tài)CPU,如果想了解每個進程的詳細情況,可以使用 pidstat 工具。例如:
$ pidstat 1 1
Linux 3.2.0-23-generic (cs) 06/24/2021 _x86_64_ (24 CPU)
09:32:41 PM PID %usr %system %guest %CPU CPU Command
09:32:42 PM 1522 0.88 0.88 0.00 1.77 20 beam.smp
...
分析
一般來說,每種類型的 CPU使用率高,都有不同導致的條件,下面列出了這幾種導致不同 CPU使用率高的情況以及著重排查點:
- 用戶 CPU 和 Nice CPU 高,說明用戶態(tài)進程占用了較多的 CPU,所以應該著重排查進程的性能問題。
- 系統 CPU 高,說明內核態(tài)占用了較多的 CPU,所以應該著重排查內核線程或者系統調用的性能問題。
- I/O 等待 CPU 高,說明等待 I/O 的時間比較長,所以應該著重排查系統存儲是不是出現了 I/O 問題。
- 軟中斷和硬中斷高,說明軟中斷或硬中斷的處理程序占用了較多的 CPU,所以應該著重排查內核中的中斷服務程序。
平均負載
第二能想到的就是平均負載。很多人會認為,平均負載不就是單位時間內的 CPU使用率嗎?其實并非如此。下面我們來一起熟悉下什么是平均負載。
通俗地說,平均負載是指單位時間內系統處于可運行狀態(tài)和不可中斷狀態(tài)的平均進程數,即平均活躍進程數。從定義我們可以解讀到其實跟 CPU使用率無直接關系。
linux 將進程的狀態(tài)劃分為以下幾種:
R 是 Running 的縮寫,表示進程正在運行或者正在等待運行。
D 是 Disk Sleep 的縮寫,為不可中斷睡眠狀態(tài),進程正與硬件進行交互,交互過程不允許被其他進程打斷,以防止數據丟失。
Z 是 Zombie 的縮寫,表示僵尸進程,進程實際已經結束,但是父進程還沒回收其資源,就會出現該狀態(tài)。
S 是 Interruptible Sleep 的縮寫,稱為可中斷睡眠狀態(tài),表示進程因等待某個事件而被系統掛起。
I 是 Idle 的縮寫,為空閑狀態(tài),用于不可中斷睡眠的內核線程上。
T 是 Traced 的縮寫,表示進程處于暫停狀態(tài)。
X 是 Dead 的縮寫,表示進程已經消亡。
如何查看
通常使用 uptime 或 top 命令查看。例如,uptime 的輸出格式為:
$ uptime
15:22:50 up 284 days, 4:40, 3 users, load average: 10.06, 12.15, 12.33
輸出分別表示為當前時間、系統運行時間、正在登錄用戶數以及過去 1 分鐘,5 分鐘,15 分鐘的平均負載。輸出這三個時間的平均負載有什么含義呢?
- 數值基本相同,說明系統負載趨于平穩(wěn)。
- 數值從過去 15 分鐘到過去 1 分鐘逐漸升高,說明系統運行任務在增多,需要我們去觀察和判斷這種升高是否合理。
- 數值從過去 15 分鐘到過去 1 分鐘在逐漸降低,說明系統運行任務在減少。
基于此就可以粗略判斷當前的系統負載情況。上面的例子可以看到目前的系統負載下降的。
多少為合理
平均負載這一數據沒有針對 CPU 個數作歸一化處理,因此最理想情況是平均負載等于 CPU 的個數。在評判當前系統是否過載時,首先需要知道當前系統的 CPU 個數,可以通過 top 或者 lscpu 查看。例如:
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 24
...
當知道了 CPU 個數后,就可以結合平均負載來判斷當前系統的運行情況了。一旦負載過高,會導致進程響應變慢,影響服務的正常功能。故當出現負載有明顯升高趨勢時,就需要我們去分析和調查了。通常認為平均負載低于 CPU 數量 70% 時為合理狀態(tài),但不是絕對的,還需要參考負載的變化趨勢來作判斷。
CPU上下文切換
前面描述了 linux 系統會在很短的時間內把 CPU 輪流分配給任務(進程),造成多個任務同時運行的錯覺。而這些任務都是從哪里加載以及開始運行的呢?這就引出了 CPU上下文。CPU寄存器和程序計數器被叫做 CPU 的上下文,那么,CPU寄存器和程序計數器分別有什么作用呢?
- CPU寄存器,存放數據的小型存儲區(qū)域,用來暫時存放參與運算的數據和運算結果。
- 程序計數器,用來存儲 CPU 下一條指令位置。
而 CPU上下文切換,就是把前一個任務的 CPU上下文保存起來,然后加載新任務的上下文到寄存器和程序計數器中,最后跳轉到程序計數器所指位置,執(zhí)行新任務。根據任務的不同,可以分為三種切換場景:進程上下文切換、線程上下文切換以及中斷上下文切換。
進程上下文切換
提到進程上下文切換,就不得不提到系統調用。linux 按照特權等級,把進程的運行空間劃分為內核空間和用戶空間。
- 內核空間,具有最高權限,可以訪問所有資源。
- 用戶空間,訪問受限,不能直接訪問內存等硬件設備,只能通過系統調用到內核中,由內核訪問。
系統調用和普通函數調用非常相似,只不過,系統調用由操作系統核心提供,運行于內核態(tài),而普通函數調用由函數庫或用戶提供,運行于用戶態(tài)。比如,某一進程需要查看文件內容時,就需要系統調用來完成:首先調用 open() 打開文件,通過 read() 讀取文件內容,并通過 write() 寫到到標準輸出,最后通過 close() 關閉文件。系統調用結束后,CPU寄存器恢復到原來保存的用戶態(tài),切換到用戶空間,繼續(xù)執(zhí)行進程。
從上我們可以知道,系統調用是在進程內進行的,其與進程上下文切換有點不同,它不會涉及到虛擬內存等用戶態(tài)資源。所以我們通常稱系統調用為一種特權模式切換,而在系統調用用上下文切換是不可避免的,一次系統調用發(fā)生了兩次上下文切換。
進程是由內核管理和調度的,進程的上下文切換在內核態(tài)進行,故其上下文切換要比系統調用多了一步:在保存當前進程的內核狀態(tài)和 CPU寄存器之前,需要先把該進程的虛擬內存、棧等用戶資源保存起來。
那么,什么時候需要進行上下文切換呢?
- 進程的系統資源不足時,進程就會被掛起,等到資源充足時才能進行。
- 進程通過 sleep 睡眠函數等方法主動掛起時。
- 當有更高優(yōu)先級的進程運行時,例如中斷等,進程會被掛起,來保證高優(yōu)先級進程正常運行。
線程上下文切換
我們知道,線程是調度的基本單位,而進程是資源擁有的基本單位,即內核中的任務調度實際對象是線程,進程只是給線程提供了虛擬內存等資源。如果進程中有多個線程,則線程會共享虛擬內存等資源。因此,對于線程上下文切換就有兩種情況:
- 前后切換線程屬于同一進程,則虛擬內存等資源就不需要切換,只需要切換線程需要的私有資源。
- 前后切換線程屬于兩個進程,此時跟進程上下文切換一致。
由此我們知道同一進程的線程上下文切換,要比進程間的上下文切換耗費更少資源,這也是多線程代替多進程的一大優(yōu)勢。
中斷上下文切換
中斷要比進程的優(yōu)先級更高,其會打斷進程的正常調度,因此中斷程序需要更短小精悍,以便盡可能快地結束。
中斷上下文切換與進程不同,其不需要虛擬內存、全局變量等用戶態(tài)資源,只需要如 CPU寄存器、硬件中斷參數等內核態(tài)中斷服務程序所需要的狀態(tài)。
如何查看
查看系統總體的上下文切換情況可以通過 vmstat 命令,其輸出格式為:
$ vmstat 1 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
10 0 0 1653888 1477852 61630304 0 0 0 112 0 0 73 8 19 0
其中,需要關注以下幾個參數:
- r (running),正在運行和等待運行的進程數。
- b (blocked),不可中斷睡眠狀態(tài)的進程數。
- in (interrupt),每秒中斷次數。
- cs (context switch), 每秒的上下文切換次數。
而如果我們想要查看進程的上下文切換,可以通過 pidstat 查看,輸出格式為:
# 通過參數 -w 可以查看進程每秒的上下文切換次數
$ pidstat -w 1 1
Linux 3.2.0-23-generic (cs) 06/26/2021 _x86_64_ (24 CPU)
03:19:25 PM PID cswch/s nvcswch/s Command
03:19:26 PM 1 0.85 0.00 init
03:19:26 PM 3 0.85 0.00 ksoftirqd/0
...
其中,需要關注兩個參數:
- cswch (voluntary context switch),每秒自愿上下文切換次數。當進程無法獲取資源,比如內存、IO 等資源不足時,就會發(fā)生自愿上下文切換。
- nvcswch (non voluntary context switch),每秒非自愿上下文切換次數。當進程的時間片已到,比如大量進程爭搶 CPU 時,就會被系統強制調度,發(fā)生非自愿上下文切換。
如果我們想要查看線程間的上下文切換,還是可以通過 pidstat 查看,加上 -wt 參數選項,輸出格式為:
$ pidstat -wt 1 1
linux 3.2.0-23-generic (cs7) 06/26/2021 _x86_64_ (24 CPU)
03:34:07 PM TGID TID cswch/s nvcswch/s Command
03:34:07 PM 2200 - 0.64 0.00 flush-8:48
03:34:07 PM - 2200 0.64 0.00 |__flush-8:48
03:34:07 PM - 2524 136.54 0.00 |__influxd
03:34:07 PM - 2537 26.92 0.00 |__influxd
03:34:07 PM - 3701 12.82 3.21 |__1_scheduler
03:34:07 PM - 3702 0.64 0.00 |__2_scheduler
03:34:07 PM - 3725 1.28 0.00 |__aux
...
多少為合理
據統計,每次上下文切換需要耗費幾十納秒到數微秒的CPU時間,這個時間還是可以接受的。但是過多的上下文切換,會把 CPU 時間都耗費在寄存器、內存棧以及虛擬內存的保存和恢復上,縮短進程真正運行的時間,導致系統性能大幅下降。那么,我們肯定想了解,多少的 CPU上下文切換為合理情況呢?
這取決于 CPU 的性能。如果上下文切換穩(wěn)定,那么從數百到上萬都是合理的。但是當 CPU上下文切換呈數量級增大時,或者數值已超過上萬次且不斷增長時,就可能出現了性能問題,需要我們著重排查。
CPU緩存命中率
CPU緩存是為了協調CPU處理速度和內存訪問速度之間的差距而出現的空間,可分為 L1、L2 和 L3 三級緩存。離 CPU 核心越近,緩存的讀寫速度就越快,因此三級緩存的讀寫速度快慢分別是:L1 > L2 > L3.
如果 CPU 所操作的數據在緩存中,則可以直接讀取,稱為緩存命中。命中緩存可以帶來很大的性能提升。因此,考慮 CPU 的緩存命中率對于 linux 性能也至關重要。
查看系統的內存情況,可通過 top 或 free 命令。比如,free 命令輸出格式為:
$ free
total used free shared buffers cached
Mem: 98979248 97562252 1416996 0 1478000 62018692
Swap: 0 0 0
輸出分別是物理內存 Mem 和交換分區(qū) Swap 的使用情況。其中,每一列分別是:
- total ,總內存大小。
- used,已使用內存的大小。
- free,未使用的內存大小。
- shared,共享內存的大小。
- buffers/cached,緩存和緩沖區(qū)的大小
查看 buffers 和 cache 的變化情況,可通過 vmstat 命令,輸出格式:
$ vmstat 1 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
9 0 0 1571640 1478044 62034792 0 0 0 112 0 0 73 8 19 0
- buffer 和 cache 就是上面所說的緩存和緩沖區(qū)的大小,單位為 KB。
- bi 和 bo 分別表示塊設備讀取和寫入的大小,單位為塊 / 秒。linux 中塊的大小是 1KB,故單位等價于 KB/s。
實踐
下面就目前機器出現的系統使用率過高的情況進行排查。
通過 top 可以查看當前的系統性能情況:
$ top
top - 16:41:29 up 285 days, 5:59, 2 users, load average: 9.82, 11.53, 12.92
Tasks: 2010 total, 9 running, 2000 sleeping, 0 stopped, 1 zombie
Cpu(s): 34.5%us, 6.5%sy, 0.0%ni, 57.6%id, 0.5%wa, 0.0%hi, 0.9%si, 0.0%st
Mem: 98979248k total, 97548032k used, 1431216k free, 1478084k buffers
Swap: 0k total, 0k used, 0k free, 62122812k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9128m 6.1g 13m S 90 6.5 160516:12 mongod
19638 root 20 0 929m 882m 520 R 58 0.9 0:01.81 supervisord
...
CPU用戶使用率為 34.5%,系統使用率為 6.5%,空閑時間占 57.6%,系統使用流暢。可以看出,當前的系統是比較空閑的。
然后,我執(zhí)行某些操作。再通過 top 命令查看當前的系統性能情況:
$ top
Tasks: 2020 total, 16 running, 2004 sleeping, 0 stopped, 0 zombie
Cpu(s): 64.5%us, 13.6%sy, 0.0%ni, 20.5%id, 0.2%wa, 0.0%hi, 1.1%si, 0.0%st
Mem: 98979248k total, 98003524k used, 975724k free, 1478128k buffers
Swap: 0k total, 0k used, 0k free, 62228740k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9128m 6.2g 13m S 249 6.5 160538:08 mongod
15108 root 20 0 929m 882m 620 R 73 0.9 0:02.24 supervisord
15121 root 20 0 929m 882m 620 R 66 0.9 0:02.03 supervisord
15122 root 20 0 929m 882m 620 R 66 0.9 0:02.03 supervisord
...
可以發(fā)現,系統使用率已經增至了原來的一倍。CPU使用率除了服務 mongod 最高以外,有幾個 supervisord 啟動的服務也出現了CPU使用率增高的情況。通過 pidstat 命令分析這些進程的系統資源占用情況:
$ pidstat -p 15108
Linux 3.2.0-23-generic (cs) 06/26/2021 _x86_64_ (24 CPU)
05:03:14 PM PID %usr %system %guest %CPU CPU Command
發(fā)現該進程并沒有輸出任何的資源使用信息。是 pidstat 指令出故障了?可以通過 ps 指令來交叉確認一下:
$ ps aux | grep 15108
2003 14211 0.0 0.0 8748 948 pts/2 S+ 17:07 0:00 grep --color=auto 15108
確實沒有輸出。現在發(fā)現問題了,原來是進程已經不存在了,所以 pidstat 沒有任何輸出。繼續(xù)分析其他幾個高 CPU使用率的進程,發(fā)現亦如此。既然進程沒有了,那么性能問題應該已經沒有了吧。通過 top 命令確認下:
$ top
Tasks: 2019 total, 18 running, 2001 sleeping, 0 stopped, 0 zombie
Cpu(s): 60.5%us, 13.4%sy, 0.0%ni, 24.7%id, 0.4%wa, 0.0%hi, 1.1%si, 0.0%st
Mem: 98979248k total, 98118260k used, 860988k free, 1478272k buffers
Swap: 0k total, 0k used, 0k free, 62353676k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9129m 6.2g 13m S 260 6.5 160559:54 mongod
27114 root 20 0 929m 882m 580 R 63 0.9 0:01.94 supervisord
27145 root 20 0 929m 882m 600 R 60 0.9 0:01.87 supervisord
...
好像問題依舊存在,系統CPU使用率依舊很高。仔細查看 top 的輸出,發(fā)現 supervisord 進程的 pid 每次都有變化,現在已經變成了 27114。進程的 PID 號在變化,說明了什么?有兩點:
- 進程在不斷的重啟,比如因為段錯誤、配置錯誤等,進程退出后可能又被監(jiān)控系統重啟了。
- 這些進程都是短時進程。這些進程一般只運行很短時間結束,很難通過 top 發(fā)現。
supervisor 是一個進程管理程序,能夠監(jiān)控進程狀態(tài),異常退出時能自動重啟。目前看來,極有可能是第一種情況。通過 supervisor 來查看服務狀態(tài):
從 uptime 更新時間長短可以發(fā)現,很多服務在不斷地重啟。可能是服務配置或者代碼問題引起的。其實是批量的服務出現配置問題導致了服務啟動失敗,然后 supervisor 監(jiān)控到服務沒有成功啟動后又自動重啟。在解決完這些服務問題后,通過 top 再次看看性能情況:
$ top
Tasks: 2012 total, 7 running, 2002 sleeping, 0 stopped, 3 zombie
Cpu(s): 29.7%us, 5.4%sy, 0.0%ni, 63.5%id, 0.8%wa, 0.0%hi, 0.5%si, 0.0%st
Mem: 98979248k total, 96231348k used, 2747900k free, 1470892k buffers
Swap: 0k total, 0k used, 0k free, 60741708k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9131m 6.2g 13m S 80 6.6 160593:01 mongod
27545 user 20 0 41880 6636 2896 S 61 0.0 0:01.86 Python/ target=_blank class=infotextkey>Python
27549 root 20 0 929m 882m 616 R 38 0.9 0:01.15 supervisord
...
總結
在動手進行性能優(yōu)化前,需要考慮以下幾個問題:
- 如何判斷優(yōu)化是有效的呢?
- 有多個性能問題同時發(fā)生時,應該先優(yōu)化哪一個?
- 提升方法有多種時,應該選擇哪一種?
對于第一個問題,我們解決性能問題的目的,自然有一個性能提升的效果,即要對性能指標進行量化,需要確認優(yōu)化前指標、優(yōu)化后指標。當優(yōu)化后指標與優(yōu)化前指標有不同時,才能判定我們的優(yōu)化有沒有效果。比如上述例子中的系統CPU使用率優(yōu)化前后的變化。
對于第二個問題,有一個 “二八原則”,也就是 80% 的問題都是由 20% 的代碼導致的。因此,并不是所有的性能問題都值得優(yōu)化。在動手優(yōu)化前,往往需要把所有性能問題分析一遍,找出最重要的、可以最大程度提升性能的問題。這個過程會花費較多的時間,下面有兩個可以簡化這一過程的方法:
- 如果發(fā)現出現系統瓶頸問題,那么首先優(yōu)化的一定是系統資源問題。
- 針對不同類型的指標,首先要去優(yōu)化那些性能指標變化幅度最大的問題。
對于第三個問題,一般來說,應該選擇能最大程度提升性能的方法。但是需要注意的是,性能優(yōu)化并非沒有成本,很有可能你優(yōu)化了一個指標,另一個指標的性能就變差了。
參考
- https://time.geekbang.org/column/article/69618
- https://time.geekbang.org/column/article/69859
- https://time.geekbang.org/column/article/70476
- https://time.geekbang.org/column/article/72147
- http://www.lyyyuna.com/2020/05/29/perftest-analysis-cpu1/
- https://juejin.cn/post/6844903608371118094
- https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/top.html