如何诊断Linux系统中占用CPU的Python进程

7

我的Python进程在Ubuntu系统上某个点开始占用大量CPU,这是自动化脚本。 我正在尝试在GDB中调试此问题,但我对GDB还不太熟悉。有没有GDB命令可以提供关于哪个线程使用了大部分CPU的信息。查看线程堆栈并不能真正揭示出来。

在Windows Windbg世界中,命令'! runaway'可以提供进程中每个线程消耗的时间信息。我们这里有类似的命令吗?还有其他调试问题的建议吗?


你现在需要的是一个分析器。我建议你从一个用Python实现的分析器开始——这样你可以使用更低级别的工具获得更好的信息。直接跳到gdb几乎肯定是错误的起点。 - Charles Duffy
虽然“我应该使用哪个 Python 分析器?”是一个工具推荐请求,在 SO 的当前规则下属于离题,但在这些规则生效之前,这个问题已经被问过并回答了,因此您可以在档案中找到大量信息。 - Charles Duffy
3个回答

18

为了澄清诊断此问题所需的所有步骤(感谢大家的帖子):

以下命令显示进程列表及其CPU /内存使用情况:

$ ps auxf 

下面的命令可以列出一个进程的所有线程,并按 CPU 使用率排序:

$ top -H -p [PID]

*PID     USER   PR  NI  VIRT  RES  SHR S  %CPU    %MEM    TIME+  COMMAND*
**1654** root   20   0 1416m 1.2g  24m t  **100** 36.8  21:26.23 python
1687     root   20   0 1416m 1.2g  24m t    0     36.8   0:05.07 python

线程1654正在消耗CPU资源。请使用gdb附加到该进程:

$ gdb /path/of/executable [pid]

使用以下命令在 gdb 中获取线程列表:

(gdb) info threads

2  Thread 0xa7bffb40 (LWP 20736)    "python" 0xb7736424 in __kernel_vsyscall ()
1  Thread 0xb73a56c0 (LWP **1654**) "python" 0xb7736424 in __kernel_vsyscall ()

gdb 中切换线程以检查其堆栈:
(gdb) thread 1
(gdb) bt

很好。有没有办法知道哪个线程正在占用CPU? - Bryce
在这个命令中,你指的是“/path/of/process”是什么意思?gdb /path/of/process [pid] - nngeek
@nngeek /bin/python - user202729
另一种方法是使用 ps -eLf 命令,它会在第四列中打印出 LWP,并显示它所消耗的时间以及更多的列。 - Linas
当您有很多线程并且不想通过视觉方式搜索LWP时,可以使用thread find 1654让gdb为您完成工作,而不是使用info threads - hackerb9

2

一种可能的解决方案是使用带有显示所有线程选项的top命令:

> top -H

默认情况下,任务将按CPU使用率排序。
在先前的线程这里中可以找到替代方案。

top -H -p PID 更好。 -p:仅监视具有给定进程ID的进程。此标志最多可以给出二十次。 - user184968
谢谢大家。这很有帮助,找出了哪个线程占用了CPU。 - Feru
1
有没有一种GDB命令可以在核心转储文件中检测线程使用情况。假设进程不处于进程正在占用CPU的状态。实际上,这可能是来自客户的核心转储。 - Feru

0

简短回答

  1. $ top -H

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    1654 root 20 0 1416m 1.2g 24m R 100 36.8 21:26.23 python
    1687 root 20 0 1416m 1.2g 24m S 0 36.8 0:05.07 python
  2. q

  3. $ gdb -p 1654

  4. (gdb) bt

长答案

top -H

运行top -H以显示在您的计算机上消耗最多CPU的线程。(如果没有-H,top通常会为每组线程显示一个PID。)如果您有一个失控的线程,您将在列表顶部看到它。请注意其PID.¹

*PID     USER   PR  NI  VIRT  RES  SHR S  %CPU    %MEM    TIME+  COMMAND*
**1654** root   20   0 1416m 1.2g  24m R  **100** 36.8  21:26.23 python
1687     root   20   0 1416m 1.2g  24m S    0     36.8   0:05.07 python

回到命令提示符

按下 q 键退出 top 命令。(或者,您可以打开第二个终端窗口。)

gdb -p [pid]

使用 -p 选项运行 gdb,并告诉它要附加到您想诊断的线程的进程ID(PID)。您将会得到一个 (gdb) 提示符。

$ gdb -p 1654
(gdb) 

bt

在(gdb)提示符下,键入bt以查看回溯信息。最有用的gdb命令,用于检查调用堆栈和变量的是:updownlistp。(我还强烈建议使用set print pretty on,使得p的输出更易读。)

(gdb) bt
#0  0x000000000057279a in std::_Hashtable<terminal::renderer::ImageFragmentKey, std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata>, std::allocator<std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata> >, std::__detail::_Select1st, std::equal_to<terminal::renderer::ImageFragmentKey>, std::hash<terminal::renderer::ImageFragmentKey>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_find_before_node(unsigned long, terminal::renderer::ImageFragmentKey const&, unsigned long) const
    (this=this@entry=0x1c11780, __bkt=__bkt@entry=69247, __k=..., __code=11352315644114232719) at /usr/include/c++/10/bits/hashtable.h:1577
#1  0x0000000000573638 in std::_Hashtable<terminal::renderer::ImageFragmentKey, std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata>, std::allocator<std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata> >, std::__detail::_Select1st, std::equal_to<terminal::renderer::ImageFragmentKey>, std::hash<terminal::renderer::ImageFragmentKey>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_find_node(unsigned long, terminal::renderer::ImageFragmentKey const&, unsigned long) const
    (__c=11352315644114232719, __key=..., __bkt=69247, this=0x1c11780) at /usr/include/c++/10/bits/hashtable.h:693
#2  std::_Hashtable<terminal::renderer::ImageFragmentKey, std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata>, std::allocator<std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata> >, std::__detail::_Select1st, std::equal_to<terminal::renderer::ImageFragmentKey>, std::hash<terminal::renderer::ImageFragmentKey>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::find(terminal::renderer::ImageFragmentKey const&) (__k=..., this=0x1c11780) at /usr/include/c++/10/bits/hashtable.h:1454
#3  std::unordered_map<terminal::renderer::ImageFragmentKey, terminal::renderer::ImageRenderer::Metadata, std::hash<terminal::renderer::ImageFragmentKey>, std::equal_to<terminal::renderer::ImageFragmentKey>, std::allocator<std::pair<terminal::renderer::ImageFragmentKey const, terminal::renderer::ImageRenderer::Metadata> > >::find(terminal::renderer::ImageFragmentKey const&) (__x=..., this=0x1c11780) at /usr/include/c++/10/bits/unordered_map.h:920
...

小细节

如果您按照我在这里建议的方法使用gdb -p连接到单个线程,gdb将表现得好像只有一个线程。如果您希望使用thread命令切换要调试的线程,则必须连接到主进程的PID。


为什么我说“PID”(进程ID)而不是“TID”(线程ID)?因为在Linux中,线程只是轻量级进程,并且在内核中使用与进程相同的内部结构。每个新创建的线程都有一个不同于父进程PID的PID。为了将它们逻辑上分组为单个“进程”,每个线程都有另一个字段称为TGID(线程组ID),它记住了父进程的PID。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接