strace常用來跟蹤進程執行時的系統調用和所接收的信號。 在Linux世界,進程不能直接訪問硬件設備,當進程需要訪問硬件設備(比如讀取磁盤文件,接收網絡數據等等)時,必須由用戶態模式切換至內核態模式,通過系統調用訪問硬件設備。strace可以跟蹤到一個進程產生的系統調用,包括參數,返回值,執行消耗的時間。
1、參數
每一行都是一條系統調用,等號左邊是系統調用的函數名及其參數,右邊是該調用的返回值。 strace 顯示這些調用的參數並返回符號形式的值。strace 從內核接收信息,而且不需要以任何特殊的方式來構建內核。
$strace cat /dev/null
execve("/bin/cat", ["cat", "/dev/null"], [/* 22 vars */]) = 0
brk(0) = 0xab1000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
...
參數含義
-c 統計每一系統調用的所執行的時間,次數和出錯的次數等. -d 輸出strace關於標准錯誤的調試信息. -f 跟蹤由fork調用所產生的子進程. -ff 如果提供-o filename,則所有進程的跟蹤結果輸出到相應的filename.pid中,pid是各進程的進程號. -F 嘗試跟蹤vfork調用.在-f時,vfork不被跟蹤. -h 輸出簡要的幫助信息. -i 輸出系統調用的入口指針. -q 禁止輸出關於脫離的消息. -r 打印出相對時間關於,,每一個系統調用. -t 在輸出中的每一行前加上時間信息. -tt 在輸出中的每一行前加上時間信息,微秒級. -ttt 微秒級輸出,以秒了表示時間. -T 顯示每一調用所耗的時間. -v 輸出所有的系統調用.一些調用關於環境變量,狀態,輸入輸出等調用由於使用頻繁,默認不輸出. -V 輸出strace的版本信息. -x 以十六進制形式輸出非標准字符串 -xx 所有字符串以十六進制形式輸出. -a column 設置返回值的輸出位置.默認 為40. -e expr 指定一個表達式,用來控制如何跟蹤.格式如下: [qualifier=][!]value1[,value2]... qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用來限定的符號或數字.默認的 qualifier是 trace.感嘆號是否定符號.例如: -eopen等價於 -e trace=open,表示只跟蹤open調用.而-etrace!=open表示跟蹤除了open以外的其他調用.有兩個特殊的符號 all 和 none. 注意有些shell使用!來執行歷史記錄里的命令,所以要使用\\. -e trace=set 只跟蹤指定的系統 調用.例如:-e trace=open,close,rean,write表示只跟蹤這四個系統調用.默認的為set=all. -e trace=file 只跟蹤有關文件操作的系統調用. -e trace=process 只跟蹤有關進程控制的系統調用. -e trace=network 跟蹤與網絡有關的所有系統調用. -e strace=signal 跟蹤所有與系統信號有關的 系統調用 -e trace=ipc 跟蹤所有與進程通訊有關的系統調用 -e abbrev=set 設定 strace輸出的系統調用的結果集.-v 等與 abbrev=none.默認為abbrev=all. -e raw=set 將指 定的系統調用的參數以十六進制顯示. -e signal=set 指定跟蹤的系統信號.默認為all.如 signal=!SIGIO(或者signal=!io),表示不跟蹤SIGIO信號. -e read=set 輸出從指定文件中讀出 的數據.例如: -e read=3,5 -e write=set 輸出寫入到指定文件中的數據. -o filename 將strace的輸出寫入文件filename -p pid 跟蹤指定的進程pid. -s strsize 指定輸出的字符串的最大長度.默認為32.文件名一直全部輸出. -u username 以username 的UID和GID執行被跟蹤的命令
2、使用實例
實例1:跟蹤可執行程序
strace -f -F -o ~/straceout.txt myserver
-f -F選項告訴strace同時跟蹤fork和vfork出來的進程,-o選項把所有strace輸出寫到~/straceout.txt里 面,myserver是要啟動和調試的程序。
實例2:跟蹤服務程序
strace -o output.txt -T -tt -e trace=all -p 28979
跟蹤28979進程的所有系統調用(-e trace=all),並統計系統調用的花費時間,以及開始時間(並以可視化的時分秒格式顯示),最后將記錄結果存在output.txt文件里面。
實例3:轉自手把手教你用Strace診斷問題
早些年,如果你知道有個 strace 命令,就很牛了,而現在大家基本都知道 strace 了,如果你遇到性能問題求助別人,十有八九會建議你用 strace 掛上去看看,不過當你掛上去了,看着滿屏翻滾的字符,卻十有八九看不出個所以然。本文通過一個簡單的案例,向你展示一下在用 strace 診斷問題時的一些套路。
如下真實案例,如有雷同,實屬必然!讓我們看一台高負載服務器的 top 結果:
技巧:運行 top 時,按「1」打開 CPU 列表,按「shift+p」以 CPU 排序。
在本例中大家很容易發現 CPU 主要是被若干個 PHP 進程占用了,同時 PHP 進程占用的比較多的內存,不過系統內存尚有結余,SWAP 也不嚴重,這並不是問題主因。
不過在 CPU 列表中能看到 CPU 主要消耗在內核態「sy」,而不是用戶態「us」,和我們的經驗不符。Linux 操作系統有很多用來跟蹤程序行為的工具,內核態的函數調用跟蹤用「strace」,用戶態的函數調用跟蹤用「ltrace」,所以這里我們應該用「strace」:
shell> strace -p <PID>
不過如果直接用 strace 跟蹤某個進程的話,那么等待你的往往是滿屏翻滾的字符,想從這里看出問題的症結並不是一件容易的事情,好在 strace 可以按操作匯總時間:
shell> strace -cp <PID>
通過「c」選項用來匯總各個操作的總耗時,運行后的結果大概如下圖所示:
很明顯,我們能看到 CPU 主要被 clone 操作消耗了,還可以單獨跟蹤一下 clone:
shell> strace -T -e clone -p <PID>
通過「T」選項可以獲取操作實際消耗的時間,通過「e」選項可以跟蹤某個操作:
很明顯,一個 clone 操作需要幾百毫秒,至於 clone 的含義,參考 man 文檔:
clone() creates a new process, in a manner similar to fork(2). It is actually a library function layered on top of the underlying clone() system call, hereinafter referred to as sys_clone. A description of sys_clone is given towards the end of this page.
Unlike fork(2), these calls allow the child process to share parts of its execution context with the calling process, such as the memory space, the table of file descriptors, and the table of signal handlers. (Note that on this manual page, “calling process” normally corresponds to “parent process”. But see the description of CLONE_PARENT below.)
簡單來說,就是創建一個新進程。那么在 PHP 里什么時候會出現此類系統調用呢?查詢業務代碼看到了 exec 函數,通過如下命令驗證它確實會導致 clone 系統調用:
shell> strace -eclone php -r 'exec("ls");'
最后再考大家一個題:如果我們用 strace 跟蹤一個進程,輸出結果很少,是不是說明進程很空閑?其實試試 ltrace,可能會發現別有洞天。記住有內核態和用戶態之分。
實例4 :轉自 http://huoding.com/2013/10/06/288
服務器卻頻現高負載,Nginx出現錯誤日志:
- connect() failed (110: Connection timed out) while connecting to upstream
- connect() failed (111: Connection refused) while connecting to upstream
看上去是Upstream出了問題,在本例中Upstream就是PHP(版本:5.2.5)。可惜監控不完善,我搞不清楚到底是哪出了問題,無奈之下只好不斷重啟PHP來緩解故障。
如果每次都手動重啟服務無疑是個苦差事,幸運的是可以通過CRON設置每分鍾執行:
#/bin/bash LOAD=$(awk '{print $1}' /proc/loadavg) if [ $(echo "$LOAD > 100" | bc) = 1 ]; then /etc/init.d/php-fpm restart fi
可惜這只是一個權宜之計,要想徹底解決就必須找出故障的真正原因是什么。
閑言碎語不要講,輪到Strace出場了,統計一下各個系統調用的耗時情況:
shell> strace -c -p $(pgrep -n php-cgi)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
30.53 0.023554 132 179 brk
14.71 0.011350 140 81 mlock
12.70 0.009798 15 658 16 recvfrom
8.96 0.006910 7 927 read
6.61 0.005097 43 119 accept
5.57 0.004294 4 977 poll
3.13 0.002415 7 359 write
2.82 0.002177 7 311 sendto
2.64 0.002033 2 1201 1 stat
2.27 0.001750 1 2312 gettimeofday
2.11 0.001626 1 1428 rt_sigaction
1.55 0.001199 2 730 fstat
1.29 0.000998 10 100 100 connect
1.03 0.000792 4 178 shutdown
1.00 0.000773 2 492 open
0.93 0.000720 1 711 close
0.49 0.000381 2 238 chdir
0.35 0.000271 3 87 select
0.29 0.000224 1 357 setitimer
0.21 0.000159 2 81 munlock
0.17 0.000133 2 88 getsockopt
0.14 0.000110 1 149 lseek
0.14 0.000106 1 121 mmap
0.11 0.000086 1 121 munmap
0.09 0.000072 0 238 rt_sigprocmask
0.08 0.000063 4 17 lstat
0.07 0.000054 0 313 uname
0.00 0.000000 0 15 1 access
0.00 0.000000 0 100 socket
0.00 0.000000 0 101 setsockopt
0.00 0.000000 0 277 fcntl
------ ----------- ----------- --------- --------- ----------------
100.00 0.077145 13066 118 total
看上去「brk」非常可疑,它竟然耗費了三成的時間,保險起見,單獨確認一下:
shell> strace -T -e brk -p $(pgrep -n php-cgi)
brk(0x1f18000) = 0x1f18000 <0.024025>
brk(0x1f58000) = 0x1f58000 <0.015503>
brk(0x1f98000) = 0x1f98000 <0.013037>
brk(0x1fd8000) = 0x1fd8000 <0.000056>
brk(0x2018000) = 0x2018000 <0.012635>
說明:在Strace中和操作花費時間相關的選項有兩個,分別是「-r」和「-T」,它們的差別是「-r」表示相對時間,而「-T」表示絕對時間。簡單統計可以用「-r」,但是需要注意的是在多任務背景下,CPU隨時可能會被切換出去做別的事情,所以相對時間不一定准確,此時最好使用「-T」,在行尾可以看到操作時間,可以發現確實很慢。
在繼續定位故障原因前,我們先通過「man brk」來查詢一下它的含義:
brk() sets the end of the data segment to the value specified by end_data_segment, when that value is reasonable, the system does have enough memory and the process does not exceed its max data size (see setrlimit(2)).
簡單點說就是內存不夠用時通過它來申請新內存(data segment),可是為什么呢?
shell> strace -T -p $(pgrep -n php-cgi) 2>&1 | grep -B 10 brk stat("/path/to/script.php", {...}) = 0 <0.000064> brk(0x1d9a000) = 0x1d9a000 <0.000067> brk(0x1dda000) = 0x1dda000 <0.001134> brk(0x1e1a000) = 0x1e1a000 <0.000065> brk(0x1e5a000) = 0x1e5a000 <0.012396> brk(0x1e9a000) = 0x1e9a000 <0.000092>
通過「grep」我們很方便就能獲取相關的上下文,反復運行幾次,發現每當請求某些PHP腳本時,就會出現若干條耗時的「brk」,而且這些PHP腳本有一個共同的特點,就是非常大,甚至有幾百K,為何會出現這么大的PHP腳本?實際上是程序員為了避免數據庫操作,把非常龐大的數組變量通過「var_export」持久化到PHP文件中,然后在程序中通過「include」來獲取相應的變量,因為變量太大,所以PHP不得不頻繁執行「brk」,不幸的是在本例的環境中,此操作比較慢,從而導致處理請求的時間過長,加之PHP進程數有限,於是乎在Nginx上造成請求擁堵,最終導致高負載故障。
下面需要驗證一下推斷似乎否正確,首先查詢一下有哪些地方涉及問題腳本:
shell> find /path -name "*.php" | xargs grep "script.php"
直接把它們都禁用了,看看服務器是否能緩過來,或許大家覺得這太魯蒙了,但是特殊情況必須做出特殊的決定,不能像個娘們兒似的優柔寡斷,沒過多久,服務器負載恢復正常,接着再統計一下系統調用的耗時:
shell> strace -c -p $(pgrep -n php-cgi)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
24.50 0.001521 11 138 2 recvfrom
16.11 0.001000 33 30 accept
7.86 0.000488 8 59 sendto
7.35 0.000456 1 360 rt_sigaction
6.73 0.000418 2 198 poll
5.72 0.000355 1 285 stat
4.54 0.000282 0 573 gettimeofday
4.41 0.000274 7 42 shutdown
4.40 0.000273 2 137 open
3.72 0.000231 1 197 fstat
2.93 0.000182 1 187 close
2.56 0.000159 2 90 setitimer
2.13 0.000132 1 244 read
1.71 0.000106 4 30 munmap
1.16 0.000072 1 60 chdir
1.13 0.000070 4 18 setsockopt
1.05 0.000065 1 100 write
1.05 0.000065 1 64 lseek
0.95 0.000059 1 75 uname
0.00 0.000000 0 30 mmap
0.00 0.000000 0 60 rt_sigprocmask
0.00 0.000000 0 3 2 access
0.00 0.000000 0 9 select
0.00 0.000000 0 20 socket
0.00 0.000000 0 20 20 connect
0.00 0.000000 0 18 getsockopt
0.00 0.000000 0 54 fcntl
0.00 0.000000 0 9 mlock
0.00 0.000000 0 9 munlock
------ ----------- ----------- --------- --------- ----------------
100.00 0.006208 3119 24 total
顯而易見,「brk」已經不見了,取而代之的是「recvfrom」和「accept」,不過這些操作本來就是很耗時的,所以可以定位「brk」就是故障的原因。