分析运行中的 Python 进程

标签: tuicool | 发表时间:2019-06-22 00:00 | 作者:
出处:http://itindex.net/relian

在 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
1 SIGHUP terminate process terminal line hangup
2 SIGINT terminate process interrupt program
3 SIGQUIT create core image quit program
4 SIGILL create core image illegal instruction
5 SIGTRAP create core image trace trap
6 SIGABRT create core image abort program (formerly SIGIOT)
7 SIGEMT create core image emulate instruction executed
8 SIGFPE create core image floating-point exception
9 SIGKILL terminate process kill program
10 SIGBUS create core image bus error
11 SIGSEGV create core image segmentation violation
12 SIGSYS create core image non-existent system call invoked
13 SIGPIPE terminate process write on a pipe with no reader
14 SIGALRM terminate process real-time timer expired
15 SIGTERM terminate process software termination signal
16 SIGURG discard signal urgent condition present on socket
17 SIGSTOP stop process stop (cannot be caught or ignored)
18 SIGTSTP stop process stop signal generated from keyboard
19 SIGCONT discard signal continue after stop
20 SIGCHLD discard signal child status has changed
21 SIGTTIN stop process background read attempted from control terminal
22 SIGTTOU stop process background write attempted to control terminal
23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2))
24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2))
25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2))
26 SIGVTALRM terminate process virtual time alarm (see setitimer(2))
27 SIGPROF terminate process profiling timer alarm (see setitimer(2))
28 SIGWINCH discard signal Window size change
29 SIGINFO discard signal status request from keyboard
30 SIGUSR1 terminate process User defined signal 1
31 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 进程

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

Python 多进程日志记录

- Guancheng(冠诚) - qingbo.blog
刚开始用 Python 做 web 开发的时候我就想一个问题,如果 Python 应用需要自己记录一些比 accesslog 更详细的日志(使用 Python 的 logging module),又有多个进程,怎么办最好呢. 多个进程往同一个日志文件写入会不会出问题. 最近有个在 Apache 里用 mod_wsgi 运行的程序,设置了4个 process.

使用 gdb 调试 Python 进程

- yinseny - python.cn(jobs, news)
有时我们会想调试一个正在运行的Python进程,或者一个Python进程的coredump. 例如现在遇到一个mod_wsgi的进程僵死了,不接受请求,想看看究竟是运行到哪行Python代码呢. 这时就需要祭出gdb这个神器了. 确认你的gdb版本是>=7,gdb从版本7开始支持对Python的debug.

Python 性能分析入门指南

- - 博客 - 伯乐在线
虽然并非你编写的每个 Python 程序都要求一个严格的性能分析,但是让人放心的是,当问题发生的时候,Python 生态圈有各种各样的工具可以处理这类问题. 分析程序的性能可以归结为回答四个基本问题:. 下面,我们将用一些神奇的工具深入到这些问题的答案中去. 用  time 粗粒度的计算时间. 让我们开始通过使用一个快速和粗暴的方法计算我们的代码:传统的 unix  time 工具.

如何用Python做情感分析?

- - 神刀安全网
商品评论挖掘、电影推荐、股市预测……情感分析大有用武之地. 本文帮助你一步步用Python做出自己的情感分析结果,难道你不想试试看. 如果你关注数据科学研究或是商业实践,“情感分析”(sentiment analysis)这个词你应该不陌生吧. 维基百科上,情感分析的定义是:. 文本情感分析(也称为意见挖掘)是指用自然语言处理、文本挖掘以及计算机语言学等方法来识别和提取原素材中的主观信息.

使用Python进行相关性分析

- - 标点符
在数据分析时,经常会针对两个变量进行相关性分析. 在Python中主要用到的方法是pandas中的corr()方法. corr():如果由数据框调用corr函数,那么将会计算每个列两两之间的相似度,返回DataFrame. corr(other):如果由序列调用corr方法,那么只是该序列与传入的序列之间的相关度,返回一个数值型,大小为相关度.

Python地理数据分析工具MovingPandas

- - 标点符
MovingPandas 是一个用于分析轨迹数据的 Python 库. 它在处理和分析移动对象的时空数据方面非常强大,适用于地理信息系统(GIS)、时空数据分析和可视化等领域. 它是在热门的地理数据处理库 GeoPandas 的基础上构建的,GeoPandas 本身是建立在Pandas数据处理库之上的.

【Python量化】手把手教你用python做股票分析入门

- -
python金融量化  已获得授权. 关注可了解更多的金融与Python干货. 目前,获取股票数据的渠道有很多,而且基本上是免费的,比如,行情软件有同花顺、东方财富等,门户网站有新浪财经、腾讯财经、和讯网等. Python也有不少免费的开源api可以获取交易行情数据,如pandas自带的库,tushare和baostock等.

使用python抓取并分析京东商品评论数据

- - 蓝鲸的网站分析笔记
本篇文章是python爬虫系列的第三篇,介绍如何抓取京东商城商品评论信息,并对这些评论信息进行分析和可视化. 下面是要抓取的商品信息,一款女士文胸. 这个商品共有红色,黑色和肤色三种颜色, 70B到90D共18个尺寸,以及超过700条的购买评论. 京东商品评论信息是由JS动态加载的,所以直接抓取商品详情页的URL并不能获得商品评论的信息.

Python做文本情感分析之情感极性分析 - 简书

- -
「NLP」最为目前及其火热的一个领域,已经逐渐渗透进越来越多产业的各项业务中,不知死活的胖子决定对常用的应用功能挨个进行尝试,死活不论……. 「情感极性分析」是对带有感情色彩的主观性文本进行分析、处理、归纳和推理的过程. 按照处理文本的类别不同,可分为基于新闻评论的情感分析和基于产品评论的情感分析.