Prhub

#25232 ci: emit machine-readable TIMINGS block at end of run_unittest_files

原始 PR 作者 hnyls2002 合并时间 2026-05-14 12:56 文件变更 1 提交数 2 评论 2 代码增减 +43 / -1

执行摘要

CI 测试框架输出机器可读 TIMINGS 块

CI 测试结果目前通过 grep 调试日志行提取耗时信息,缺乏稳定接口。本次变更提供机器可读的 TIMINGS 块,便于下游 scrapers/dashboards 消费。

作为 CI 基础设施改进,值得合并。后续可基于 TIMINGS 块构建更丰富的可视化或监控面板。新引入的 _repo_relative_path 函数可被其他需要稳定文件路径的模块复用。

讨论亮点

PR 讨论极少,仅有一条 Gemini 机器人的每日配额警告和一条 /tag-and-rerun-ci 命令。无实质 review 意见。

实现拆解

  1. 新增辅助函数_repo_relative_path(p: str) -> str 将任意路径转换为仓库相对路径(如 test/registered/foo.py),确保 CI runner 不同布局下文件 key 稳定。
  2. 引入 file_elapsed 字典:在 run_unittest_files 函数中增加 file_elapsed: Dict[str, float] 字典,记录每个文件的最新一次执行耗时(重试时覆盖旧值)。
  3. 在关键位置记录耗时:在 run_one_file 正常返回时记录 file_elapsed[filename] = elapsed;在超时异常处理分支也记录 file_elapsed[filename] = float(timeout_per_file),确保超时文件出现在 TIMINGS 块中。
  4. 输出 TIMINGS 块:在函数末尾,遍历 file_elapsed,对每个文件输出一行 JSON:{"file": repo_relative_path, "passed": fname in passed_set, "elapsed": round(elapsed)}。用 '========== TIMINGS BEGIN ==========''========== TIMINGS END ==========' 固定包裹。
  5. 保持人类可读输出:原有的 PASSED/FAILED/RETRIED 段落不变,仅新增机器可读块。
文件 模块 状态 重要度
python/sglang/test/ci/ci_utils.py 测试工具 modified 5.56

关键符号

_repo_relative_path run_unittest_files

关键源码片段

python/sglang/test/ci/ci_utils.py test-coverage

唯一变更文件,包含 TIMINGS 块输出逻辑和 _repo_relative_path 辅助函数

# python/sglang/test/ci/ci_utils.pydef _repo_relative_path(p: str) -> str:
    """Return path stripped to repo-relative form (e.g. 'test/srt/foo.py').    Used in the machine-readable TIMINGS block so downstream scrapers
    get a stable key regardless of CI runner checkout layout.
    """
    if not os.path.isabs(p):
        p = os.path.join(os.getcwd(), p)
    marker = "/sglang/"
    idx = p.rfind(marker)
    return p[idx + len(marker):] if idx >= 0 else p
​
​
def run_unittest_files(
    files: Union[List[TestFile], List[CIRegistry]],
    timeout_per_file: float,
    continue_on_error: bool = False,
    enable_retry: bool = False,
    max_attempts: int = 2,
    retry_wait_seconds: int = 60,
):
    # ... 原有逻辑不变 ...
    # Per-file elapsed seconds, latest attempt wins.
    file_elapsed: Dict[str, float] = {}
​
    for i, file in enumerate(files):
        # ... 文件循环 ...
        def run_one_file(filename, capture_output=False):
            # ... 原有逻辑 ...
            elapsed = time.perf_counter() - file_tic
            file_elapsed[filename] = elapsed # 记录耗时,重试覆盖
            # ... 其余代码 ...
        # ... 重试、超时处理等 ...
        # 超时分支中:
        except TimeoutError:
            kill_process_tree(process.pid)
            time.sleep(5)
            file_elapsed[filename] = float(timeout_per_file) # 超时也记录
​
    # 所有文件执行完毕后,输出 TIMINGS 块
    passed_set = set(passed_tests)
    logger.info("========== TIMINGS BEGIN ==========")
    for fname, elapsed in file_elapsed.items():
        logger.info(
            json.dumps({
                "file": _repo_relative_path(fname),
                "passed": fname in passed_set,
                "elapsed": round(elapsed),
            })
        )
    logger.info("========== TIMINGS END ==========")

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

本次变更仅涉及 CI 测试框架的工具函数,不改变测试执行逻辑,风险极低。可能的风险是 TIMINGS 块格式变更或 JSON 序列化异常导致下游消费端解析失败,但由于输出到日志而非关键 pipeline,影响有限。

影响范围仅限于 CI 测试流程中的 run_unittest_files 函数。对开发者本地运行无影响。下游仪表盘可按契约解析 TIMINGS 块,替代不稳定的 grep 方式。

下游解析契约变更风险

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论