1. 程式人生 > 其它 >Linux命令拾遺-軟體資源觀測

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

上面用法在網上是很常見的,下面這些就不太常見了,但也很有用,如下:

  1. 檢視程序啟動時間。
    有時程序在非常頻繁的重啟,會導致系統CPU升高,可通過檢視程序啟動時間來推斷是否剛重啟了。
# 檢視各java程序啟動時間與已執行時長
$ ps -o lstart,etime -C java
                 STARTED     ELAPSED
Fri Oct 22 20:33:31 2021       10:05
  1. 檢視程序的執行緒數量。
    因為在Linux中,執行緒也叫做輕量級程序Light Weight Process(LWP),所以檢視LWP數量就是執行緒數量。
# 檢視各java程序的執行緒數量
$ ps -o pid,nlwp -C java
  PID NLWP
 2121   21
  1. 檢視執行與阻塞狀態的執行緒數量
    如果系統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日誌呢?

  1. 登入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建立了哪些連線。

  1. 查詢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埠。

  1. 再根據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程序了,如下確認一下:

  1. 檢視程序詳情
$ 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

  1. 查詢其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

往期內容

Linux命令拾遺-入門篇
原來awk真是神器啊
Linux文字命令技巧(上)
Linux文字命令技巧(下)
字元編碼解惑