分析運(yùn)行中的 Python 進(jìn)程詳細(xì)解析
在 Java 中打印當(dāng)前線程的方法棧,可以用 kill -3 命令向 JVM 發(fā)送一個 OS 信號,JVM 捕捉以后會自動 dump 出來;當(dāng)然,也可以直接使用 jstack 工具完成,這些方法好幾年前我在這篇性能分析的文章 中介紹過。這樣的需求可以說很常見,比如定位死鎖,定位一個不工作的線程到底卡在哪里,或者定位為什么 CPU 居高不下等等問題。
現(xiàn)在工作中我用的是 Python,需要線上問題定位的緣故,也有了類似的需求——想要知道當(dāng)前的 Python 進(jìn)程“在干什么”。但是沒有了 JVM 的加持,原有的命令或者工具都不再適用。傳統(tǒng)的 gdb 的 debug 大法在線上也不好操作。于是我尋找了一些別的方法,來幫助定位問題,我把它們記錄在這里。
signal
在代碼中,我們可以使用 signal 為進(jìn)程預(yù)先注冊一個信號接收器,在進(jìn)程接收到特定信號的時候,可以打印方法棧:
import traceback, signal class Debugger(): def __init__(self, logger): self._logger = logger def log_stack_trace(self, sig, frame): d={'_frame':frame} d.update(frame.f_globals) d.update(frame.f_locals) messages = "Signal received. Stack trace:\n" messages += ''.join(traceback.format_stack(frame)) self._logger.warn(messages) def listen(self): signal.signal(signal.SIGUSR1, self.log_stack_trace)
通過調(diào)用上面的 listen 方法(比如 new Debug(logger).listen()),就將一個可以接收 SIGUSR1 并打印方法棧的接收器注冊到當(dāng)前進(jìn)程了。這里是打印方法棧,但是實(shí)際上可以做任何事,因?yàn)榉椒▓?zhí)行的當(dāng)前,上下文已經(jīng)跑到進(jìn)程里面了。
那么怎么向進(jìn)程發(fā)送信號呢?和 JVM 的方法類似,可以通過操作系統(tǒng)命令來發(fā)送:
kill -30 pid
這里的信號為什么是 30?這是因?yàn)?SIGUSR1 被當(dāng)前操作系統(tǒng)定義成 30(請注意不同的操作系統(tǒng)這個映射表是可能不同的),這點(diǎn)可以通過 man signal 查看:
No Name Default Action Description SIGHUP terminate process terminal line hangup SIGINT terminate process interrupt program SIGQUIT create core image quit program SIGILL create core image illegal instruction SIGTRAP create core image trace trap SIGABRT create core image abort program (formerly SIGIOT) SIGEMT create core image emulate instruction executed SIGFPE create core image floating-point exception SIGKILL terminate process kill program SIGBUS create core image bus error SIGSEGV create core image segmentation violation SIGSYS create core image non-existent system call invoked SIGPIPE terminate process write on a pipe with no reader SIGALRM terminate process real-time timer expired SIGTERM terminate process software termination signal SIGURG discard signal urgent condition present on socket SIGSTOP stop process stop (cannot be caught or ignored) SIGTSTP stop process stop signal generated from keyboard SIGCONT discard signal continue after stop SIGCHLD discard signal child status has changed SIGTTIN stop process background read attempted from control terminal SIGTTOU stop process background write attempted to control terminal SIGIO discard signal I/O is possible on a descriptor (see fcntl(2)) SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2)) SIGXFSZ terminate process file size limit exceeded (see setrlimit(2)) SIGVTALRM terminate process virtual time alarm (see setitimer(2)) SIGPROF terminate process profiling timer alarm (see setitimer(2)) SIGWINCH discard signal Window size change SIGINFO discard signal status request from keyboard SIGUSR1 terminate process User defined signal 1 SIGUSR2 terminate process User defined signal 2
當(dāng)然,也可以寫一點(diǎn)點(diǎn) python 腳本來發(fā)送這個信號:
import os, signal os.kill($PID, signal.SIGUSR1)
原理是一樣的。
strace
如果進(jìn)程已經(jīng)無響應(yīng)了,或者上面的信號接收器沒有注冊,那么就要考慮別的方法來或者“進(jìn)程在干什么”這件事情了。其中,一個有用的命令是 strace:
strace -p pid
比如,我自己寫了一個測試腳本 t.py,使用 python 執(zhí)行,然后調(diào)用 sleep,再給它發(fā)送一個 SIGUSR1 的消息,它打印方法棧并退出。這整個過程,我使用 strace 可以得到這樣的結(jié)果:
strace -p 9157 strace: Process 9157 attached select(0, NULL, NULL, NULL, {9999943, 62231}) = ? ERESTARTNOHAND (To be restarted if no handler) --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=9273, si_uid=9007} --- rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call) stat("t.py", {st_mode=S_IFREG|0644, st_size=1281, ...}) = 0 open("t.py", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=1281, ...}) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=1281, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f631e866000 read(3, "import traceback, signal, time\n "..., 8192) = 1281 read(3, "", 4096) = 0 close(3) = 0 munmap(0x7f631e866000, 4096) = 0 stat("t.py", {st_mode=S_IFREG|0644, st_size=1281, ...}) = 0 write(1, "Signal received. Stack trace:\n "..., 134) = 134 write(1, "\n", 1) = 1 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f631e06f5d0}, {0x7f631e392680, [], SA_RESTORER, 0x7f631e06f5d0}, 8) = 0 rt_sigaction(SIGUSR1, {SIG_DFL, [], SA_RESTORER, 0x7f631e06f5d0}, {0x7f631e392680, [], SA_RESTORER, 0x7f631e06f5d0}, 8) = 0 exit_group(0) = ? +++ exited with 0 +++
可以看到從 strace attached 開始,到進(jìn)程退出,所有重要的調(diào)用都被打印出來了。
在 iOS 下,沒有 strace,但是可以使用類似的(更好的)命令 dtruss。
lsof
lsof 可以打印某進(jìn)程打開的文件,而 Linux 下面一切都是文件,因此查看打開的文件列表有時可以獲取很多額外的信息。比如,打開前面提到的這個測試進(jìn)程:
lsof -p 16872 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME Python 16872 xxx cwd DIR 1,5 2688 1113586 /Users/xxx Python 16872 xxx txt REG 1,5 51744 10627527 /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python Python 16872 xxx txt REG 1,5 52768 10631046 /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/_locale.so Python 16872 xxx txt REG 1,5 65952 10631134 /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/time.so Python 16872 xxx txt REG 1,5 841440 10690598 /usr/lib/dyld Python 16872 xxx txt REG 1,5 1170079744 10705794 /private/var/db/dyld/dyld_shared_cache_x86_64h Python 16872 xxx 0u CHR 16,2 0t39990 649 /dev/ttys002 Python 16872 xxx 1u CHR 16,2 0t39990 649 /dev/ttys002 Python 16872 xxx 2u CHR 16,2 0t39990 649 /dev/ttys002
它有幾個參數(shù)很常用,比如-i,用來指定網(wǎng)絡(luò)文件(如果是“-i: 端口號”這樣的形式還可以指定端口)。
總結(jié)
以上所述是小編給大家介紹的分析運(yùn)行中的 Python 進(jìn)程,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大家的!
相關(guān)文章
Python讀取文件內(nèi)容為字符串的方法(多種方法詳解)
這篇文章主要介紹了Python讀取文件內(nèi)容為字符串的方法,本文通過三種方式給大家介紹,在文章末尾給大家提到了python讀取txt文件中字符串,字符串用空格分隔的相關(guān)知識,需要的朋友可以參考下2020-03-03Python?Flask中Cookie和Session區(qū)別詳解
Flask是一個使用?Python?編寫的輕量級?Web?應(yīng)用框架。其?WSGI?工具箱采用?Werkzeug?,模板引擎則使用?Jinja2?。Flask使用?BSD?授權(quán)。Flask也被稱為?“microframework”?,因?yàn)樗褂煤唵蔚暮诵?,?extension?增加其他功能,F(xiàn)lask中Cookie和Session有什么區(qū)別呢2022-07-07selenium+python實(shí)現(xiàn)1688網(wǎng)站驗(yàn)證碼圖片的截取功能
這篇文章主要介紹了selenium+python實(shí)現(xiàn)1688網(wǎng)站驗(yàn)證碼圖片的截取,需要的朋友可以參考下2018-08-08Python中用altzone()方法處理時區(qū)的教程
這篇文章主要介紹了Python中用altzone()方法處理時區(qū)的教程,是Python入門中的基礎(chǔ)知識,需要的朋友可以參考下2015-05-05python中array數(shù)組添加一行或一列數(shù)據(jù)的具體實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于python中array數(shù)組添加一行或一列數(shù)據(jù)的具體實(shí)現(xiàn),最近經(jīng)常使用到數(shù)組方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09Python 判斷 有向圖 是否有環(huán)的實(shí)例講解
下面小編就為大家分享一篇Python 判斷 有向圖 是否有環(huán)的實(shí)例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02python3中http協(xié)議提供文件服務(wù)器功能詳解
http協(xié)議是互聯(lián)網(wǎng)的通用基礎(chǔ)協(xié)議,也可以利用其來開發(fā)文件服務(wù)器,給客戶提供文件瀏覽,查看,下載,上傳等功能,這篇文章主要介紹了python3中http協(xié)議提供文件服務(wù)器功能,需要的朋友可以參考下2023-06-06Python實(shí)現(xiàn)多線程下載文件的代碼實(shí)例
這篇文章主要介紹了Python實(shí)現(xiàn)多線程下載文件的代碼實(shí)例,需要的朋友可以參考下2014-06-06