from __future__ import annotations from dataclasses import dataclass, field, asdict import json SCHEMA_VERSION = 4 @dataclass class Environment: python: str platform: str processor: str timerResolution: float @dataclass class WallTime: seconds: float unit: str = "s" @dataclass class Calibration: # v4 新增:cProfile 开销校准系数,用于把 instrumented wall-time 折算为干净耗时。 # 用户脚本只 exec 一次(单跑架构)的代价是 wall-time 带 cProfile 开销;启动期 # 用同一段 tight-loop 各跑一次(裸 vs 仪器化),ratio = instrumented / clean, # 用户 wall_time = instrumented_user / ratio。 ratio: float # 校准负载名:方便 debug 时看出是哪段负载产生的系数;目前固定为 "tight-loop" workloadName: str = "tight-loop" # 校准负载在 cProfile 下的耗时(秒)—— 透传给 UI 方便 debug instrumentedWorkloadSec: float = 0.0 # 校准负载裸跑的耗时(秒) cleanWorkloadSec: float = 0.0 @dataclass class FunctionNode: id: str # "file:line:name" file: str line: int name: str cumtime: float tottime: float ncalls: int percallTot: float # 顶层模块名(用于 scope=all 时的 UI 分类): # "" 用户脚本 / "json" / "numpy" / "" 等。 # 提取逻辑见 engine/structure._top_module;这一层做归类, # 让 UI 不必重新解析文件路径(路径 normalize 在 OS 间不一致)。 module: str = "" # 帧来源(v3 新增;用于 UI 按 origin 分组): # "user" 用户脚本 / "stdlib" 标准库 / "third_party" 第三方包 / # "builtin" 内置(C 实现的 builtin) / "frozen" frozen importlib 等 / # "other" 兜底(未匹配任何已知来源,例如奇怪的 帧) # # 优先用 sys.stdlib_module_names 校准(3.10+),降级用路径启发式: # /Lib/ 或 /lib/pythonX.Y/ → stdlib;含 site-packages/dist-packages → third_party。 # 推导逻辑见 engine/structure._classify_origin。 origin: str = "" @dataclass class FlameNode: name: str value: float children: list = field(default_factory=list) @dataclass class AnalysisResult: schemaVersion: int environment: Environment config: dict status: str # ok|syntax_error|runtime_error|timeout error: dict | None wallTime: WallTime | None functions: list flame: FlameNode | None # v4 新增:ok 状态下必有;非 ok(语法错 / 运行时错 / 超时)下为 None。 # 见 Calibration 字段注释 —— 折算 wallTime 时需要,存到 result 里给 UI 看。 calibration: Calibration | None = None def to_json(self) -> str: # ensure_ascii=True(默认):在 GBK locale / 没设 PYTHONUTF8 的环境下 # stdout 不是合法 UTF-8,UI 解析会挂。转义成纯 ASCII 后逐字节一致, # JSON.parse 原生还原。主进程仍然会设那两个环境变量,这里是纵深防御。 return json.dumps(asdict(self), ensure_ascii=True)