Linux命令拾遺-軟體資源觀測
原創:打碼日記(微信公眾號ID:codelogs),歡迎分享,轉載請保留出處。
簡介
這是Linux命令拾遺系列的第三篇,本篇主要介紹Linux中觀測軟體資源的命令,如ps、netstat、lsof,以及檢視程序資訊的寶庫/proc目錄。
本系列文章索引
Linux命令拾遺-入門篇
Linux命令拾遺-文字處理篇
系統資源常分為兩類,一類是軟體資源,如程序、執行緒、檔案描述符、socket連線,偏開發視角,另一類是硬體資源,如CPU、記憶體、硬碟、網路,偏運維視角,而本章主要介紹Linux中與觀測軟體資源有關的命令。
ps檢視程序相關資訊
相信ps命令大家也比較熟悉,主要是用來列出程序的,基本用法如下:
# 列出所有程序,-e表示列出所有程序,-f表示列出程序詳細資訊
$ ps -ef
# 也是列出所有程序,這是從BSD系統中保留過來的用法
$ ps aux
# -C java指定列出java程序
$ ps -fC java
# 列出java程序的pid,cpu使用率,記憶體使用率,-o指定列出的欄位
$ ps -o pid,pcpu,pmem -C java
# 列出pid=328的程序,-p指定列出具體程序
$ ps -fp 328
# 列出前10個程序,cpu及記憶體倒序排序,--sort指定排序欄位
ps aux --sort -pcpu,-pmem | head -n 10
上面用法在網上是很常見的,下面這些就不太常見了,但也很有用,如下:
- 檢視程序啟動時間。
有時程序在非常頻繁的重啟,會導致系統CPU升高,可通過檢視程序啟動時間來推斷是否剛重啟了。
# 檢視各java程序啟動時間與已執行時長
$ ps -o lstart,etime -C java
STARTED ELAPSED
Fri Oct 22 20:33:31 2021 10:05
- 檢視程序的執行緒數量。
因為在Linux中,執行緒也叫做輕量級程序Light Weight Process(LWP),所以檢視LWP數量就是執行緒數量。
# 檢視各java程序的執行緒數量 $ ps -o pid,nlwp -C java PID NLWP 2121 21
- 檢視執行與阻塞狀態的執行緒數量
如果系統CPU使用率很高,我們可以通過top命令進一步找到是哪個程序佔用cpu高,但在某些特殊場景下,可能會發現系統負載很高,但CPU使用率不高,這是由於Linux系統負載綜合考察了系統中正在執行(R狀態)的執行緒數量以及阻塞於IO(D狀態)的執行緒數量,如果執行緒全阻塞於IO呼叫,就可能出現此現象。
這時可以嘗試用下面方法統計各程序執行與阻塞執行緒數量,以判斷是哪個程序導致了問題。
# 看程序執行及阻塞的執行緒數量之和,其中h表示不列印標題行,-L表示顯示執行緒而不是程序
$ ps h -eLo s,pid | grep ^[RD] |sort|uniq -c|sort -nrk1
/proc目錄
在Linux系統中,程序的資訊,都被虛擬到/proc
目錄中了,而ps命令不過是從這個目錄中讀取數量,並展示出來罷了。
而/proc/397/
就表示397這個程序相關資訊的起始目錄,如下:
$ ll /proc/397/
total 0
dr-xr-xr-x 2 work work 0 2021-10-17 21:41:53 attr
-r-------- 1 work work 0 2021-10-17 21:41:53 auxv
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 cgroup
-r--r--r-- 1 work work 0 2021-10-17 21:41:41 cmdline
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 comm
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 coredump_filter
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 cpuset
lrwxrwxrwx 1 work work 0 2021-10-17 21:41:53 cwd -> /home/work
-r-------- 1 work work 0 2021-10-17 21:41:53 environ
lrwxrwxrwx 1 work work 0 2021-10-17 21:41:53 exe -> /usr/bin/ncat
dr-x------ 2 work work 0 2021-10-17 21:41:53 fd
dr-x------ 2 work work 0 2021-10-17 21:41:53 fdinfo
-r-------- 1 work work 0 2021-10-17 21:41:53 io
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 limits
dr-x------ 2 work work 0 2021-10-17 21:41:53 map_files
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 maps
-rw------- 1 work work 0 2021-10-17 21:41:53 mem
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 mounts
dr-xr-xr-x 12 work work 0 2021-10-17 21:41:53 net
dr-x--x--x 2 work work 0 2021-10-17 21:41:53 ns
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 oom_adj
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 oom_score
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 oom_score_adj
lrwxrwxrwx 1 work work 0 2021-10-17 21:41:53 root -> /
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 sched
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 schedstat
-rw-r--r-- 1 work work 0 2021-10-17 21:41:53 setgroups
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 smaps
-r-------- 1 work work 0 2021-10-17 21:41:53 stack
-r--r--r-- 1 work work 0 2021-10-17 21:41:41 stat
-r--r--r-- 1 work work 0 2021-10-17 21:41:41 status
-r-------- 1 work work 0 2021-10-17 21:41:53 syscall
dr-xr-xr-x 3 work work 0 2021-10-17 21:41:53 task
-r--r--r-- 1 work work 0 2021-10-17 21:41:53 timers
可見內容是相當的多,我們來看看這裡面常見的項:
# cmdline儲存了程序啟動的命令列
$ cat /proc/397/cmdline|xargs -0
ncat -lk 8888
# cwd是個軟連結,指向了程序的工作目錄
$ readlink /proc/397/cwd
/home/work
# environ儲存了程序的環境變數
$ cat /proc/397/environ
# exe是個軟連結,指向了啟動程序的命令程式
$ readlink /proc/397/exe
/usr/bin/ncat
# status儲存程序一些基礎資訊
$ cat /proc/397/status
Name: ncat
Umask: 0022
State: S (sleeping)
Tgid: 397
Ngid: 0
Pid: 397
PPid: 31607
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
# /task目錄儲存程序的執行緒相關資訊
cat /proc/397/task/
# fd目錄裡面儲存著程序開啟的檔案描述符,可以用來檢視程序打開了什麼檔案,打開了多少網路連線
$ ll /proc/397/fd
total 0
lrwx------ 1 work work 64 2021-10-17 21:47:23 0 -> /dev/pts/3
l-wx------ 1 work work 64 2021-10-17 21:47:23 1 -> /home/work/app.log
lrwx------ 1 work work 64 2021-10-17 21:47:23 2 -> /dev/pts/3
lrwx------ 1 work work 64 2021-10-17 21:47:23 3 -> 'socket:[49436]'
lrwx------ 1 work work 64 2021-10-17 21:47:23 4 -> 'socket:[49437]'
# maps儲存了程序的記憶體段對映資訊,pmap命令取的就是這裡面的資料
# 如果你是C/C++系開發者,可能會相對熟悉一些
$ cat /proc/397/maps | head
5606c2808000-5606c280d000 r--p 00000000 08:10 11419 /usr/bin/ncat
5606c280d000-5606c282f000 r-xp 00005000 08:10 11419 /usr/bin/ncat
5606c282f000-5606c283d000 r--p 00027000 08:10 11419 /usr/bin/ncat
5606c283e000-5606c283f000 r--p 00035000 08:10 11419 /usr/bin/ncat
5606c283f000-5606c2840000 rw-p 00036000 08:10 11419 /usr/bin/ncat
5606c2840000-5606c2841000 rw-p 00000000 00:00 0
5606c397e000-5606c399f000 rw-p 00000000 00:00 0 [heap]
7f0c7a5ce000-7f0c7a5d0000 rw-p 00000000 00:00 0
7f0c7a5d0000-7f0c7a5df000 r--p 00000000 08:10 44447 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f0c7a5df000-7f0c7a686000 r-xp 0000f000 08:10 44447 /usr/lib/x86_64-linux-gnu/libm-2.31.so
# net目錄儲存著網路相關的資訊,如下/net/tcp儲存著程序的tcp連線資訊
$ cat /proc/397/net/tcp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 49437 1 0000000007f9a245 100 0 0 10 0
# sched儲存著程序被排程的一些資訊,如:
# se.nr_migrations執行緒遷移次數
# nr_voluntary_switches自願上下文切換次數
# nr_involuntary_switches非自願上下文切換次數
$ cat /proc/397/sched
ncat (397, #threads: 1)
-------------------------------------------------------------------
se.exec_start : 85694324.807968
se.vruntime : 333882.289243
se.sum_exec_runtime : 10.572689
se.nr_migrations : 0
nr_switches : 10
nr_voluntary_switches : 10
nr_involuntary_switches : 0
se.load.weight : 1048576
se.runnable_weight : 1048576
se.avg.load_sum : 42293
se.avg.runnable_load_sum : 42293
se.avg.util_sum : 26159838
se.avg.load_avg : 914
se.avg.runnable_load_avg : 914
se.avg.util_avg : 552
se.avg.last_update_time : 85694324807680
se.avg.util_est.ewma : 499
se.avg.util_est.enqueued : 553
policy : 0
prio : 120
clock-delta : 43
通過工作目錄找程序
有時候,多個相同的程式部署在同一臺機器上,比如Tomcat,但你只知道你們的Tomcat在/home/work/tomcat_order/目錄,這時可以查詢出各個tomcat程序的工作目錄(cwd),然後辨別哪個是自己的Tomcat,如下:
# 查詢各java程序的工作目錄,注:tomcat是java實現的
# 可以看到867程序的工作目錄是/home/work/tomcat_order/,那它就是我們要找的程序囉
$ pgrep java | xargs -i ls -l /proc/{}/cwd
lrwxrwxrwx 1 work work 0 2021-10-17 22:15:30 /proc/867/cwd -> /home/work/tomcat_order
lrwxrwxrwx 1 work work 0 2021-10-17 22:15:30 /proc/885/cwd -> /home/work/tomcat_goods
lrwxrwxrwx 1 work work 0 2021-10-17 22:15:42 /proc/906/cwd -> /home/work/tomcat_stock
找程序的日誌檔案
在排查問題時,經常需要查閱日誌檔案,如果你記不住相關程序的日誌檔案寫到什麼目錄了,就可以通過/proc/$pid/fd/
目錄來查詢,如下:
$ ll /proc/867/fd | grep .log$
l-wx------ 1 work work 64 2021-10-17 22:24:03 1 -> /home/work/app.log
netstat檢視網路連線
netstat是用來檢視網路連線資訊的工具命令,具體來說,像在程式語言中可以通過建立socket來建立網路連線,而netstat就是用來檢視這些socket資訊的,如下:
# 檢視所有的socket,-n代表不解析ip為主機名,-a表示all所有,-t代表tcp
$ netstat -nat
# 顯示各狀態socket數量,TIME_WAIT與CLOSE_WAIT數量太多,一般都不是好事情
$ netstat -nat | awk '/tcp/{print $6}'|sort|uniq -c
21 ESTABLISHED
3 TIME_WAIT
3 CLOSE_WAIT
2 LISTEN
# 檢視LISTEN狀態的socket,-l代表只顯示LISTEN狀態的
$ netstat -nlt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN
tcp6 0 0 :::8888 :::* LISTEN
# 檢視程序867的socket數量,-p顯示出建立網路連線的程序號
$ netstat -natp|grep -w 867 -c
2
# 找到監聽在8888埠的程序
$ netstat -nltp|grep -w 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 867/ncat
tcp6 0 0 :::8888 :::* LISTEN 867/ncat
lsof檢視開啟檔案
在Linux的設計哲學中,"一切皆檔案",像程序(/proc/$pid
)、網路連線(/proc/net/tcp
)、主機裝置(/dev
)等,Linux也都把它虛擬成了檔案,方便程式或shell讀取,就像上面的ps命令,如果你願意,通過讀取/proc目錄的資料,可以非常容易地實現自己的ps命令!
而lsof(list open files)命令,就是用來檢視系統或程序開啟的檔案的,所以這個命令非常的強大,它幾乎是觀測軟體資源最好用的工具了,如下:
# 檢視系統開啟的所有檔案(包括網路連線等)
$ lsof
# 檢視程序867開啟的檔案(包括網路連線等),-p指定具體程序號
$ lsof -p 867
# 類似上面介紹的,查詢程序的日誌檔案
$ lsof -p 867 | grep .log$
# 類似上面介紹的,檢視java程序的工作目錄, -c java表示過濾出java程序的檔案,-d cwd表示過濾出工作目錄
# 而-a表示-c與-d是AND關係(預設OR),即java程序的cwd檔案,就是工作目錄
$ lsof -a -c java -d cwd
ncat 867 work 1w REG 8,16 0 171403 /home/work/app.log
# 顯示程序867的tcp連線,-n不解析ip為主機名,-P不解析埠為服務名,-i TCP顯示TCP連線
$ lsof -a -nP -i TCP -p 867
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ncat 867 work 3u IPv6 48737 0t0 TCP *:8888 (LISTEN)
ncat 867 work 4u IPv4 48738 0t0 TCP *:8888 (LISTEN)
# 顯示8888號埠的連線
$ lsof -nP -i :8888
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ncat 867 work 3u IPv6 48737 0t0 TCP *:8888 (LISTEN)
ncat 867 work 4u IPv4 48738 0t0 TCP *:8888 (LISTEN)
# 顯示與172.16.12.5主機的連線
$ lsof -nP -i @172.16.12.5
# 通過指定目錄或檔案找程序
$ lsof /home/work/app.log
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ncat 867 work 1w REG 8,16 0 171403 /home/work/app.log
有時候,系統磁碟空間不足了,我們想刪掉一些大的日誌檔案來釋放磁碟空間,卻發現刪除後,磁碟剩餘空間沒有變多,這是因為有程序引用了這個檔案,可以如下確認:
# 找出已刪除但被程序引用的檔案
$ lsof | grep deleted
ncat 867 work 1w REG 8,16 0 171403 /home/work/app.log (deleted)
這時我們只需要重啟867這個程序,即可真正釋放空間。
注:可以這麼理解,刪除檔案只是去掉了檔案系統對這個檔案的引用,而如果程序之前還打開了這個檔案,那程序中保有的檔案描述符也是引用了這個檔案的,此時檔案還不能回收,因為回收可能會導致程序出錯,然而重啟程序後,對檔案的所有引用就都沒有了,檔案佔用的空間才能被真正回收。
實踐
1、陌生環境找服務日誌
當我們剛開始接手一些系統時,對系統的部署情況不太瞭解,比如訂單系統的資料庫地址是test.mycat.order.proxy:8566
,它是個mycat,部署在test.mycat.order.proxy
機器上,但運維在這臺機器上還部署了好幾個其它系統的mycat,那怎麼找到自己的mycat程序,並找到其gc日誌呢?
- 登入test.mycat.order.proxy,根據8566埠找程序
$ netstat -nltp|grep -w 8566
tcp 0 0 0.0.0.0:8566 0.0.0.0:* LISTEN 467313/haproxy
可見8566並不是mycat程序,而是一個網路代理haproxy,對於網路代理程序,它連出最多的埠,一般就是代理後面的服務提供的,所以我們需要再查一下haproxy建立了哪些連線。
- 查詢haproxy建立了哪些連線
# 注:467313是haproxy的程序號
$ netstat -natp|grep 467313
tcp 0 0 0.0.0.0:8566 0.0.0.0:* LISTEN 467313/haproxy
tcp 0 0 10.12.231.1:8566 10.39.12.32:42282 ESTABLISHED 467313/haproxy
tcp 0 0 10.12.231.1:8566 10.39.12.32:36638 ESTABLISHED 467313/haproxy
tcp 0 0 10.12.231.1:21103 10.12.231.1:8466 ESTABLISHED 467313/haproxy
tcp 0 0 10.12.231.1:31417 10.12.231.1:8466 ESTABLISHED 467313/haproxy
tcp 0 0 10.12.231.1:48432 10.12.231.1:8466 ESTABLISHED 467313/haproxy
tcp 0 0 10.12.231.1:59047 10.12.231.1:8466 ESTABLISHED 467313/haproxy
可見,haproxy程序連出最多的是8466埠。
- 再根據8466埠找程序
$ netstat -nltp|grep -w 8466
tcp 0 0 0.0.0.0:8466 0.0.0.0:* LISTEN 956758/java
找到了一個java程序,mycat就是java實現的,那這個估計就是我們的mycat程序了,如下確認一下:
- 檢視程序詳情
$ ps -fp 956758 | cat
UID PID PPID C STIME TTY TIME CMD
work 956758 956756 11 Oct15 ? 09:15:36 java -DMYCAT_HOME=. -server -XX:MaxPermSize=128M -XX:-UseGCOverheadLimit -XX:+AggressiveOpts -XX:MaxDirectMemorySize=4G -XX:+HeapDumpOnOutOfMemoryError ...
$ ll /proc/956758/cwd
lrwxrwxrwx 1 work work 0 Oct 15 16:22 /proc/956758/cwd -> /home/work/order/mycat
可見,它就是個mycat,且其工作目錄在/home/work/order/mycat
。
- 查詢其gc日誌
$ ll /proc/956758/fd | grep .log$
l-wx------ 1 work work 64 Oct 16 14:12 3 -> /home/work/order/mycat/logs/gc-2021-10-15_15-27-40.log
l-wx------ 1 work work 64 Oct 16 14:12 57 -> /home/work/order/mycat/logs/sql_time.log
l-wx------ 1 work work 64 Oct 16 14:12 58 -> /home/work/order/mycat/logs/mycat_auth.log
l-wx------ 1 work work 64 Oct 16 14:12 59 -> /home/work/order/mycat/logs/error_msg.log
l-wx------ 1 work work 64 Oct 16 14:12 60 -> /home/work/order/mycat/logs/mycat_prepare.log
l-wx------ 1 work work 64 Oct 16 14:12 61 -> /home/work/order/mycat/logs/sql_error_msg.log
這樣,我們就在自己本不熟悉的機器環境裡,找到了自己服務的gc日誌。
2、檔案描述符洩露
系統的檔案描述符資源是有限的,用完了必須歸還,不然就會發生洩露,對應在Java中的場景如下:
a. 通過fis=new FileInputStream("xxx")
開啟檔案,讀取完檔案後,必須呼叫fis.close()
關閉,不然FD(file descriptor)建立越來越多,導致oom。
b. jdbc中通過conn=DriverManager.getConnection()
獲取連線,操作完資料庫後,必須呼叫conn.close()
關閉,不然就會socket建立越來越多(socket也是一種檔案,也有FD),導致oom。
要想確認java服務中,是否存在那種忘記呼叫close的地方,只需要不斷監控程序的FD數量,如果它一直都在增長,基本就是發生了洩露,如下:
$ while true; do pgrep java | xargs -i ls -l /proc/{}/fd | wc -l; sleep 10; done | tee fd_num.log
12303
12292
12290