不要因此而激怒我,但这是真的。我正在编写一个运行很长时间的多线程python应用程序,通常需要2-3个小时才能完成10个进程。这台机器并不慢,只需要进行大量的计算。
问题在于,由于外部工具,应用程序有时会占用大约85-90%的路径。
我已经将这个测试分解成更小的部分然后可以成功运行但是长时间运行的程序会挂起。
例如,假设我必须分析100,000,000项长的列表中的一些数据。将其分解为二十五万个列表,所有较小的部分都将完成。
尝试完成100,000,000个项目,并将其挂起。我使用了一些我无法改变的外部工具,所以我只是想看看发生了什么。
我设置了Dtrace并运行
sudo dtrace -n 'syscall:::entry / execname == "python2.7" / { @[ustack()] = count() }'
在我的程序正确挂起时,我得到一个输出,如下面的代码示例。
libc.so.7`__sys_recvfrom+0xa
_socket.so`0x804086ecd
_socket.so`0x8040854ac
libpython2.7.so.1`PyEval_EvalFrameEx+0x52d7
libpython2.7.so.1`PyEval_EvalCodeEx+0x665
libpython2.7.so.1`0x800b3317d
libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
libpython2.7.so.1`0x800b33250
libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
libpython2.7.so.1`PyEval_EvalCodeEx+0x665
libpython2.7.so.1`0x800abb5a1
libpython2.7.so.1`PyObject_Call+0x64
libpython2.7.so.1`0x800aa3855
libpython2.7.so.1`PyObject_Call+0x64
libpython2.7.so.1`PyEval_EvalFrameEx+0x4de2
libpython2.7.so.1`PyEval_EvalCodeEx+0x665
libpython2.7.so.1`0x800abb5a1
libpython2.7.so.1`PyObject_Call+0x64
libpython2.7.so.1`0x800aa3855
libpython2.7.so.1`PyObject_Call+0x64
代码只是一遍又一遍地重复。我试着查看Dtrace python探测器,但是那些似乎从星期二两侧被破坏,所以这可能是我最接近的。
我的问题,我有一个模糊的想法,libpython2.7.so.1
是将函数pyObject_Call
保存在十六进制偏移量为0x64的共享库
是吗?
我怎么能破译这个?我不知道该怎么称呼它,以便我可以谷歌寻找答案或指南。
答案 0 :(得分:2)
您应该首先阅读Showing the stack trace from a running Python application。
你的具体 问题是关于DTrace的ustack()动作的解释 所以这个回复可能超出你的需要。这是因为其中之一 DTrace的设计原则是显示系统的确切状态。 所以,即使你对你的Python方面感兴趣 程序,DTrace揭示了它的底层实现。
您提供的输出是一个堆栈,这是一种方式 描述其中特定点的线程状态 执行。例如,如果您有代码
void c(void) { pause(); }
void b(void) { c(); }
void a(void) { b(); }
然后你要求堆栈,而执行是在pause()之内 你可能会看到类似
的内容pause()
c()
b()
a()
您使用的任何工具都会找到当前指令及其指令 在找到"返回地址"之前封闭功能,即 指出该函数最终将返回;重复一遍 程序产生一个堆栈。因此,尽管应该读取堆栈 从顶部到底部作为一系列返回地址,它通常是 从另一个方向读取一系列呼叫者。注意 程序的相应方式的细微之处 组装说明意味着这第二种解释 有时可能会产生误导。
为了扩展上面的例子,它可能是a(),b()和c() 所有人都存在于同一个图书馆内 - 而且可能存在 其他库中具有相同名称的函数。因此,它 有用的是为每个函数显示它所对应的对象 所属。因此,上面的堆栈可能变成
libc.so`pause()
libfoo.so`c()
libfoo.so`b()
libfoo.so`a()
这在某种程度上允许开发人员确定如何 程序最终处于特定状态:libfoo中的函数c() 已经调用了pause()。但是,还有更多工作要做:如果c() 看起来像
void c() {
pause();
pause();
}
然后调用pause()是程序在等待吗?
函数a(),b()和c()将是序列 通常会占据连续区域的指令 记忆。调用其中一个函数只涉及到 记下完成后返回的地方(即退货) 地址)然后跳转到对应的任何内存地址 到功能的开始。功能'起始地址和大小是 记录在"符号表"嵌入在对象中;它' S 通过阅读此表,调试器能够找到该函数 包含给定位置,例如返回地址。这样一个 函数中的特定点可以通过偏移来描述, 通常以十六进制表示,从一开始。所以更好 上面的堆栈版本可能是
libc.so`pause()+0x12
libfoo.so`c()+0x42
libfoo.so`b()+0x12
libfoo.so`a()+0x12
此时,开发人员可以使用"反汇编程序"在libfoo.so上 在c()中显示指令;与c()相比较 源代码将允许他透露具体的行 已经调用了pause()。
在结束对堆栈的描述之前,它值得制作 再观察一次。鉴于存在足够的"调试 数据"在像libfoo这样的库中,可以使用更好的调试器 更进一步,显示源代码文件名和 行号而不是每个"帧"的十六进制偏移量在 堆栈。
现在,要回到问题中的堆栈, libpython(2.7.so.1)是一个函数执行作业的库 执行Python脚本。 Python脚本中的函数是 在运行中转换成可执行指令,所以我的猜测是 片段
libpython2.7.so.1`0x800b33250
libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
libpython2.7.so.1`PyEval_EvalCodeEx+0x665
表示PyEval_EvalFrameEx()是libpython中的功能 本身调用Python函数(即写入的东西) Python)驻留在地址0x800b33250附近的内存中。一个 简单的调试器可以看到这个地址属于libpython但是 在图书馆的符号表中找不到相应的条目; 没有选择,它只是打印" raw"地址。
所以,你需要查看Python脚本,看看它是什么 但不幸的是,没有任何迹象表明 堆栈的 Python 组件中的函数。
有几种方法可以继续。首先是找到一个 libpython的版本(如果存在),带有"DTrace helper"。这个 是一些额外的功能,让DTrace看到的状态 Python程序本身除了周围 实现。结果是每个Python框架都是 用Python源代码中的对应点注释。
另一个,如果您在Solaris上,则使用pstack(1);这有 对Python的原生支持。
最后,尝试使用特定的Python调试器。
还值得指出你的dtrace调用会显示出来 你看到的所有堆栈,按人气排序,每当 程序" python2.7"进行系统调用。根据你的描述, 这可能不是你想要的。如果您正在尝试理解 挂起的行为然后你可能想要从一开始 python2.7进程时的单个快照 挂起