Python 性能剖析利器:DTrace 与 SystemTap 深度指南

news/2025/2/23 19:40:02

在 Python 开发过程中,深入了解程序的运行时行为对于优化性能、排查问题至关重要。本文聚焦于 DTraceSystemTap 这两款强大的监控工具,详细介绍它们在 CPython 中的应用,包括启用静态标记、编写 DTraceSystemTap 脚本、利用可用的静态标记和 Tapsets 等内容,帮助开发者精准剖析 CPython 程序,提升开发效率和程序质量。

目录

DTrace%20%E5%92%8C%20SystemTap%20%E7%AE%80%E4%BB%8B-toc" name="tableOfContents" style="margin-left:40px">一、DTraceSystemTap 简介

二、启用静态标记

(一)macOS 系统

(二)Linux 系统

DTrace%20%E6%8E%A2%E9%92%88-toc" name="tableOfContents" style="margin-left:40px">三、静态 DTrace 探针

SystemTap%20%E6%A0%87%E8%AE%B0-toc" name="tableOfContents" style="margin-left:40px">四、静态 SystemTap 标记

(一)直接使用静态标记

(二)可用的静态标记

SystemTap%20Tapsets-toc" name="tableOfContents" style="margin-left:80px">(三)SystemTap Tapsets

五、总结

相关学习资源


DTrace%20%E5%92%8C%20SystemTap%20%E7%AE%80%E4%BB%8B" name="%E4%B8%80%E3%80%81DTrace%20%E5%92%8C%20SystemTap%20%E7%AE%80%E4%BB%8B">一、DTraceSystemTap 简介

DTraceSystemTap 都是功能强大的监控工具,为用户提供了检查计算机系统上进程的有效方式。它们通过特定领域的语言,允许用户编写脚本实现进程监视过滤、数据收集以及生成数据报告等功能。从 Python 3.6 开始,CPython 支持构建带有嵌入式 “标记”(探测器)的版本,借助 DTraceSystemTap 脚本,开发者能够更轻松地监视系统上 CPython 进程的运行状态 。不过需要注意的是,DTrace 标记属于 CPython 解释器的实现细节,在不同 CPython 版本之间,探针兼容性无法得到保证,版本变更时 DTrace 脚本可能会失效。

二、启用静态标记

(一)macOS 系统

macOS 系统内置了对 DTrace 的支持。在 macOS 上,用户可以通过在后台运行 Python 进程,然后使用以下命令列出 Python 程序提供的所有探测器:

$ python3.6 -q&
$ sudo dtrace -l -P python$! 
# 或者:dtrace -l -m python3.6

(二)Linux 系统

在 Linux 系统中,若要使用 SystemTap 的嵌入式标记构建 CPython,首先需要安装 SystemTap 开发工具。可以通过以下命令进行安装:

# 使用yum安装
$ yum install systemtap-sdt-devel
# 使用apt-get安装
$ sudo apt-get install systemtap-sdt-dev

安装完成后,在构建 CPython 时需要配置--with-dtrace选项,例如:

checking for --with-dtrace... yes

构建完成后,可以通过查看二进制文件是否包含.note.stapsdt部分来验证 SystemTap 静态标记是否存在:

$ readelf -S./python|grep.note.stapsdt

如果 Python 被编译为共享库(使用--enable-shared配置选项),则需要在共享库内部进行查看,如:

$ readelf -S libpython3.3dm.so.1.0|grep.note.stapsdt

部分较新版本的readelf命令还可以打印元数据,通过这些元数据能够详细了解 SystemTap 的相关信息,包括如何修补机器码指令以启用跟踪钩子 。

DTrace%20%E6%8E%A2%E9%92%88" name="%E4%B8%89%E3%80%81%E9%9D%99%E6%80%81%20DTrace%20%E6%8E%A2%E9%92%88">三、静态 DTrace 探针

下面通过一个 DTrace 脚本示例,展示如何显示 Python 脚本的调用 / 返回层次结构,并且仅在调用名为start的函数内进行跟踪(即导入时的函数调用不会被列出)。

self int indent;
​
python$target:::function-entry
/copyinstr(arg1) == "start"/
{
    self->trace = 1;
}
​
python$target:::function-entry
/self->trace/
{
    printf("%d\t%*s:", timestamp, 15, probename);
    printf("%*s", self->indent, "");
    printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);
    self->indent++;
}
​
python$target:::function-return
/self->trace/
{
    self->indent--;
    printf("%d\t%*s:", timestamp, 15, probename);
    printf("%*s", self->indent, "");
    printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);
}
​
python$target:::function-return
/copyinstr(arg1) == "start"/
{
    self->trace = 0;
}

运行该脚本的命令如下:

$ sudo dtrace -q -s call_stack.d -c "python3.6 script.py"

执行后,输出结果类似如下形式:

156641360502280  function-entry:call_stack.py:start:23
156641360518804  function-entry: call_stack.py:function_1:1
156641360532797  function-entry:  call_stack.py:function_3:9
156641360546807 function-return:  call_stack.py:function_3:10
156641360563367 function-return: call_stack.py:function_1:2
156641360578365  function-entry: call_stack.py:function_2:5
156641360591757  function-entry:  call_stack.py:function_1:1
156641360605556  function-entry:   call_stack.py:function_3:9
156641360617482 function-return:   call_stack.py:function_3:10
156641360629814 function-return:  call_stack.py:function_1:2
156641360642285 function-return: call_stack.py:function_2:6
156641360656770  function-entry: call_stack.py:function_3:9
156641360669707 function-return: call_stack.py:function_3:10
156641360687853  function-entry: call_stack.py:function_4:13
156641360700719 function-return: call_stack.py:function_4:14
156641360719640  function-entry: call_stack.py:function_5:18
156641360732567 function-return: call_stack.py:function_5:21
156641360747370 function-return:call_stack.py:start:28

通过上述脚本和命令,我们可以清晰地看到函数的调用和返回顺序,以及对应的文件名、函数名和行号,方便开发者分析程序的执行流程 。

SystemTap%20%E6%A0%87%E8%AE%B0" name="%E5%9B%9B%E3%80%81%E9%9D%99%E6%80%81%20SystemTap%20%E6%A0%87%E8%AE%B0">四、静态 SystemTap 标记

(一)直接使用静态标记

直接使用静态标记时,需要明确指定包含标记的二进制文件。例如,以下 SystemTap 脚本用于显示 Python 脚本的调用 / 返回层次结构:

probe process("python").mark("function__entry") {
    filename = user_string($arg1);
    funcname = user_string($arg2);
    lineno = $arg3;
​
    printf("%s => %s in %s:%d\\n",
           thread_indent(1), funcname, filename, lineno);
}
​
probe process("python").mark("function__return") {
    filename = user_string($arg1);
    funcname = user_string($arg2);
    lineno = $arg3;
​
    printf("%s <= %s in %s:%d\\n",
           thread_indent(-1), funcname, filename, lineno);
}

运行该脚本的命令为:

$ stap show-call-hierarchy.stp -c "./python test.py"

输出结果如下:

11408 python(8274):        => __contains__ in Lib/_abcoll.py:362
11414 python(8274):         => __getitem__ in Lib/os.py:425
11418 python(8274):          => encode in Lib/os.py:490
11424 python(8274):          <= encode in Lib/os.py:493
11428 python(8274):         <= __getitem__ in Lib/os.py:426
11433 python(8274):        <= __contains__ in Lib/_abcoll.py:366

输出结果中的列分别表示脚本开始后经过的微秒数、可执行文件的名字、进程的 PID 以及脚本执行时的调用 / 返回层次结构。

如果使用的是 CPython--enable-shared编译版,由于标记包含在libpython共享库内部,probe 的加点路径需要相应调整。例如,上述脚本中的probe process("python").mark("function__entry") {应改为probe process("python").library("libpython3.6dm.so.1.0").mark("function__entry") {(假定为 CPython 3.6 的调试编译版) 。

(二)可用的静态标记

CPython 提供了多个可用的静态标记,方便开发者从不同角度监控程序运行:

标记名称触发时机参数说明用途
function__entry(str filename, str funcname, int lineno)Python 函数执行开始(仅针对纯 Python 字节码函数)$arg1:文件名(使用user_string($arg1)访问) $arg2:函数名(使用user_string($arg2)访问) $arg3:行号用于跟踪函数的调用,分析函数执行的起始位置和相关信息
function__return(str filename, str funcname, int lineno)Python 函数执行结束(通过return或异常,仅针对纯 Python 字节码函数)function__entry参数相同用于跟踪函数的返回,分析函数执行的结束位置和相关信息
line(str filename, str funcname, int lineno)Python 行即将被执行(不会在 C 函数中触发)function__entry参数相同类似于 Python 分析器逐行追踪,可用于细粒度的代码执行分析
gc__start(int generation)Python 解释器启动垃圾回收循环时arg0:要扫描的代(与gc.collect()中的参数含义相同)用于监控垃圾回收机制的启动,分析垃圾回收操作的触发时机和相关参数
gc__done(long collected)Python 解释器完成垃圾回收循环时arg0:收集到的对象数量用于监控垃圾回收机制的结束,分析垃圾回收的效果和效率
import__find__load__start(str modulename)importlib试图查找并加载模块之前arg0:模块名称用于跟踪模块导入的开始阶段,分析模块导入的触发原因和相关模块信息
import__find__load__done(str modulename, int found)importlibfind_and_load函数被调用后arg0:模块名称,arg1:表示模块是否成功加载用于跟踪模块导入的结束阶段,分析模块导入的结果和相关模块信息
audit(str event, void *tuple)sys.audit()PySys_Audit()被调用时arg0:事件名称的 C 字符串,arg1:指向元组对象的PyObject指针用于监控系统审计相关的操作,分析程序运行过程中的安全相关事件

SystemTap%20Tapsets" name="%EF%BC%88%E4%B8%89%EF%BC%89SystemTap%20Tapsets">(三)SystemTap Tapsets

SystemTap 的 Tapsets 是一种更高层次的集成方式,它类似于库,能够隐藏静态标记的一些底层细节,使开发者使用起来更加便捷。例如,以下是一个基于 CPython 非共享构建的 tapset 文件示例:

/*
   提供对 function__entry 和 function__return 标记的高级封装
 */
probe python.function.entry = process("python").mark("function__entry")
{
    filename = user_string($arg1);
    funcname = user_string($arg2);
    lineno = $arg3;
    frameptr = $arg4
}
probe python.function.return = process("python").mark("function__return")
{
    filename = user_string($arg1);
    funcname = user_string($arg2);
    lineno = $arg3;
    frameptr = $arg4
}

如果将这个文件安装在 SystemTap 的 tapset 目录下(如/usr/share/systemtap/tapset),就会新增两个可用的探测点:

  • python.function.entry(str filename, str funcname, int lineno, frameptr):表示一个 Python 函数的执行已经开始,仅针对纯 Python 字节码函数触发。

  • python.function.return(str filename, str funcname, int lineno, frameptr):表示一个 Python 函数的执行已经结束(通过return或异常),仅针对纯 Python 字节码函数触发。

基于上述 tapset,我们可以编写更简洁的 SystemTap 脚本。例如,以下脚本使用该 tapset 实现跟踪 Python 函数调用层次结构:

probe python.function.entry
{
    printf("%s => %s in %s:%d\n",
           thread_indent(1), funcname, filename, lineno);
}
​
probe python.function.return
{
    printf("%s <= %s in %s:%d\n",
           thread_indent(-1), funcname, filename, lineno);
}

还有另一个脚本,使用该 tapset 提供所有运行中的 CPython 代码的类似top的视图,显示整个系统中每一秒内前 20 个最频繁进入的字节码帧:

global fn_calls;

probe python.function.entry
{
    fn_calls[pid(), filename, funcname, lineno] += 1;
}

probe timer.ms(1000) {
    printf("\033[2J\033[1;1H") /* clear screen */
    printf("%6s %80s %6s %30s %6s\n",
           "PID", "FILENAME", "LINE", "FUNCTION", "CALLS")
    foreach ([pid, filename, funcname, lineno] in fn_calls -limit 20) {
        printf("%6d %80s %6d %30s %6d\n",
            pid, filename, lineno, funcname,
            fn_calls[pid, filename, funcname, lineno]);
    }
    delete fn_calls;
}

五、总结

本文详细介绍了如何使用 DTraceSystemTapCPython 进行检测。通过启用静态标记,开发者可以在不同系统上为 CPython 添加监控能力;利用静态 DTrace 探针和 SystemTap 标记,能够实现对 CPython 程序的函数调用、垃圾回收、模块导入等关键行为的跟踪;而 SystemTap Tapsets 则提供了更高级、更便捷的方式来编写监控脚本。掌握这些工具和技术,开发者可以更深入地了解 CPython 程序的运行时行为,为性能优化、问题排查提供有力支持。

TAG: Python;DTraceSystemTapCPython性能剖析程序监控

相关学习资源

  1. Python 官方文档:使用 DTraceSystemTap 检测 CPython,提供了基础的使用方法和概念。

  2. DTrace 官方文档DTrace 的官方文档,深入了解 DTrace 的语法和功能,有助于编写更复杂的 DTrace 脚本 。

  3. SystemTap 官方文档SystemTap 的官方文档,详细介绍了 SystemTap 的使用方法和特性,是学习 SystemTap 的重要资源 。


http://www.niftyadmin.cn/n/5863709.html

相关文章

GPU和FPGA的区别

GPU&#xff08;Graphics Processing Unit&#xff0c;图形处理器&#xff09;和 FPGA&#xff08;Field-Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;不是同一种硬件。 我的理解是&#xff0c;虽然都可以用于并行计算&#xff0c;但是GPU是纯计算的硬件…

算法日记24:leetcode198打家劫舍(DFS->记忆化搜索->倒序动态规划->循序动态规划)

一、递归写法&#xff08;dfs深搜&#xff09; 1.1&#xff09;思路讲解 递归思想&#xff1a; dfs(x)表示从第x家店开始的最大劫掠值。对每一家店铺&#xff0c;有两个选择&#xff1a; 不劫掠 当前店铺&#xff0c;即跳到下家 dfs(x1)。劫掠 当前店铺&#xff0c;且跳过下家…

《深度学习实战》第1集:深度学习基础回顾与框架选择

本专栏系列博文旨在帮助读者从深度学习的基础知识逐步进阶到前沿技术&#xff0c;涵盖理论、实战和行业应用。每集聚焦一个核心知识点&#xff0c;并结合实际项目进行实践&#xff0c;避免空谈理论&#xff0c;简洁明快&#xff0c;快速切入代码&#xff0c;所有代码都经过验证…

CSS `transform` 属性详解:打造视觉效果与动画的利器

CSS transform 属性详解&#xff1a;打造视觉效果与动画的利器 引言一、transform 属性简介二、平移&#xff08;Translation&#xff09;三、旋转&#xff08;Rotation&#xff09;四、缩放&#xff08;Scale&#xff09;五、倾斜&#xff08;Skew&#xff09;六、组合变换&am…

《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成

量子计算的神秘面纱 在科技飞速发展的今天,量子计算作为前沿领域,正逐渐走进大众的视野。它宛如一把神秘的钥匙,有望开启未来科技变革的大门,而微软量子芯片则是这把钥匙上一颗璀璨的明珠。 量子计算,简单来说,是一种遵循量子力学规律调控量子信息单元进行计算的新型计算…

破解Docker镜像拉取难题:为Docker配置代理加速镜像拉取

为Docker配置代理加速镜像拉取 概述守护进程配置&#xff08;推荐长期使用&#xff09;Systemd环境变量配置&#xff08;适合临时调整&#xff09;其他 概述 为什么需要配置代理与镜像加速? 跨国网络限制&#xff1a;境外镜像仓库拉取速度慢或无法访问企业安全策略&#xff…

计算机专业知识【揭开汇编的神秘面纱:从基础概念到实际应用】

在计算机编程的广阔领域中&#xff0c;汇编语言扮演着独特而重要的角色。对于刚接触编程的小白来说&#xff0c;汇编可能听起来有些神秘。下面我们就来全面了解一下汇编是什么。 一、汇编的基本定义 汇编语言&#xff08;Assembly Language&#xff09;是一种低级程序设计语言…

Pytorch实现之GIEGAN(生成器信息增强GAN)训练自己的数据集

简介 简介:在训练数据样本之前首先利用VAE来推断潜在空间中不同类的分布,用于后续的训练,并使用它来初始化GAN。与ACGAN和BAGAN不同的是,提出的GIEGAN有一个分类器结构,这个分类器主要判断生成的图像或者样本图像属于哪个类,而鉴别器仅判断图像是来自于生成器还是真实样…