分析运行中的 Python 进程详细解析


Posted in Python onJune 22, 2019

在 Java 中打印当前线程的方法栈,可以用 kill -3 命令向 JVM 发送一个 OS 信号,JVM 捕捉以后会自动 dump 出来;当然,也可以直接使用 jstack 工具完成,这些方法好几年前我在这篇性能分析的文章 中介绍过。这样的需求可以说很常见,比如定位死锁,定位一个不工作的线程到底卡在哪里,或者定位为什么 CPU 居高不下等等问题。

现在工作中我用的是 Python,需要线上问题定位的缘故,也有了类似的需求——想要知道当前的 Python 进程“在干什么”。但是没有了 JVM 的加持,原有的命令或者工具都不再适用。传统的 gdb 的 debug 大法在线上也不好操作。于是我寻找了一些别的方法,来帮助定位问题,我把它们记录在这里。

signal

在代码中,我们可以使用 signal 为进程预先注册一个信号接收器,在进程接收到特定信号的时候,可以打印方法栈:

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)

通过调用上面的 listen 方法(比如 new Debug(logger).listen()),就将一个可以接收 SIGUSR1 并打印方法栈的接收器注册到当前进程了。这里是打印方法栈,但是实际上可以做任何事,因为方法执行的当前,上下文已经跑到进程里面了。

那么怎么向进程发送信号呢?和 JVM 的方法类似,可以通过操作系统命令来发送:

kill -30 pid

这里的信号为什么是 30?这是因为 SIGUSR1 被当前操作系统定义成 30(请注意不同的操作系统这个映射表是可能不同的),这点可以通过 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

当然,也可以写一点点 python 脚本来发送这个信号:

import os, signal
os.kill($PID, signal.SIGUSR1)

原理是一样的。

strace

如果进程已经无响应了,或者上面的信号接收器没有注册,那么就要考虑别的方法来或者“进程在干什么”这件事情了。其中,一个有用的命令是 strace:

strace -p pid

比如,我自己写了一个测试脚本 t.py,使用 python 执行,然后调用 sleep,再给它发送一个 SIGUSR1 的消息,它打印方法栈并退出。这整个过程,我使用 strace 可以得到这样的结果:

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 开始,到进程退出,所有重要的调用都被打印出来了。

在 iOS 下,没有 strace,但是可以使用类似的(更好的)命令 dtruss。

lsof

lsof 可以打印某进程打开的文件,而 Linux 下面一切都是文件,因此查看打开的文件列表有时可以获取很多额外的信息。比如,打开前面提到的这个测试进程:

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

它有几个参数很常用,比如-i,用来指定网络文件(如果是“-i: 端口号”这样的形式还可以指定端口)。

总结

以上所述是小编给大家介绍的分析运行中的 Python 进程,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Python 相关文章推荐
Python3通过Luhn算法快速验证信用卡卡号的方法
May 14 Python
Python 加密的实例详解
Oct 09 Python
解决python nohup linux 后台运行输出的问题
May 11 Python
django反向解析和正向解析的方式
Jun 05 Python
华为2019校招笔试题之处理字符串(python版)
Jun 25 Python
python 读取修改pcap包的例子
Jul 23 Python
python 接口实现 供第三方调用的例子
Aug 13 Python
python:动态路由的Flask程序代码
Nov 22 Python
用OpenCV将视频分解成单帧图片,图片合成视频示例
Dec 10 Python
python中 _、__、__xx__()区别及使用场景
Jun 30 Python
手把手教你用Django执行原生SQL的方法
Feb 18 Python
Python 实现劳拉游戏的实例代码(四连环、重力四子棋)
Mar 03 Python
机器学习实战之knn算法pandas
Jun 22 #Python
解决py2exe打包后,总是多显示一个DOS黑色窗口的问题
Jun 21 #Python
pyinstaller打包单个exe后无法执行错误的解决方法
Jun 21 #Python
pyinstaller打包多个py文件和去除cmd黑框的方法
Jun 21 #Python
解决Pyinstaller 打包exe文件 取消dos窗口(黑框框)的问题
Jun 21 #Python
十行代码使用Python写一个USB病毒
Jun 21 #Python
Python pandas DataFrame操作的实现代码
Jun 21 #Python
You might like
如何对PHP程序中的常见漏洞进行攻击(下)
2006/10/09 PHP
PHP中for循环语句的几种变型
2006/11/26 PHP
隐性调用php程序的方法
2009/03/09 PHP
PHP页面中文乱码分析
2013/10/29 PHP
Zend Framework入门教程之Zend_Session会话操作详解
2016/12/08 PHP
javascript中万恶的function实例分析
2011/05/25 Javascript
ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能
2012/02/03 Javascript
javascript中Date对象的getDay方法使用指南
2014/12/22 Javascript
js调用百度地图及调用百度地图的搜索功能
2015/09/07 Javascript
JAVASCRIPT代码编写俄罗斯方块网页版
2015/11/26 Javascript
JavaScript数组的一些奇葩行为
2016/01/25 Javascript
JS模仿手机端九宫格登录功能实现代码
2016/04/28 Javascript
js中常用的Tab切换效果(推荐)
2016/08/30 Javascript
jQuery实现导航滚动到指定内容效果完整实例【附demo源码下载】
2016/09/20 Javascript
JS短信验证码倒计时功能的实现(没有验证码,只有倒计时)
2016/10/27 Javascript
JavaScript编写九九乘法表(两种任选)
2017/02/04 Javascript
基于Vue实例对象的数据选项
2017/08/09 Javascript
Vue axios全局拦截 get请求、post请求、配置请求的实例代码
2018/11/28 Javascript
Webpack4 使用Babel处理ES6语法的方法示例
2019/03/07 Javascript
vue实现后台管理权限系统及顶栏三级菜单显示功能
2019/06/19 Javascript
小程序实现悬浮搜索框
2019/07/12 Javascript
JS实现图片懒加载(lazyload)过程详解
2020/04/02 Javascript
[59:15]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第一场 11.20
2020/11/20 DOTA
详解Django中的权限和组以及消息
2015/07/23 Python
Python开发中爬虫使用代理proxy抓取网页的方法示例
2017/09/26 Python
简单实现Python爬取网络图片
2018/04/01 Python
Python实现的爬取豆瓣电影信息功能案例
2019/09/15 Python
Python实现CNN的多通道输入实例
2020/01/17 Python
世界顶级足球门票网站:Live Football Tickets
2017/10/14 全球购物
ECOSUSI官网:女式皮革背包
2019/09/27 全球购物
实习护士自我鉴定
2013/10/13 职场文书
开学典礼主持词
2014/03/19 职场文书
试用期员工工作自我评价
2014/09/10 职场文书
校园安全教育心得体会
2016/01/15 职场文书
浅谈如何提高PHP代码质量之单元测试
2021/05/28 PHP
redis cluster支持pipeline的实现思路
2021/06/23 Redis