首页
/ 深入 CPython 的 sys.monitoring:PEP 669 执行事件监控 API 完全指南

深入 CPython 的 sys.monitoring:PEP 669 执行事件监控 API 完全指南

2026-09-07 16:27:23作者:宣海椒Queenly

本篇技术指南以 CPython 官方文档 Doc/library/sys.monitoring.rst 为核心骨架,系统讲解 PEP 669 落地到 CPython 3.12+ 的执行事件监控(Execution Event Monitoring)体系:工具标识符(Tool ID)、事件分类、全局与逐代码对象(per code object)的事件开关、回调注册与各事件签名,并结合仓库源码(Python/instrumentation.cInclude/internal/pycore_instruments.h 等)剖析其字节码插桩与"停止整个世界"(Stop The World)再插桩的实现原理。读完本文,你将能基于 sys.monitoring 从零编写一套调试器、覆盖率统计或轻量级 Profiler,并理解它为何比旧的 sys.settrace/sys.setprofile 更适合高性能监控场景。

一、认识 sys.monitoring:它是"命名空间"而非模块

sys.monitoring 是 CPython 3.12(PEP 669)起随解释器内置提供的执行事件监控接口,用于在程序运行过程中接收回调,从而观察函数调用、返回、行号推进、分支跳转与异常等执行细节。

首先必须澄清一个最容易踩的坑:sys.monitoringsys 模块内部的一个命名空间对象,而不是一个可独立导入的模块。直接执行:

import sys.monitoring  # ModuleNotFoundError: No module named 'sys.monitoring'

会抛出 ModuleNotFoundError。正确用法是导入 sys 后访问其属性:

import sys
sys.monitoring   # 模块对象(m_name 为 "sys.monitoring")

从实现上看,该对象由 Python/instrumentation.c 中的 PyModuleDef monitoring_module 定义,模块名被命名为 "sys.monitoring",再通过 _Py_CreateMonitoringObject() 创建并挂载到 sys 模块的属性上。

整个监控 API 由三个核心部件构成:

  1. 工具标识符(Tool identifiers):整数编号 + 关联名称,用于让多个监控工具并行协作而不互相干扰;
  2. 事件(Events)sys.monitoring.events 命名空间下的一组事件常量;
  3. 回调(Callbacks):为某个工具、某个事件注册的回调函数,事件发生时由虚拟机触发。

这三个部件对应文档原文的 Tool identifiersEventsCallbacks 三节,下面逐一展开。

二、工具标识符(Tool ID):让多个监控工具和平共处

2.1 为什么需要 Tool ID

当调试器、覆盖率工具、Profiler、JIT 优化器等多个工具同时希望监听执行事件时,如果它们共用一套回调,就会相互踩踏。sys.monitoring0~5 共 6 个整数 Tool ID 把各工具隔离:每个工具申请一个 ID,各工具拥有自己独立的事件集合与回调表。

文档同时说明:目前工具之间是完全独立的,一个工具不能监控另一个工具的行为(即不能用监控 API 观察其他工具注册了什么),这一限制未来可能放开。

2.2 预定义的 ID 常量

虽然虚拟机对 0~5 的所有 ID 一视同仁,但 PEP 669 预定义了几个 ID 以便生态协作:

sys.monitoring.DEBUGGER_ID = 0   # 调试器
sys.monitoring.COVERAGE_ID = 1   # 覆盖率工具
sys.monitoring.PROFILER_ID = 2   # Profiler
sys.monitoring.OPTIMIZER_ID = 5  # JIT 优化器

这些常量在 Include/cpython/monitoring.h 之外另有对应内部宏:PY_MONITORING_DEBUGGER_ID 0PY_MONITORING_COVERAGE_ID 1PY_MONITORING_PROFILER_ID 2PY_MONITORING_OPTIMIZER_ID 5,定义于 Include/internal/pycore_instruments.h。注意:这个内部头文件还预留了 PY_MONITORING_SYS_PROFILE_ID 6PY_MONITORING_SYS_TRACE_ID 7——它们是解释器内部用来支撑旧版 sys.setprofile() / sys.settrace() 的工具位(详见后文"与 sys.settrace/sys.setprofile 的关系"),面向用户的可用 ID 只有 0~5

Tool ID 的合法性校验位于 check_valid_tool()Python/instrumentation.c),越界会抛出 ValueError: invalid tool %d (must be between 0 and 5)

2.3 工具生命周期管理 API

一个工具在使用前必须先注册 ID,使用结束后释放。官方提供 4 个函数:

函数 说明 异常/边界
use_tool_id(tool_id: int, name: str, /) -> None tool_id 使用前必须调用;name 为工具名 tool_id 必须在 0~5;若该 ID 已被占用则抛 ValueError
clear_tool_id(tool_id: int, /) -> None 注销与 tool_id 关联的全部事件与回调函数 需先注册过该 ID
free_tool_id(tool_id: int, /) -> None 工具不再需要该 ID 时调用;内部先调用 clear_tool_id 再释放 ID 需先注册过该 ID
get_tool(tool_id: int, /) -> str | None 返回占用该 ID 的工具名;若无人使用返回 None tool_id 必须在 0~5

参数末尾的 / 表示这些函数只支持位置参数,不支持关键字传参。

use_tool_id 为例,Python/instrumentation.c 的实现细节是:工具名必须是 str(否则抛 ValueError: tool name must be a str),且 monitoring_tool_names[tool_id] 已非空时抛 ValueError: tool %d is already in use;名称被存储在解释器状态 PyInterpreterState.monitoring_tool_names 数组中。free_tool_idL2237-L2254)则会先 _PyMonitoring_ClearToolId 清空事件与回调,再 Py_CLEAR 释放名称引用。

典型的工具启动/收尾模式:

import sys

MY_TOOL = 4
sys.monitoring.use_tool_id(MY_TOOL, "MyTool.Tracer")
assert sys.monitoring.get_tool(MY_TOOL) == "MyTool.Tracer"
# ... 注册事件与回调,开始工作 ...
sys.monitoring.free_tool_id(MY_TOOL)   # 收尾,等价于 clear + 释放

这也是仓库测试 Lib/test/test_monitoring.py 的规范用法——该测试在每个用例的 tearDown 中都会调用 sys.monitoring.free_tool_id(TEST_TOOL) 保证测试间互不污染(其 test_tool 用例就断言了 get_tool 返回注册时传入的名称)。

三、事件体系:events 命名空间与事件分类

3.1 事件的三种定位与事件常量

监控 API 支持 18 种执行事件(外加 1 个已弃用事件)。它们都是 sys.monitoring.events 命名空间的属性,每个事件是一个 2 的幂整数常量,因此"事件集合"可以直接用按位或组合,例如同时要 PY_RETURNPY_START 就写 PY_RETURN | PY_START。此外还提供:

sys.monitoring.events.NO_EVENTS   # 0 的别名,便于显式比较

NO_EVENTS 的典型用法是判断当前没有任何事件在监听:

if sys.monitoring.get_events(sys.monitoring.DEBUGGER_ID) == sys.monitoring.events.NO_EVENTS:
    ...  # 该工具当前未激活任何事件

NO_EVENTS(即 0)设为事件集合等价于关闭全部事件。事件常量的构造逻辑在 _Py_CreateMonitoringObject()Python/instrumentation.c):先创建一个 types.SimpleNamespace 类型的命名空间对象,再用 add_power2_constant()1 << i 依次填入各事件,最后挂上 NO_EVENTS = 0

Include/cpython/monitoring.h 可以看到事件编号的完整布局(序号即 1 << 序号 的幂次):

事件编号 宏常量 对应 events 属性 分类
0~10 PY_MONITORING_EVENT_PY_START ... PY_MONITORING_EVENT_STOP_ITERATION PY_START ... STOP_ITERATION 局部事件(Local,可逐位置关闭)
11~15 RAISE / EXCEPTION_HANDLED / PY_UNWIND / PY_THROW / RERAISE 同左 其他事件(Other,主要面向异常)
16~17 C_RETURN / C_RAISE 同左 附属事件(Ancillary,由 CALL 控制)
18 BRANCH 同左 已弃用(3.14 起)

3.2 局部事件(Local Events)

局部事件与程序的正常执行绑定,发生在明确的位置上,因而可以针对某个具体代码位置单独禁用。共 11 种:

  • PY_START:Python 函数开始(发生在调用之后立刻,被调方 frame 已在栈上)
  • PY_RESUME:Python 函数被恢复执行——针对生成器与协程函数,不含 throw() 调用
  • PY_RETURN:Python 函数返回(发生在 return 之前的一瞬间,被调方 frame 仍在栈上)
  • PY_YIELD:Python 函数产出值(yield 之前触发,被调方 frame 仍在栈上)
  • CALL:Python 代码中的一次调用(发生在调用之前)
  • LINE:即将执行一条与前一条指令行号不同的指令
  • INSTRUCTION:一条虚拟机指令即将被执行
  • JUMP:控制流图中发生一次无条件跳转
  • BRANCH_LEFT:条件分支走向"左"
  • BRANCH_RIGHT:条件分支走向"右"
  • STOP_ITERATION:人为抛出的 StopIteration(见 3.5 专门说明)

关于"左/右"分支,文档强调:没有任何保证哪一边是"左"哪一边是"右",唯一保证是整个程序运行期间方向保持一致,具体如何向用户呈现左右完全由工具决定。

3.3 附属事件(Ancillary Events)与已弃用事件

C_RAISE(从任意可调用对象抛出的异常,Python 函数除外,在退出后发生)与 C_RETURN(从任意可调用对象返回,Python 函数除外,在返回后发生)虽然可以被监听,但受控于 CALL 事件:只有对应位置的 CALL 事件处于被监听状态时,才会看到 C_RETURN/C_RAISE。这一约束在源码层强制执行——set_eventsPython/instrumentation.c)和 set_local_events 都会检查:若事件集合包含 C_RETURN_EVENTS 却不包含 C_CALL_EVENTS,直接抛 ValueError: cannot set C_RETURN or C_RAISE events independently,随后还会把 C_RETURN_EVENTS 从集合中剥离(仅作 CALL 的附随产物)。

已弃用事件 BRANCH:该事件在 3.14 起被弃用。文档给出的理由是:改用 BRANCH_LEFT / BRANCH_RIGHT 会获得更好的性能,因为它们能被独立地按位置禁用。源码侧也保留了兼容处理:set_events/set_local_events 收到 BRANCH 位时,会将其清除并自动展开成 BRANCH_RIGHT | BRANCH_LEFTL2367-L2370)。

3.4 其他事件(Other Events):与位置解耦

另有一类事件不与程序中的某个特定位置强绑定,无法针对单个代码位置单独禁用

  • RAISE:异常被抛出(会导致 STOP_ITERATION 事件的除外)
  • RERAISE:异常被重新抛出,例如 finally 块结束时的隐式 re-raise
  • EXCEPTION_HANDLED:某个异常被处理
  • PY_THROW:Python 函数通过 throw() 调用恢复执行
  • PY_UNWIND:Python 函数在异常展开(unwinding)期间退出,包括函数内部直接抛出并放任继续传播的异常

3.15 的行为变化:文档标注 versionchanged:: 3.15——"其他事件"现在也可以按整个 code object 粒度开关了:回调返回 DISABLE 会为整个 code object(针对当前工具)禁用该事件。仓库当前处于开发分支,Include/internal/pycore_instruments.h 头文件注释也印证了这一点:"Other events. These can now be turned on and disabled on a per code object basis."

3.5 STOP_ITERATION 事件的前因后果

PEP 380 规定:生成器或协程返回一个值时会抛出一个 StopIteration 异常。但用抛异常来传返回值非常低效,因此 CPython 3.12+ 的实现除非该异常会对其他代码可见,否则不再真的抛异常

为了让工具能监控到真实的异常又不必拖慢生成器/协程,sys.monitoring 提供了可局部禁用的 STOP_ITERATION 事件。需要特别强调的是:STOP_ITERATION 事件与"针对 StopIteration 异常的 RAISE 事件"在语义上是等价的,生成事件时二者可以互换。实现出于性能考虑会优先产生 STOP_ITERATION,但也可能对某个 StopIteration 产生 RAISE 事件——所以工具若要统计真正的 StopIteration,应当同时对这两种事件都做好准备。

3.6 未来扩展

文档明确"未来可能增加更多事件",事件编号存在天然扩展空间(内部事件总数上限为 _PY_MONITORING_EVENTS,共 19 个位,见 pycore_instruments.h)。

四、开关事件:全局、按 code object、DISABLE 与重启

一个事件要被触发,需要同时满足:① 事件已打开;② 已注册对应回调。事件开关分两层:全局(对整个解释器)与逐 code object(局部)。如果一个事件同时在全局和局部都打开,它仍然只会触发一次

默认情况下没有任何事件处于激活状态。

4.1 全局事件开关

sys.monitoring.get_events(tool_id: int, /) -> int   # 返回该工具所有已激活事件组成的 int
sys.monitoring.set_events(tool_id: int, event_set: int, /) -> None
  • set_events 会激活 event_set 中置位的全部事件;
  • tool_id 未注册使用(不在 monitoring_tool_names 中),抛 ValueError
  • event_set 超出合法事件范围(>= 1 << 19)或非法组合也会抛 ValueError

实现上,set_eventsPython/instrumentation.c)内部会先做合法性检查与 BRANCH/附属事件归一化,然后调用 _PyEval_StopTheWorld(interp) 暂停所有线程、执行 _PyMonitoring_SetEvents() 完成真正的字节码插桩、再 _PyEval_StartTheWorld(interp) 恢复运行。全局活动工具集合存放在解释器状态的 interp->monitors_Py_GlobalMonitors 结构,每个事件一个字节记录哪些工具在位)中。

4.2 按 code object 开关(局部事件)

sys.monitoring.get_local_events(tool_id: int, code: CodeType, /) -> int
sys.monitoring.set_local_events(tool_id: int, code: CodeType, event_set: int, /) -> None

这两个函数只针对局部事件生效,需要传入一个 types.CodeType 对象(一般从函数/方法的 __code__ 属性取得)。文档特别提示:凡是接受 CodeType 的函数都应能接受来自非 Python 定义的函数的"外观相似对象"——这与 Doc/c-api/monitoring.rst 描述的 C API 约定一致(C 侧事件触发接口接受 codelike 对象)。源码中 get_local_events/set_local_events 都先做 PyCode_Check(code) 类型校验,不满足则抛 TypeError: code must be a code objectset_local_events 同样执行 StopTheWorld + _PyMonitoring_SetLocalEvents() 的重插桩流程(L2456-L2459)。

每个 code object 的局部监控状态存于其 _co_monitoring 字段指向的 _PyCoMonitoringData 结构(pycore_instruments.h)中,其中:

  • local_monitors / active_monitors:记录各工具请求监听与实际生效的局部事件;
  • tools / line_tools / per_instruction_tools:按 code unit 记录"这个位置要通知哪些工具",是实现逐位置禁用的数据基础;
  • tool_versions[PY_MONITORING_TOOL_IDS]:记录各工具插桩时的版本号,用于增量去插桩/重插桩;
  • per_instruction_opcodes:指令级事件需要保存的原始操作码。

从源码结构可以推断:LINEINSTRUCTION 事件拥有独立的按 code unit 的数据通道,因此它们的"按位置禁用"开销可以被压缩到每 code unit 一个字节级别,这正是高性能监控的根基。

4.3 从回调中返回 DISABLE:按位置精准关闭

DISABLEsys.monitoring 模块层面的一个特殊单例值(源码中为 _PyInstrumentation_DISABLE,随模块初始化挂载,见 L2574)。它只能在回调函数中作为返回值使用:

  • 局部事件:返回 DISABLE 会禁用当前这个代码位置的该事件。它不会改动"设置了哪些事件",也不影响同事件的其他代码位置;
  • 其他事件(3.15 起):返回 DISABLE 会按整个 code object 维度禁用该事件(针对当前工具)。

文档着重指出:按位置禁用对高性能监控至关重要。例如调试器可以让程序在"除了少数几个断点之外全部禁用监控"的状态下近乎零开销地运行,只有真正命中断点位置时才产生事件。

4.4 restart_events():全局重新启用

sys.monitoring.restart_events() -> None

restart_events 会为所有工具重新启用所有曾被 DISABLE 关闭的事件。其实现(Python/instrumentation.c)比较精巧:利用"版本号"机制——每次设置/重启事件都会递增一个全局版本号,而每个 code object 记录着"上次按什么版本插的桩";restart_events 会把 last_restart_version 提升到一个中间值、再推进全局版本,随后调用 instrument_all_executing_code_objects() 让所有正在执行的 code object 重新按新版本插桩,从而"复活"被禁用的位置。若版本号溢出则抛 OverflowError: events set too many times

五、注册回调:register_callback 与各类事件签名

5.1 注册与注销

sys.monitoring.register_callback(tool_id: int, event: int, func: Callable | None, /) -> Callable | None
  • tool_idevent 注册回调 func
  • 如果该工具/事件之前已有回调,旧回调会被注销并作为返回值返回;否则返回 None
  • 注销回调只需传 func=Nonesys.monitoring.register_callback(tool_id, event, None)
  • 回调可以在任意时刻注册或注销(甚至可以在另一个回调触发过程中进行);
  • 若同一事件在全局与局部都打开了,回调只被调用一次,因此工具的代码需要能同时处理两种触发来源。

源码中的校验逻辑(Python/instrumentation.c):

  • check_valid_tool:tool_id 必须在 0~5;
  • _Py_popcount32(event) != 1:event 必须是单个事件(2 的幂),一次注册多个事件会抛 ValueError: The callback can only be set for one event at a time
  • event 编号越界抛 ValueError: invalid event %d
  • 触发审计钩子 PySys_Audit("sys.monitoring.register_callback", "O", func)——即注册回调会发出 sys.monitoring.register_callback 审计事件,便于安全工具审计;
  • func is None 时内部转为 NULL 完成注销。

5.2 MISSING:表达"调用没有参数"

MISSING 是另一个特殊单例值(_PyInstrumentation_MISSING),专门用于 CALL/C_RAISE/C_RETURN 事件中表示"该调用没有参数"。CALL 事件回调的第四个参数 arg0 仅在确有参数时才是真实值;若调用没有参数,arg0 就是 MISSING

5.3 各类事件的回调签名总表

回调的返回值除了 DISABLE 外,返回任何其他对象都不会产生效果。不同事件传给回调的参数不同,官方文档的完整契约如下(CodeTypetypes.CodeTypeinstruction_offset 是指令偏移量):

事件 回调签名 要点说明
PY_START, PY_RESUME func(code: CodeType, instruction_offset: int) -> object
PY_RETURN, PY_YIELD func(code: CodeType, instruction_offset: int, retval: object) -> object 携带返回值
CALL, C_RAISE, C_RETURN func(code: CodeType, instruction_offset: int, callable: object, arg0: object) -> object arg0 可为 MISSINGcode发起调用处的 code object,callable 是即将被调用的对象
RAISE, RERAISE, EXCEPTION_HANDLED, PY_UNWIND, PY_THROW, STOP_ITERATION func(code: CodeType, instruction_offset: int, exception: BaseException) -> object 携带异常对象
LINE func(code: CodeType, line_number: int) -> object 注意第二参是行号而非指令偏移
BRANCH_LEFT, BRANCH_RIGHT, JUMP func(code: CodeType, instruction_offset: int, destination_offset: int) -> object destination_offset下一步将要执行的位置
INSTRUCTION func(code: CodeType, instruction_offset: int) -> object

关于 CALL 事件,文档给了两条关键语义:

  1. CALLcode 表示"正在发起调用的那个 code object",callable 才是触发了该事件的、即将被调用的对象;
  2. 对于实例方法,callable 会是从类上找到的函数对象,而 arg0 被设为实例本身(即方法的 self 参数)。

5.4 完整示例:一个最小可用的调用监控器

综合上面所有 API,一个监控"Python 函数调用并统计返回"的最小工具如下:

import sys

TID = 3  # 自己选一个 0~5 之间未被占用的 ID

if sys.monitoring.get_tool(TID) is None:
    sys.monitoring.use_tool_id(TID, "call.tracer")

calls = {}

def on_py_start(code, offset):
    calls[code] = calls.get(code, 0) + 1

def on_call(code, offset, callable, arg0):
    arg = "no-args" if arg0 is sys.monitoring.MISSING else repr(arg0)
    print(f"CALL {callable.__qualname__} at {code.co_filename}:{offset} arg0={arg}")

sys.monitoring.register_callback(TID, sys.monitoring.events.PY_START, on_py_start)
sys.monitoring.register_callback(TID, sys.monitoring.events.CALL, on_call)

# 需要同时监听 CALL 才能收到 C_RETURN/C_RAISE(附属事件规则)
def on_c_return(code, offset, callable, retval):
    print(f"C_RETURN {callable.__qualname__} -> {retval!r}")

sys.monitoring.register_callback(TID, sys.monitoring.events.C_RETURN, on_c_return)

# 打开事件:全局打开(对所有函数生效)
sys.monitoring.set_events(
    TID,
    sys.monitoring.events.PY_START
    | sys.monitoring.events.CALL
    | sys.monitoring.events.C_RETURN,   # CALL 已包含,合法
)

def demo(x):
    return x * 2

demo(21)
print("PY_START count:", calls[demo.__code__])

# 收尾清理
sys.monitoring.free_tool_id(TID)

5.5 只监控"某个函数":set_local_events 用法

如果只想精确监控某一个函数(例如只在函数 foo 上设事件),使用 set_local_events

sys.monitoring.set_local_events(
    TID,
    foo.__code__,
    sys.monitoring.events.LINE | sys.monitoring.events.PY_RETURN,
)

def on_line(code, lineno):
    print(f"line {lineno}")

def on_return(code, offset, retval):
    print(f"returned {retval!r}")
    return sys.monitoring.DISABLE   # 该位置只触发一次,随后按位置关闭

在回调中返回 sys.monitoring.DISABLE,即可实现文档所说的"断点式"精准监控:定位到目标行后立刻关闭该位置的事件,把额外开销降到最低。

六、底层原理:从事件到字节码插桩

6.1 本地事件需要字节码插桩

sys.monitoring 并不是简单地在解释器主循环里查一张"事件→回调"大表。对 PY_STARTLINECALLJUMPBRANCH_* 这类局部事件,事件与具体指令绑定,因此需要在字节码层面插桩(instrumentation):VM 会改写 code object 中的指令(例如把一条指令替换为 INSTRUMENTED_LINE 之类的特殊指令并保存原始操作码),让指令执行到该位置时去查询"哪个工具监听、该调哪个回调"。

关键数据路径是 Include/internal/pycore_instruments.h 中定义的 _PyCoMonitoringData(挂载在 PyCodeObject._co_monitoring 上)以及 _Py_LocalMonitors / _Py_GlobalMonitors(每事件用一个字节的位图记录哪些工具在位,即"工具集")。由于每事件 8 个工具位可用 1 个 uint8_t 表达,判断"某个事件是否有工具监听"只需一次内存读与一次按位与,这是它能够低开销运行的结构基础。

由于插桩会改写正在执行的字节码,必须先停止所有线程再改。因此 set_events / set_local_events / restart_events 乃至启用 sys.settrace 时,源码都遵循同一范式:

_PyEval_StopTheWorld(interp)   # 1. 停止世界,保证无线程正在执行被改代码
... 修改插桩 / 更新版本号 ...
_PyEval_StartTheWorld(interp)  # 2. 恢复运行

Python/instrumentation.cset_events 实现。

6.2 事件触发与 C 层 Fire 接口

当某个被监控位置真正执行时,VM 通过 _Py_call_instrumentation* 系列内部函数(pycore_instruments.h)把事件分发给对应工具的回调。面向扩展模块作者,CPython 在 3.13 起提供了 C 级监控 API,完整文档见 Doc/c-api/monitoring.rst,头文件声明位于 Include/cpython/monitoring.h

  • 每个事件对应一个 PyMonitoring_FireXxxEvent(...) 接口(如 PyMonitoring_FirePyStartEventPyMonitoring_FireLineEventPyMonitoring_FireCallEvent),用于扩展在模拟 Python 代码执行时主动触发监控事件
  • 这些函数接收一个 PyMonitoringState 结构(封装事件的激活状态)以及事件参数:codelikeCodeType 或模拟它的对象)、指令偏移,以及部分事件特有的参数;
  • VM 在触发事件时会自动禁用 tracing,用户代码无需自己处理重入问题;
  • 调用监控函数时不应处于异常已设置状态(文档明确列出的少数"与当前异常配合工作"的接口除外);
  • 所有 Fire 函数成功返回 0、出错返回 -1 并设置异常;
  • 注意:监控 API 目前没有受限 API(Limited API)monitoring.h 顶部注释写明 #ifndef Py_LIMITED_API 守护。

6.3 与 sys.settrace / sys.setprofile 的关系

sys.monitoring 并未完全取代旧机制——从 pycore_instruments.h 可以看出,解释器内部把 sys.settracesys.setprofile 映射为两个保留工具 ID(6 与 7),通过 Python/legacy_tracing.c 把旧 API 转译到监控框架上。也就是说:新旧两套追踪体系底层共用同一套监控/插桩机制,且解释器利用 0~5 之外预留的这两个位,使旧 API 与 sys.monitoring 用户工具互不干扰。

6.4 与其他文档/测试的交叉印证

仓库中与 sys.monitoring 相关的第一手资料还包括:

七、实践要点与最佳实践小结

回顾文档与源码,落地一个 sys.monitoring 工具时应遵循以下要点:

  1. 命名空间,不可导入:统一 import sys 后用 sys.monitoring / sys.monitoring.events
  2. ID 即契约:0~5 任选,优先使用预定义常量(调试器用 DEBUGGER_ID、覆盖率用 COVERAGE_ID、Profiler 用 PROFILER_ID),use_tool_id 注册、free_tool_id 释放、get_tool 探测占用;同一 ID 不能重复注册(会抛 ValueError);
  3. 事件集合用按位或:所有 events 常量都是 2 的幂;NO_EVENTS(0) 用于显式关闭与空集比较;
  4. 先开事件、再等回调:事件未打开则回调不会被触发;全局与局部都打开也只触发一次,回调需容忍两类来源;
  5. CALL 附属规则:想收 C_RETURN/C_RAISE,必须同时监听 CALL,否则 set_events 会直接抛 ValueError
  6. 按位置禁用是性能关键:局部事件在回调中返回 DISABLE 即关掉当前位置,实现"除断点外零开销";restart_events() 可全局复活所有被禁位置;3.15 起其他事件也可按整个 code object 禁用;
  7. 不要用旧 BRANCH:3.14 起已弃用,用 BRANCH_LEFT/BRANCH_RIGHT 替代(性能更好且可独立禁用);
  8. 统计 StopIteration 需双保险STOP_ITERATION 与携带 StopIterationRAISE 语义等价、产生时互换,二者都应处理;
  9. 区分 code 与 callableCALL 系列事件里,第一参 code 是调用发生处的 code object,callable 才是被调用对象;无参数调用时 arg0 == MISSING,实例方法调用时 arg0selfcallable 是类上的函数对象;
  10. 扩展模块请用 C API:需要主动产生事件时参考 Doc/c-api/monitoring.rstPyMonitoring_Fire* 系列,注意目前无 Limited API 版本。

sys.monitoring 让 CPython 首次拥有了一套面向并行多工具、可按 code object 与代码位置精准裁剪的官方监控通道。理解其 Tool ID 隔离、事件分类与 DISABLE/MISSING 契约后,无论是实现断点调试器、轻量级覆盖率统计还是自研采样 Profiler,你都有了比 sys.settrace 更精确、更可控的底层接口。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391