1. 程式人生 > >Linux 定位程序中CPU佔用高的執行緒

Linux 定位程序中CPU佔用高的執行緒


一、Top+pstack+gdb的組合拳

閒言少述,先直接上操作例項,再做原理講解。

1.1 用top命令找到最佔CPU的程序

top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
22688 root 20 0 1842m 136m 13m S 110.0 0.9 1568:44 test-program

1.2 使用pstack跟蹤程序棧
此命令可顯示每個程序的棧跟蹤。
pstack 命令必須由相應程序的屬主或 root 執行。可以使用 pstack 來確定程序掛起的位置。
此命令允許使用的唯一選項是要檢查的程序的 PID。

這個命令在排查程序問題時非常有用,
比如我們發現一個服務一直處於work狀態(如假死狀態,好似死迴圈),
使用這個命令就能輕鬆定位問題所在;
可以在一段時間內,多執行幾次pstack,若發現程式碼棧總是停在同一個位置,
那個位置就需要重點關注,很可能就是出問題的地方;

pstack 22688
Thread 44 (Thread 0x7fa97035f700 (LWP 22689)):
#0 0x00007fa96f386a00 in sem_wait () from /lib64/libpthread.so.0
#1 0x0000000000dfef12 in uv_sem_wait ()
#2 0x0000000000d67832 in node::DebugSignalThreadMain(void*) ()
#3 0x00007fa96f380aa1 in start_thread () from /lib64/libpthread.so.0
#4 0x00007fa96f0cdaad in clone () from /lib64/libc.so.6
Thread 43 (Thread 0x7fa96efe4700 (LWP 22690)):
#0 0x00007fa96f386a00 in sem_wait () from /lib64/libpthread.so.0
#1 0x0000000000e08a38 in v8::base::Semaphore::Wait() ()
#2 0x0000000000dddde9 in v8::platform::TaskQueue::GetNext() ()
#3 0x0000000000dddf3c in v8::platform::WorkerThread::Run() ()
#4 0x0000000000e099c0 in v8::base::ThreadEntry(void*) ()
#5 0x00007fa96f380aa1 in start_thread () from /lib64/libpthread.so.0
#6 0x00007fa96f0cdaad in clone () from /lib64/libc.so.6
Thread 42 (Thread 0x7fa96e5e3700 (LWP 22691)):
#0 0x00007fa96f386a00 in sem_wait () from /lib64/libpthread.so.0
#1 0x0000000000e08a38 in v8::base::Semaphore::Wait() ()
#2 0x0000000000dddde9 in v8::platform::TaskQueue::GetNext() ()
#3 0x0000000000dddf3c in v8::platform::WorkerThread::Run() ()
#4 0x0000000000e099c0 in v8::base::ThreadEntry(void*) ()
#5 0x00007fa96f380aa1 in start_thread () from /lib64/libpthread.so.0
#6 0x00007fa96f0cdaad in clone () from /lib64/libc.so.6
Thread 41 (Thread 0x7fa96dbe2700 (LWP 22692)):
#0 0x00007fa96f386a00 in sem_wait () from /lib64/libpthread.so.0
#1 0x0000000000e08a38 in v8::base::Semaphore::Wait() ()
#2 0x0000000000dddde9 in v8::platform::TaskQueue::GetNext() ()
#3 0x0000000000dddf3c in v8::platform::WorkerThread::Run() ()
#4 0x0000000000e099c0 in v8::base::ThreadEntry(void*) ()
#5 0x00007fa96f380aa1 in start_thread () from /lib64/libpthread.so.0
#6 0x00007fa96f0cdaad in clone () from /lib64/libc.so.6

使用top命令檢視指定程序最耗CPU的執行緒,
下面找到的執行緒號為 22970.

top -H -p 22688
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
22970 root 20 0 1842m 136m 13m R 100.2 0.9 1423:40 test-program

NOTE:
這裡的PID是系統給每個執行緒分配的唯一的執行緒號,不是程序號,但名稱也是PID。
這兩者的具體區別可見:
《linux中pid,tid, 以及 真實pid的關係》
http://blog.csdn.net/u012398613/article/details/52183708

使用執行緒號PID反查其對應的執行緒號。
如下就找到了 執行緒 22970對應的執行緒10

pstack 22688 | grep 22970
Thread 10 (Thread 0x7fa92f5fe700 (LWP 22970)):

使用VIM檢視程序快照,定位到具體的執行緒,並檢視其呼叫堆疊;

pstack 22688 | vim -
Thread 10 (Thread 0x7fa92f5fe700 (LWP 22970)):
#0 0x00007fa96f02a04f in vfprintf () from /lib64/libc.so.6
#1 0x00007fa96f054712 in vsnprintf () from /lib64/libc.so.6
#2 0x00007fa967b3861c in lv_write_log () from /opt/test-program
#3 0x00007fa967b26173 in LvJbuf::pjmedia_jbuf_put_rtp_pkg(pjmedia_rtp_decoded_pkg const*, int*) () from /opt/test-program
#4 0x00007fa96782409f in livesrv::LvAudio::on_rtp_stream(void*, unsigned int, unsigned int) () from /opt/test-program
#5 0x00007fa96781fc87 in livesrv::LvMedia::recv_media(void*, unsigned int, unsigned char, unsigned int) () from /opt/test-program
#6 0x00007fa967818c7f in livesrv::LvChannel::do_recv_media_check_thread2() () from /opt/test-program/node_modules/livesource/Debug/linux/livesource.node
#7 0x00007fa967814699 in recv_media_process2(void*) () from /opt/test-program
#8 0x00007fa96f380aa1 in start_thread () from /lib64/libpthread.so.0
#9 0x00007fa96f0cdaad in clone () from /lib64/libc.so.6

上面的操作基本定位到了具體執行緒和大概的函式,
如果想檢視具體的原因,如現場的函式中變數等的數值等,就要使用的GDB的實時除錯功能。
1.3 使用gdb除錯實時程序

gdb attach 22688
:thread 10
:bt
:frame x
:p xxx



二、top用法

2.1 top:動態觀察程式的變化
[[email protected] ~]# top [-d] | top [-bnp]
引數:
-d :後面可以接秒數,就是整個程式畫面更新的秒數。預設是 5 秒;
-b :以批次的方式執行 top ,還有更多的引數可以使用喔!
通常會搭配資料流重導向來將批次的結果輸出成為檔案。
-n :與 -b 搭配,意義是,需要進行幾次 top 的輸出結果。
-p :指定某些個 PID 來進行觀察監測而已。

在 top 執行過程當中可以使用的按鍵指令:
:顯示在 top 當中可以輸入的按鍵指令;
P :以 CPU 的使用資源排序顯示;
M :以 Memory 的使用資源排序顯示;
N :以 PID 來排序喔!
T :由該 Process 使用的 CPU 時間累積 (TIME+) 排序。
k :給予某個 PID 一個訊號 (signal)
r :給予某個 PID 重新制訂一個 nice 值。

2.2 top 也是個挺不錯的程式觀察工具!
不同於 ps 是靜態的結果輸出, top 這個程式可以持續的監測 (monitor) 整個系統的程式工作狀態,
例如上面的範例一所示啊! 在預設的情況下,每次更新程式資源的時間為 5 秒,
不過,可以使用 -d 來進行修改。
top 主要分為兩個畫面,上面的畫面為整個系統的資源使用狀態,基本上總共有六行, 顯示的內容依序是:
第一行:顯示系統已啟動的時間、目前上線人數、系統整體的負載(load)。
比較需要注意的是系統的負載,三個資料分別代表 1, 5, 10 分鐘的平均負載。
一般來說,這個負載值應該不太可能超過 1 才對,除非您的系統很忙碌。
如果持續高於 5 的話,那麼…仔細的看看到底是那個程式在影響整體系統吧!
第二行:顯示的是目前的觀察程式數量,
比較需要注意的是最後的 zombie 那個數值,如果不是 0 ,
嘿嘿!好好看看到底是那個 process 變成疆屍了吧?!
第三行:顯示的是 CPU 的整體負載,每個專案可使用 ? 查閱。
需要觀察的是 id (idle) 的數值,一般來說,他應該要接近 100% 才好,表示系統很少資源被使用啊! _。
第四行與第五行:表示目前的實體記憶體與虛擬記憶體 (Mem/Swap) 的使用情況。
第六行:這個是當在 top 程式當中輸入指令時,顯示狀態的地方。 例如範例四就是一個簡單的使用例子。

至於 top 底下的畫面,則是每個 process 使用的資源情況。比較需要注意的是:
PID :每個 process 的 ID 啦!
USER :該 process 所屬的使用者;
PR :Priority 的簡寫,程式的優先執行順序,越小越早被執行;
NI :Nice 的簡寫,與 Priority 有關,也是越小越早被執行;
%CPU :CPU 的使用率;
%MEM :記憶體的使用率;
TIME+ :CPU 使用時間的累加;
一般來說,如果鳥哥想要找出最損耗 CPU 資源的那個程式時,大多使用的就是 top 這支程式啦!
然後強制以 CPU 使用資源來排序 (在 top 當中按下 P 即可), 就可以很快的知道啦! _。

 

三、pstack用法

此命令可顯示每個程序的棧跟蹤。
pstack 命令必須由相應程序的屬主或 root 執行。可以使用 pstack 來確定程序掛起的位置。
此命令允許使用的唯一選項是要檢查的程序的 PID。請參見 proc(1) 手冊頁。

這個命令在排查程序問題時非常有用,比如我們發現一個服務一直處於work狀態(如假死狀態,好似死迴圈),
使用這個命令就能輕鬆定位問題所在;
可以在一段時間內,多執行幾次pstack,
若發現程式碼棧總是停在同一個位置,那個位置就需要重點關注,很可能就是出問題的地方;

示例:檢視bash程式程序棧:
/opt/app/tdev1$ps -fe| grep bash
tdev1 7013 7012 0 19:42 pts/1 00:00:00 -bash
tdev1 11402 11401 0 20:31 pts/2 00:00:00 -bash
tdev1 11474 11402 0 20:32 pts/2 00:00:00 grep bash

/opt/app/tdev1$pstack 7013
#0 0x00000039958c5620 in __read_nocancel () from /lib64/libc.so.6
#1 0x000000000047dafe in rl_getc ()
#2 0x000000000047def6 in rl_read_key ()
#3 0x000000000046d0f5 in readline_internal_char ()
#4 0x000000000046d4e5 in readline ()
#5 0x00000000004213cf in ?? ()
#6 0x000000000041d685 in ?? ()
#7 0x000000000041e89e in ?? ()
#8 0x00000000004218dc in yyparse ()
#9 0x000000000041b507 in parse_command ()
#10 0x000000000041b5c6 in read_command ()
#11 0x000000000041b74e in reader_loop ()
#12 0x000000000041b2aa in main ()

 

四、GDB除錯執行中程式的方法

4.1 多執行緒除錯
多執行緒除錯最重要就是下面幾個命令:

  1. 檢視當前程序的執行緒。
    info thread

  2. 切換除錯的執行緒為指定ID的執行緒。
    thread

  3. 在file.c檔案第100行處為所有經過這裡的執行緒設定斷點。
    break file.c:100 thread all

  4. 執行緒開關
    在使用step或者continue命令除錯當前被除錯執行緒的時候,其他執行緒也是同時執行的,
    怎麼只讓被除錯程式執行呢?通過這個命令就可以實現這個需求。
    set scheduler-locking off|on|step,
    . off 不鎖定任何執行緒,也就是所有執行緒都執行,這是預設值。
    . on 只有當前被除錯程式會執行。
    . step 在單步的時候,除了next過一個函式的情況
    (熟悉情況的人可能知道,這其實是一個設定斷點然後continue的行為)以外,
    只有當前執行緒會執行。

4.2 除錯巨集
在GDB下,我們無法print巨集定義,因為巨集是預編譯的。
但是我們還是有辦法來除錯巨集,這個需要GCC的配合。
在GCC編譯程式的時候,加上-ggdb3引數,這樣,你就可以除錯巨集了。

另外,你可以使用下述的GDB的巨集除錯命令 來檢視相關的巨集。
info macro – 你可以檢視這個巨集在哪些檔案裡被引用了,以及巨集定義是什麼樣的。
macro – 你可以檢視巨集展開的樣子。
1、首先獲得程式的PID
ps -ef | grep xxxxx

2、進入除錯程式
gdb attach PID

3、gcore命令生成CORE檔案
4、程序資訊可以用info proc顯示
5、暫存器資訊可以用info reg顯示

 

轉自: https://blog.csdn.net/hhq163/article/details/82787815