Metadata-Version: 2.4
Name: service-host-profiling
Version: 0.7.6
Summary: Opt-in Python Host spans with JSONL storage and offline Chrome Trace export
Author: Host Service Profiler contributors
License: 木兰宽松许可证， 第2版
        
        2020年1月 http://license.coscl.org.cn/MulanPSL2
        
        您对“软件”的复制、使用、修改及分发受木兰宽松许可证，第2版（“本许可证”）的如下条款的约束：
        
        0.   定义
        
        “软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
        
        “贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
        
        “贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
        
        “法人实体” 是指提交贡献的机构及其“关联实体”。
        
        “关联实体” 是指，对“本许可证”下的行为方而言，控制、受控制或与其共同受控制的机构，此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
        
        1.   授予版权许可
        
        每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可，您可以复制、使用、修改、分发其“贡献”，不论修改与否。
        
        2.   授予专利许可
        
        每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的（根据本条规定撤销除外）专利许可，供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求，不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地，就“软件”或其中的“贡献”对任何人发起专利侵权诉讼（包括反诉或交叉诉讼）或其他专利维权行动，指控其侵犯专利权，则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
        
        3.   无商标许可
        
        “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可，但您为满足第4条规定的声明义务而必须使用除外。
        
        4.   分发限制
        
        您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发，不论修改与否，但您必须向接收者提供“本许可证”的副本，并保留“软件”中的版权、商标、专利及免责声明。
        
        5.   免责声明与责任限制
        
        “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下，“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任，不论因何种原因导致或者基于何种法律理论，即使其曾被建议有此种损失的可能性。
        
        6.   语言
        
        “本许可证”以中英文双语表述，中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致，以中文版为准。
        
        条款结束
        
        如何将木兰宽松许可证，第2版，应用到您的软件
        
        如果您希望将木兰宽松许可证，第2版，应用到您的新软件，为了方便接收者查阅，建议您完成如下三步：
        
        1， 请您补充如下声明中的空白，包括软件名、软件的首次发表年份以及您作为版权人的名字；
        
        2， 请您在软件包的一级目录下创建以“LICENSE”为名的文件，将整个许可证文本放入该文件中；
        
        3， 请将如下声明文本放入每个源文件的头部注释中。
        
        Copyright (c) [Year] [name of copyright holder]
        [Software Name] is licensed under Mulan PSL v2.
        You can use this software according to the terms and conditions of the Mulan PSL v2.
        You may obtain a copy of Mulan PSL v2 at:
                 http://license.coscl.org.cn/MulanPSL2
        THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
        EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
        MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
        See the Mulan PSL v2 for more details.
        Mulan Permissive Software License，Version 2
        Mulan Permissive Software License，Version 2 (Mulan PSL v2)
        
        January 2020 http://license.coscl.org.cn/MulanPSL2
        
        Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
        
        0. Definition
        
        Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
        
        Contribution means the copyrightable work licensed by a particular Contributor under this License.
        
        Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
        
        Legal Entity means the entity making a Contribution and all its Affiliates.
        
        Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
        
        1. Grant of Copyright License
        
        Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
        
        2. Grant of Patent License
        
        Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
        
        3. No Trademark License
        
        No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4.
        
        4. Distribution Restriction
        
        You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
        
        5. Disclaimer of Warranty and Limitation of Liability
        
        THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
        
        6. Language
        
        THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
        
        END OF THE TERMS AND CONDITIONS
        
        How to Apply the Mulan Permissive Software License，Version 2 (Mulan PSL v2) to Your Software
        
        To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
        
        Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
        Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package;
        Attach the statement to the appropriate annotated syntax at the beginning of each source file.
        Copyright (c) [Year] [name of copyright holder]
        [Software Name] is licensed under Mulan PSL v2.
        You can use this software according to the terms and conditions of the Mulan PSL v2.
        You may obtain a copy of Mulan PSL v2 at:
                 http://license.coscl.org.cn/MulanPSL2
        THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
        EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
        MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
        See the Mulan PSL v2 for more details.
        
Project-URL: Repository, https://gitee.com/mjsz/service-host-profiling
Project-URL: Documentation, https://gitee.com/mjsz/service-host-profiling/blob/main/README.md
Keywords: profiling,pytorch,npu,sglang,chrome-trace,host-spans
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# service-host-profiling

Lightweight opt-in collection of Host-side service spans for SGLang / PyTorch （NPU） workloads， with offline Chrome Trace export and in-place merging into Torch profiler capture directories.

为 SGLang 与 PyTorch（NPU）训练推理场景补充 **Host 侧服务层区间**的轻量采集工具：在业务代码中显式打点，经离线导出为独立 Chrome Trace，或原位合入 Torch profiler 采集目录，使服务层耗时与算子耗时呈现在同一时间轴上。

## 项目背景

Torch profiler 的 trace 覆盖 CPU 算子、Python 函数与设备 kernel，但 HTTP 请求处理、数据加载与预处理、调度锁等待、异步结果等待、执行器边界、结果发送等服务层耗时在 trace 中没有对应区间，设备空隙的耗时来源因此难以定位。

本工具的工作方式：

- 在实际执行工作的线程内以显式打点包住服务段，记录墙钟区间；
- 原始数据写入独立目录中的 JSONL，与 Torch 采集目录完全分离；
- 离线合入时原位更新各 rank 的 trace，Torch profiler 已有的事件、顺序、属性与调用关系全部保留，仅补充缺失的服务段。

## 功能特性

- **默认关闭，按需启用**：不设置开关即完全不采集，业务代码无需改动；启用时设置环境变量 `HOST_SERVICE_PROFILER_ENABLE=1`。关闭状态下调用点仅保留基础调用成本（见“性能与开销”）。
- **零依赖**：仅使用 Python 标准库，pip 安装即用，无需编译，无第三方运行依赖。
- **打点开销低且恒定**：每对打点约 0.4 μs，不随区间时长或已采集条数增长；落盘集中在 `stop()` 一次完成，不占用采集窗口。
- **数据自主可控**：原始数据为调用方指定目录下的 JSONL，保留真实操作系统 PID 与线程身份；属性支持延迟到 `stop()` 时求值。
- **跨函数、跨线程、跨进程的 flow**：`flow_*` / `process_flow_*` / `thread_flow_*` 三族按名字（可加 `flow_id`）配对，没有句柄、不需要改调用链签名；未配对的孤立点直接丢弃，不制造假区间。
- **合入不破坏原有数据**：合入 Torch profiler trace 时，已有事件、顺序、属性与调用关系原样保留，仅补充服务段；文件缺失、匹配歧义、时钟未校准均会在写入前报错，重跑同一映射不会重复插入。

## 安装

Python 3.10 及以上。仓库名、pip 包名、CLI 命令统一为 `service-host-profiling`，导入名为 `service_host_profiling`。

```bash
python -m pip install service-host-profiling
```

源码安装：

```bash
git clone https://gitee.com/mjsz/service-host-profiling.git
python -m pip install -e .
```

启用采集须在每个采集进程**首次 import 之前**设置环境变量 `HOST_SERVICE_PROFILER_ENABLE=1`，仅取值恰为 `1` 时开启，默认关闭，进程运行中修改不生效。各进程独立 start/stop，同一进程内线程共享会话。采集 API 共十六个函数（见下方 API 表），离线解析不受开关影响。

## 使用指南

### 场景一：仅采集 Host 服务区间

不依赖 Torch profiler，单独查看服务层各段耗时。

#### 采集

```python
import service_host_profiling as hp

hp.start("profiles/run-001/host")
hp.process_attr({"rank": 0, "world_size": 2, "model": "llama-3"})   # 进程自己的事实
span_id = hp.span_start("host.prepare", {"component": "scheduler"})
try:
    run_business_work()
finally:
    hp.span_attr(span_id, {"queue_depth": depth})   # 结束时才知道的属性，补一层
    hp.span_end(span_id)
closed = hp.stop()    # 停止采集并写出 JSONL；开关关闭时为 None
```

| 函数 | 说明 |
|---|---|
| `hp.start(output_dir, session_id=None)` | 开启本进程采集，返回将生成的 JSONL 路径，`stop()` 成功前不可读取。 |
| `hp.span_start(name, attrs=None)` | 记录区间开始，返回本线程 span_id；`attrs` 可为字典或零参函数，延迟到 `stop()` 时才求值。 |
| `hp.span_attr(span_id, attrs=None)` | 追加一层属性，停止时按调用顺序合并：同名键后写覆盖、未触及的键保留；`span_end` 之后仍可调用。 |
| `hp.process_attr(attrs=None)` | 补充进程级属性（rank、world size、组件、模型、构建版本）。立即求值与合并，不进热路径；同名键后写覆盖。**属于进程而非会话**，一次注册后每次采集都会带上。 |
| `hp.span_end(span_id)` | 结束区间；须与 `span_start` 在同一线程、同一函数内，且恰好结束一次。 |
| `hp.stop()` | 停止采集并写出 JSONL，返回文件路径。 |
| `hp.profile(name, attrs=None)` | 装饰同步函数，自动开始与结束，业务异常原样抛出；包住整条函数时优先用它。 |
| `hp.flow_start/flow_step/flow_end(name, attrs=None, *, flow_id=None)` | 全域 flow 点：同一份导出内的线程、进程都能按名字配对；没有句柄，停机后离线配对。 |
| `hp.process_flow_start/step/end(...)` | 只在本进程内配对。 |
| `hp.thread_flow_start/step/end(...)` | 只在本线程内配对。 |
| `hp.is_enabled()` | 返回本进程采集是否开启。 |

未闭合的区间在 `stop()` 时计入 `dropped_spans` 丢弃；span_id 不能跨线程使用。

**进程属性**：`process_attr` 描述的是进程本身，不挂在任何 span 上。它在调用点立即求值和合并（不读时钟、不进热路径），所以写错会被当场报错，而不是留到 `stop()`；重复调用按 key 合并，后写覆盖前写。属性存在进程上、不随某次采集结束而清空，所以常驻服务可以在启动时注册一次（rank、模型、构建版本），之后每次采集都自动带上；在第一次 `start()` 之前、或两次采集之间调用同样会被记住。导出时进程轨道会多一条 `process_labels`，内容是 `key=value` 文本，供 Perfetto/chrome://tracing 直接显示；带类型的原值在 `hostServiceProfiler.sources[].processArgs`，合入 Torch trace 后同样保留。只写了进程属性、一次打点都没有的进程不算空输入，仍会出现在导出里。

**反复开关**：`start()`/`stop()` 可以在一个进程里反复调用，跟随 torch profiler 一轮一轮地开和关。每次采集各自写一个独立文件（原子发布），互不覆盖；同一个进程的多次采集共享同一个 `process_uuid`，并用从 0 递增的 `session_index` 标记先后，导出时 `sources[]` 按 pid、再按这个序号排列，能直接看出哪几个文件是同一进程的第几轮。因为全程用同一个 monotonic 时钟，不同轮次的区间可以画在同一条时间轴上。跨越 `stop()` 仍未闭合的区间按原规则丢弃并计入 `dropped_spans`；上一轮的 span_id 留到下一轮再用也是安全的，它在新一轮里不指向任何东西，直接被忽略。

```python
hp.process_attr({"rank": rank, "world_size": world})   # 服务启动时注册一次

for step in range(steps):
    if step in windows:                                # torch profiler 打开
        hp.start("profiles/run-001/host")
        torch_profiler.start()
    ...
    if step in windows_close:
        torch_profiler.stop()
        hp.stop()                                      # 本轮落盘，下一轮另起文件
```

一个进程一次只能有一个会话，重复 `start()` 会报 `RuntimeError`；`stop()` 前必须已 `start()`。

**区间不跨函数**：一个 span 要么是整条函数（`profile` 装饰器，或首行 `span_start` + `finally` 里 `span_end`），要么在同一个函数内开始并结束。不要 A 函数开始、B 函数结束，也不要把 span_id 当返回值、参数或实例属性传给别的函数——跨函数的区间在评审里看不到闭合点，异常路径容易漏掉 `end`，最后静默计入 `dropped_spans`；要观察被调函数内部的耗时，就让被调函数自己打自己的 span，不要在调用链上拉一条长区间把「等待」和「执行」压成一段。

**跨函数用 flow**：确实要跨函数、跨线程甚至跨进程时（网关收请求 → 排队 → worker 执行 → 另一个 rank 收尾），用 flow 家族。它按名字配对、没有句柄、不返回任何 id，配对在停机后离线完成。

```python
def handle_request(request):
    hp.flow_start("host.request", {"route": request.route}, flow_id=request.id)
    try:
        dispatch(request)
    finally:
        hp.flow_end("host.request", {"status": "handled"}, flow_id=request.id)
```

| 家族 | 配对范围 |
|---|---|
| `flow_start/step/end` | 全域：同一份导出内的所有线程与进程 |
| `process_flow_start/step/end` | 本进程 |
| `thread_flow_start/step/end` | 本线程 |

`start, end` 配成一段；`start, step, end` 配成两段（`step` 既是前段终点也是后段起点）；`start, step` 里 `step` 充当 end，`step, end` 里 `step` 充当 start，连续的 `step` 之间也成段；`start, start, end` 保留后一个 start、前一个丢弃；只有 start、只有 end、只有 step、`end` 之后的 step 都直接丢弃，并计入导出 metadata 的 `unpairedFlowPoints`。同名的并发 flow 必须给 `flow_id`（典型值是请求 ID），否则相邻点会被串在一起。段在图上是一条落在起点线程的区间，外加一对指向终点所在轨道的箭头。

#### 解析

JSONL 为原始数据，经命令行导出为 Chrome Trace：

```bash
service-host-profiling export profiles/run-001/host -o profiles/run-001/host.trace.json
service-host-profiling export a.jsonl b.jsonl -o merged.trace.json
service-host-profiling --skills-path      # 打印随包技能目录，供 agent 读取
```

`--help` 会同时打印技能文件路径，agent 不必猜去哪里读用法与约束。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `PATH ...` | 目录或文件，可多个 | 是 | 一个采集目录（取其下 `*.jsonl`）、一个或多个已 `stop()` 的 Host JSONL、或历史 SQLite。同一主机的输入合成一份 trace；跨主机报错，需分开导出。 |
| `-o, --output` | 文件路径 | 是 | 导出的 Chrome Trace JSON 输出路径。 |
| `-r, --recursive` | 开关 | 否 | 输入为目录时连子目录一起扫。默认只取直接子文件，因为一次采集的所有进程文件写在同一个目录里。 |

目录按“一次采集”理解：同目录下的文件会被当成同一次运行的多个进程。`export` 不校验它们是否真属同一批——把多个 run 的中间目录扫进来会合成一条时间轴对不上的 trace。`*.jsonl.pending` 不会被收，未成功 `stop()` 的会话进不来。

零记录的输入（进程起来了但一次打点都没落，`written_spans` 与 `written_flows` 同时为 0，且没有 `process_attr` 属性）在校验通过后被跳过：不解析行、不占空进程轨，导出 metadata 的 `skippedEmptySources` 记个数。跳过前会核对文件体确实只有 metadata 行，两者不符时报错。全部输入都是零记录则直接报错，不会产出空 trace。

同一主机的多个进程文件可写在同一条命令里合成一份 trace；跨主机的数据无法对齐时间，须分别导出。

#### 查看

导出结果可使用 `chrome://tracing`、Perfetto 等标准 Chrome Trace 工具打开：

- 每个采集进程一条 `Host process {PID}` 进程轨道，其下每个原生线程一条 `Host thread {TID}` 轨道，服务段按普通时间区间显示；多进程文件一同导出时共享同一时间零点（最早会话的起点）。
- 导出文件顶层的元数据记录每份来源的 `writtenSpans`、`droppedSpans`、会话与起止时间，可核对是否有丢段。

### 场景二：Host 与设备联合分析（与 Torch profiler 集成）

在 Torch profiler 的同一时间轴上回答“设备空隙期间 Host 在执行什么”。

#### 采集

Host 数据与 Torch 数据分目录存放：

```text
profiles/run-001/
├── host/      # 本工具原始 JSONL，可含多个进程文件
└── torch/     # Torch profiler 的完整采集根目录
    ├── rank0_ascend_pt/ASCEND_PROFILER_OUTPUT/trace_view.json
    └── rank1_ascend_pt/ASCEND_PROFILER_OUTPUT/trace_view.json
```

将本工具的采集窗口置于应用已有的 Torch profiler 窗口之内：

```python
import service_host_profiling as hp

profiler.start()                      # 应用已有的 Torch / torch_npu profiler
try:
    hp.start("profiles/run-001/host")
    try:
        span_id = hp.span_start("host.prepare")
        try:
            run_business_work()       # 先等待本窗口内的工作线程任务完成
        finally:
            hp.span_end(span_id)
    finally:
        closed_host = hp.stop()
finally:
    profiler.stop()
```

待所有 rank 的 Torch trace 完成导出后进行合入。SGLang 等具备捕获生命周期的框架可使用 `hp.attach_sglang(...)`、`hp.attach_torch_profiler(...)` 辅助归档；PID 核实、时钟校准与 daemon 边界见[接入指南](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/INTEGRATION.md)。

#### 解析（合入）

```bash
service-host-profiling merge-dir profiles/run-001/host \
  --torch-dir profiles/run-001/torch \
  --pid-map '{"1234":214152,"1235":214153}' \
  --clock-offset-ns '{"1234":100,"1235":200}'
```

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `HOST_CAPTURE_DIRECTORY` | 目录 | 是 | 本次采集的 Host 根目录，须在 Torch 目录之外。 |
| `--torch-dir` | 目录 | 是 | 包含**全部 rank** 的 Torch 采集根目录；只传单个 `*_ascend_pt` 目录会被拒绝。 |
| `--pid-map` | JSON 对象 | 视情况 | Host JSONL 记录真实操作系统 PID，与 trace 内的进程 PID 通常不同；trace 的 `process_name` 元数据带有可证实的 `os_pid` 且唯一对应时可省略，否则必须提供。 |
| `--clock-offset-ns` | 整数或 JSON 对象 | 可省略 | Host 单调时钟与 trace 时钟零点不同，合入需要换算：`trace 时间 = Host 时间 + 偏移`。省略时按 rank 从 `profiler_info_*.json` 自动推导，并通过两项自检；多 rank 按 PID 分别给出时以显式值为准。 |
| `--clock-anchors-ns` | JSON 对象 | 可省略 | 每进程两点时钟锚点；与 `--clock-offset-ns` 互斥，给出时覆盖自动推导，需要亚毫秒对齐时使用。 |

示例中的数字仅示意参数格式。时钟两项都可省略：省略时从各 rank 的 `profiler_info_*.json` 推导常数偏移，仅当它与本次 Host epoch 样本（≤1 ms）和 trace 时间范围同时相容才使用，否则拒绝并要求显式提供。每份 Host JSONL 按进程身份唯一匹配一份 trace，文件缺失、匹配歧义或时钟未校准都会在写入前报错；重跑同一映射不会重复插入事件。若仅需 Host 单独视图，按场景一导出。

#### 查看

合入后的 `trace_view.json` 需在 MindStudio Insight 中**重新导入**（已导入的旧缓存不会自动刷新），时间线中同一 rank 的 Host/Python 进程下将出现服务段，与原有 CPU/NPU 事件对齐；Torch profiler 已有的事件、顺序、属性与调用关系原样保留，目录内其他文件不受影响。原 trace 的 `thread_name` 元数据带有可证实的 `os_tid`、`native_tid` 时，Host 段复用原线程轨道，否则在同一进程下建立独立轨道。

## 性能与开销

三层成本同轮实测（2026-10-29，Windows x64，CPython 3.12.14，`python tools/benchmark_deferred_attrs.py --iterations 30000 --repeats 7`；每条为一对 `span_start`/`span_end`，取中位数）：

| 场景 | 耗时（ns/条） |
|---|---:|
| 空循环（基线） | 15 |
| 接入打点，开关关闭 | 58 |
| 开关开启，处于采集会话内 | 394 |

- **接入成本约 58 ns/条**：打点调用点在开关关闭时同样执行，这是接入本工具引入的固定成本，与开关状态无关。
- **开启后一对打点约 0.4 μs**，固定不随区间时长增长。其中约 0.18 μs 是两次高精度时钟读取（Windows 上 `perf_counter_ns` 每次约 90 ns，`monotonic_ns` 粒度是 15.6 ms，不能用来量区间），约 0.08 μs 是两次函数调用，其余是线程缓冲查找与四次列写入。
- **结论：10 ms 推理窗口内采集 100 个区间约 0.4%**。估算式：`影响 ≈ 0.4 μs × 区间数 ÷ 窗口时长`；10 ms 窗口控制在约 250 个区间以内即可保持 1% 以内。
- **打点深度建议：不超过 eager 模式的 layer 边界，不应置于循环体内逐次打点**——区间数随迭代次数线性增长，例如 1000 次迭代逐次打点的开销即约为窗口时长的 4%；更细粒度的算子与设备数据由 Torch profiler 覆盖。

属性与 flow 成本（同轮、同一脚本；跨日或换解释器不可直接相减，只看组内关系）：

| 打点方式 | 关闭时（ns/条） | 开启时（ns/条） | `stop()`（ms/3 万条） |
|---|---:|---:|---:|
| 无属性 | 58 | 394 | 158 |
| `{"a": expensive()}` | 253 | 733 | 176 |
| `{"a": lambda: expensive()}` | 132 | 657 | 188 |
| `lambda: {"a": expensive()}` | 95 | 518 | 203 |
| 复用已创建的零参函数 | 58 | 448 | 191 |
| 一层 `span_attr` | 281 | 830 | 181 |
| 三层 `span_attr` | 755 | 2397 | 234 |
| `flow_start` 一次 | 52 | 315 | 173 |
| `flow_start` + `flow_end` 一对 | 85 | 658 | 368 |

- 属性值延迟至 `stop()` 时求值；整字典闭包（`lambda: {...}`）把字典构造也推迟，比逐值构造省约 30%，代价是计算全部移到停止阶段。
- 一层 `span_attr` 比在 `span_start` 传同样属性多约 0.1 μs（一次额外调用，不读时钟）；三层的增量主要来自每层现构造 dict 并计算值，约 +0.8 μs/层，所以应攒成一个 dict 再追加。
- 每条 span 只写四个槽位，不再为它分配记录对象，第二次追加属性也不分配；因此采集成本与会话内已采集条数基本无关。旧实现（每条 span 一个 list）在 3 万条时约 0.45 μs，30 万条约 0.70 μs，100 万条约 0.85–1.0 μs，列式实现在同样规模下保持约 0.5 μs。
- flow 点仍走进程级列表：一次时钟读加一次 append，比一对 span 便宜约 0.08 μs。取号、配对、分段与箭头生成都在停机后的解析阶段，不占采集窗口。
- 落盘集中在 `stop()`，约 5 ms/千条，不占采集窗口。

以上均为局部微基准；**真实 SGLang 负载下的扰动尚未测量**，验收方法与验证分界见[验证范围](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/VALIDATION.md)。

## 限制说明

- 新增区间为墙钟时间，不包含 CPU 占用与等待原因、GIL 状态、调用栈、设备采样，不应将等待时间解释为上述数据。
- 区间必须落在单个函数内（整条函数或函数内的一段）；跨函数传递 span_id 属于未定义行为，漏闭合会在 `stop()` 时被丢弃而不是报错。
- flow 的段是「起止两点的墙钟差」，不是「这一段在跑」；未配对的点被丢弃，导出时的 `unpairedFlowPoints` 就是唯一的丢弃口径；跨 rank 的 flow 只在一次性 `export` 多份文件时配对，`merge-dir` 不跨 Trace 拼接。
- 采集内存随记录数增长，长窗口采集需控制区间数量。flow 点存在进程级列表里，数量同样计入内存。
- 当前 Python/JSONL 方案尚未完成 MindStudio Insight 界面验收；历史原生实现的性能与界面结论不适用于本版本。

## 项目文档

- [Agent 技能](https://gitee.com/mjsz/service-host-profiling/blob/main/python/service_host_profiling/skills/service-host-profiling/SKILL.md)：面向 AI 代理的用法速览，接口；约束与参考资料；
- [使用手册](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/USAGE.md)：采集 API、属性写法、CLI 参数与失败处理。
- [接入指南](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/INTEGRATION.md)：生命周期、PID/TID、时钟校准、daemon 与 Insight 边界。
- [格式约束](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/CONTRACT.md)：JSONL schema 与解析输出契约。
- [验证范围](https://gitee.com/mjsz/service-host-profiling/blob/main/docs/VALIDATION.md)：已验证与未验证清单、微基准数据。

## 开源许可

本项目以木兰宽松许可证 v2（Mulan PSL 2）发布，详见 [LICENSE](https://gitee.com/mjsz/service-host-profiling/blob/main/LICENSE)。项目源自 Ascend msserviceprofiler 的定向裁剪，渊源见 [UPSTREAM.md](https://gitee.com/mjsz/service-host-profiling/blob/main/UPSTREAM.md)。
