news 2026/9/28 6:15:10

Apache Beam 测试指标分析:用 Jupyter Notebook 从 Jenkins 采集与剖析 PreCommit 任务耗时

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache Beam 测试指标分析:用 Jupyter Notebook 从 Jenkins 采集与剖析 PreCommit 任务耗时

【免费下载链接】beam

Apache Beam is a unified programming model for Batch and Streaming data processing.

项目地址:https://gitcode.com/gh_mirrors/beam18/beam
点击查看免费下载

Apache Beam 的.test-infra/jupyter目录承载着测试基础设施中一项重要工作:用 Jupyter Notebook 从 Jenkins 上抓取、整理并可视化 Beam 各语言 PreCommit 任务的测试指标。本文基于该目录下的 README 与实战 notebook precommit_job_times.ipynb,完整讲解环境搭建、Jenkins API 数据采集、任务排队/总耗时分析、95 分位统计以及单测耗时排查的整套流程,读完即可复现 Beam 测试指标的采集与分析环境。

目录定位:测试指标分析工作台

.test-infra/jupyter目录的唯一用途,就是存放用于收集和分析测试指标的 Jupyter notebooks。与之对应的核心分析对象,是 Jenkins 上 Apache Beam 项目持续运行的 CI 任务——尤其是各语言的 PreCommit(提交前)定时任务。目录结构非常精简:

.test-infra/jupyter/ ├── README.md # 环境搭建与贡献规范说明 └── precommit_job_times.ipynb # PreCommit 任务耗时分析 notebook

notebook 本身(precommit_job_times.ipynb)围绕"Precommit Job Times"这一主题,按数据流划分为四个阶段:从 Jenkins 拉取构建级统计 → 绘制排队/总耗时趋势图 → 计算 95 分位耗时指标 → 深入单个测试用例层级的耗时排序。

上下文提醒:根据 .test-infra/jenkins/README.md,自 2023 年 11 月起 Beam 的 CI 已逐步迁移到自托管 GitHub Actions,Jenkins 上的任务计划关闭;但本 notebook 及其背后完整的 Jenkins 指标采集方法论,对理解 CI 数据分析和迁移前的 Beam 测试体系仍有重要参考价值(下文分析以仓库中 notebook 实际代码为准)。

环境搭建:pip + venv 快速启动

README 给出了基于 Linux 的安装指引,核心思路是使用 venv 隔离环境并安装 Jupyter:

python3 -m venv ~/virtualenvs/jupyter source ~/virtualenvs/jupyter/bin/activate pip install jupyter # Optional packages, for example: pip install pandas matplotlib requests cd .test-infra/jupyter jupyter notebook # Should open a browser window.

各步骤要点:

  • venv 隔离:将环境创建在~/virtualenvs/jupyter,激活后所有包安装不会污染系统 Python;source激活是每次会话进入环境的前提。
  • 核心依赖:jupyter是运行 notebook 的基础;pandas、matplotlib、requests为可选但实际必备——notebook 的导入单元格(import pandas as pd、import numpy as np、import matplotlib.pyplot as plt、import matplotlib.dates as md、import requests)直接依赖它们完成数据处理、绘图与 HTTP 请求。
  • 工作目录:必须在.test-infra/jupyter下启动jupyter notebook,保证 notebook 的相对资源访问正常。
  • 启动形态:jupyter notebook默认打开浏览器窗口;notebook 元数据中的kernelspec声明为 Python 3 内核。

一个值得注意的细节:notebook 的说明中特别提到"可能需要重启 Jupyter 才能让 matplotlib 正常工作"——这是因为 matplotlib 的后端加载时机问题,遇到绘图不显示时优先尝试重启内核。

数据采集:读懂 Jenkins API 请求协议

notebook 的第一段代码块完成了核心数据抓取,其设计对任何 Jenkins API 使用者都有直接参考意义。

API 地址与 tree 参数(防封禁关键)

url = 'https://ci-beam.apache.org/job/%s/api/json' % job_name params = { 'tree': '%s[result,number,timestamp,actions[queuingDurationMillis,totalDurationMillis]]' % builds_key} r = requests.get(url, params=params) data = r.json()

notebook 在开头明确警告:对ci-beam.apache.org的请求必须携带?depth=或?tree=参数,否则 IP 会被封禁(这一规则来自 ASF Jenkins API 的使用政策)。代码中对tree参数做了精确限定,只请求最小必要字段:

  • result:构建结果(如SUCCESS、FAILURE);
  • number:构建号;
  • timestamp:构建开始时间(毫秒级 Unix 时间戳);
  • actions[queuingDurationMillis,totalDurationMillis]:来自构建 Action 的排队时长与总时长。

使用tree而非depth是更节省带宽和响应时间的方式——只抓取指定字段,避免拉取每个构建的完整 JSON 对象。

builds 与 allBuilds 的选择

代码中通过可配置变量控制抓取范围:

# Can be 'builds' (last 50) or 'allBuilds'. builds_key = 'allBuilds'
  • builds:最近 50 次构建;
  • allBuilds:全部构建历史。

notebook 默认选择allBuilds,以便支撑后文"4 周 / 1 周 / 1 天"三个时间窗口的切片分析。

目标任务:三大语言的 PreCommit Cron

job_names = ['beam_PreCommit_Java_Cron', 'beam_PreCommit_Python_Cron', 'beam_PreCommit_Go_Cron']

这三个任务正是 Beam Jenkins 上的 PreCommit 定时任务。其命名规则可以从 CI 定义代码交叉印证:在 .test-infra/jenkins/PrecommitJobBuilder.groovy 中,任务名由scope.job("beam_PreCommit_${nameBase}_${nameSuffix}")拼装而成,即"beam_PreCommit_+ 语言/模块基名 + 后缀(如Cron)";BUILD_STATUS.md 的"Pre-Commit Tests Status"表格也逐一列出了beam_PreCommit_Java_Cron、beam_PreCommit_Python_Cron、beam_PreCommit_Go_Cron等任务及其状态徽章。

构建记录的解析模型:Build 类

class Build(dict): def __init__(self, job_name, json): self['job_name'] = job_name self['result'] = json['result'] self['number'] = json['number'] self['timestamp'] = pd.Timestamp.utcfromtimestamp(json['timestamp'] / 1000) self['queuingDurationMillis'] = -1 self['totalDurationMillis'] = -1 for action in json['actions']: if action.get('_class', None) == 'jenkins.metrics.impl.TimeInQueueAction': self['queuingDurationMinutes'] = action['queuingDurationMillis'] / 60000. self['totalDurationMinutes'] = action['totalDurationMillis'] / 60000. if self['queuingDurationMinutes'] == -1: raise ValueError('could not find queuingDurationMillis in: %s', json) if self['totalDurationMinutes'] == -1: raise ValueError('could not find totalDurationMillis in: %s', json)

这一模型揭示了 Jenkins 构建时长数据的底层来源:

  • 构建的时间戳从毫秒 Unix 时间戳转为 pandasTimestamp(utcfromtimestamp(timestamp / 1000)),为后续时间窗口过滤与时间轴绘图做准备;
  • 排队时长(queuing)与总时长(total)并不直接出现在构建顶层,而是藏在actions数组里、由jenkins.metrics.impl.TimeInQueueAction这个_class标识的 Action 提供;
  • 两个时长从毫秒换算为分钟(/ 60000.),统一了后续统计与绘图的量纲;
  • 若找不到该 Action,代码会显式抛出ValueError,防止静默使用 -1 脏数据。

抓取完成后,所有Build对象汇入 DataFrame:

df = pd.DataFrame(builds)

时间窗口切片与耗时趋势可视化

按时间窗口过滤

timestamp_cutoff = pd.Timestamp.utcnow().tz_convert(None) - pd.Timedelta(weeks=4) df_4weeks = df[df.timestamp >= timestamp_cutoff] timestamp_cutoff = pd.Timestamp.utcnow().tz_convert(None) - pd.Timedelta(weeks=1) df_1week = df[df.timestamp >= timestamp_cutoff] timestamp_cutoff = pd.Timestamp.utcnow().tz_convert(None) - pd.Timedelta(days=1) df_1day = df[df.timestamp >= timestamp_cutoff]
  • 当前时间取 UTC(pd.Timestamp.utcnow()),去掉时区信息(tz_convert(None))后与构建的 UTC 时间戳对齐;
  • 用pd.Timedelta分别构造4 周、1 周、1 天三个回溯窗口,得到三份切片 DataFrame,供趋势图与分位统计复用。

绘制耗时趋势图

for job_name in job_names: duration_df = df_4weeks[df_4weeks.job_name == job_name] duration_df = duration_df[['timestamp', 'queuingDurationMinutes', 'totalDurationMinutes']] ax = duration_df.plot(x='timestamp') ax.set_title(job_name)

对每个任务,取最近 4 周数据,以时间戳为 X 轴绘制queuingDurationMinutes与totalDurationMinutes两条曲线,并分别以任务名作为图表标题。这样的图可以直观回答两类问题:

  1. **总耗时(total)**是否随时间恶化,反映 CI 本身的性能回归;
  2. **排队耗时(queuing)**是否偏高,反映执行机资源不足或任务调度拥塞。

95 分位耗时指标:量化 CI 性能基线

数据分析的经典需求是"CI 到底多慢"。notebook 用 95 分位(P95)给出量化答案:

test_dfs = {'4 weeks': df_4weeks, '1 week': df_1week, '1 day': df_1day} metrics = [] for sample_time, test_df in test_dfs.items(): for job_name in job_names: df_times = test_df[test_df.job_name == job_name] for percentile in [95]: total_all = np.percentile(df_times.totalDurationMinutes, q=percentile) total_success = np.percentile(df_times[df_times.result == 'SUCCESS'].totalDurationMinutes, q=percentile) queue = np.percentile(df_times.queuingDurationMinutes, q=percentile) metrics.append({'job_name': '%s %s %dth' % ( job_name.replace('beam_PreCommit_','').replace('_GradleBuild',''), sample_time, percentile), 'totalDurationMinutes_all': total_all, 'totalDurationMinutes_success_only': total_success, 'queuingDurationMinutes': queue, }) pd.DataFrame(metrics).sort_values('job_name')

这一统计逻辑包含三个层次的洞察:

  • 区分全部构建与成功构建:total_all统计所有构建的 P95 总耗时,total_success只统计result == 'SUCCESS'的构建——成功构建的 P95 更能代表"正常状态下的 CI 耗时基线",两者差值可侧面反映失败构建拉长耗时的程度;
  • 独立统计排队耗时:queue单独给出 P95 排队时长,用于评估调度拥塞;
  • 任务名可读化:通过replace去掉beam_PreCommit_与_GradleBuild前缀,让输出的job_name更易读(例如Java_Cron 4 weeks 95th)。

最终以表格形式(pd.DataFrame(metrics).sort_values('job_name'))输出三个时间窗口 × 三个任务 × 三个耗时指标的组合矩阵,为 CI 性能报告提供可直接引用的数字。

深入单测层级:定位最慢的测试用例

趋势图与分位统计回答"CI 多慢",而**定位"哪个测试最慢"**则需要另一条数据链路:testReport API。

按构建抓取测试报告

MAX_FETCH_PER_JOB_TYPE = 5 test_results_raw = [] for job_name in list(df.job_name.unique()): if job_name == 'beam_PreCommit_Go_Cron': # TODO: Go builds are missing testReport data on Jenkins. continue build_nums = list(df.number[df.job_name == job_name].unique()) num_fetched = 0 for build_num in build_nums: url = 'https://ci-beam.apache.org/job/%s/%s/testReport/api/json?depth=1' % (job_name, build_num) print('.', end='') r = requests.get(url) if not r.ok: # Typically a 404 means that the job is still running. print('skipping (%s): %s' % (r.status_code, url)) continue raw_result = r.json() raw_result['job_name'] = job_name raw_result['build_num'] = build_num test_results_raw.append(raw_result) num_fetched += 1 if num_fetched >= MAX_FETCH_PER_JOB_TYPE: break print(' done')

这段代码的设计约束值得展开:

  • 每个任务最多拉取 5 次构建(MAX_FETCH_PER_JOB_TYPE = 5),在样本充分性与 API 负载之间取得平衡;
  • Go 任务被跳过:notebook 用 TODO 注释明确说明"Go builds are missing testReport data on Jenkins",即 Jenkins 端没有 Go 构建的测试报告数据,这是一个真实的平台限制,分析时需留意;
  • 404 处理:testReport/api/json?depth=1返回非 OK 状态通常意味着"该构建仍在运行中"(报告尚未生成),代码打印skipping后跳过,不会中断整个抓取流程;
  • 使用depth=1:此处与主 API 请求不同,testReport 接口要求depth=1展开嵌套的 suites/cases 结构(顶层请求中特意省略了 tree 参数)。

测试用例结果解析与排序

class TestResult(dict): def __init__(self, job_name, build_num, json): self['job_name'] = job_name self['build_num'] = build_num self['name'] = json['name'] self['duration'] = json['duration'] self['className'] = json['className'] self['status'] = json['status']

抓回的原始报告按suites → cases两级结构展开,将每个测试用例(case)转成TestResult记录:

for suite in test_result_raw['suites']: for case in suite['cases']: test_results.append(TestResult(job_name, build_num, case)) df_tests = pd.DataFrame(test_results) df_tests = df_tests.drop(columns=['build_num']) df_tests = df_tests.groupby(['className', 'job_name', 'name', 'status'], as_index=False).max() df_tests = df_tests.sort_values('duration', ascending=False)

关键的处理步骤:

  • 去重取最大值:按className + job_name + name + status分组后取max(),即同一测试用例多次构建出现时只保留最慢的一次,同时自然去除了build_num维度(故先drop该列);
  • 降序排序:按duration从大到小排列,最慢的测试排在最前。

交互式过滤最慢测试

def filter_test_results(job_name, status): res = df_tests if job_name != 'all': res = res[res.job_name == job_name] if status != 'all': res = res[res.status == status] return res.head(n=20) from ipywidgets import interact interact(filter_test_results, job_name=['all'] + list(df_tests.job_name.unique()), status=['all'] + list(df_tests.status.unique()))

最后借助ipywidgets.interact生成交互式控件:用户可从下拉框选择job_name(all或某个具体任务)与status(all或具体状态),即时查看耗时 Top 20 的测试用例(head(n=20))。这是整个分析流程的出口——从"CI 慢了"到"具体是哪个类、哪个用例最耗时",可以直接指导测试优化或失败用例排查。

协作规范:提交 notebook 前清理输出

README 对贡献者提出一条明确要求,是保持仓库整洁的关键约定:

To minimize file size, diffs, and ease reviews, please clear all cell output (cell -> all output -> clear) before committing.

即在提交 notebook 前,通过菜单cell → all output → clear清空所有单元格输出。这样做有三重收益:

  1. 减小文件体积:.ipynb是 JSON 文本格式,大量图片型输出(尤其 matplotlib 图表 base64 编码)会急剧膨胀文件;
  2. 精简 diff:运行时间戳、随机图表数据等每次运行都会变化的输出,若不清空会制造大量无关 diff;
  3. 简化评审:评审者只需关注代码逻辑本身,而不是海量输出截图。

这也解释了仓库中precommit_job_times.ipynb的outputs: []状态——所有单元格均为"未运行输出"的干净形态,与仓库约定完全一致。

与周边测试基础设施的关联

.test-infra/jupyter并非孤立存在,它属于 Beam 测试基础设施的分析侧,与周边组件形成完整闭环:

  • .test-infra/jenkins/:Jenkins 任务定义(Groovy DSL),其中 PrecommitJobBuilder.groovy 定义了 notebook 所分析任务的命名与触发;目录 README 说明其已进入弃用迁移状态;
  • .test-infra/metrics/:Beam 的指标监控栈(InfluxDB 时序库 + Grafana 仪表盘 + PostgreSQL 分析库),提供另一条面向社区与测试结果的指标可视化路径;
  • .test-infra/BUILD_STATUS.md:集中展示 PreCommit/PostCommit 任务状态徽章与触发短语,其中 Pre-Commit Tests Status 表格与 notebook 分析的三个任务一一对应;
  • CI.md:GitHub Actions CI 的说明文档,代表 Beam CI 的当前主流演进方向。

在 Jenkins 时代,这套 notebook 流程承担了"从 Jenkins API 侧拉取数据做自主分析"的轻量职责,与面向生产部署的 InfluxDB/Grafana 监控栈形成互补——前者适合工程师临时探查具体慢测试,后者适合持续观测整体指标。

小结

.test-infra/jupyter用两个文件提供了完整的 CI 指标分析范式:README 给出了可复现的 venv 环境搭建步骤与协作规范,precommit_job_times.ipynb则展示了从 Jenkins API 采集构建级与测试级数据、按时间窗口切片、绘制趋势图、计算 95 分位基线、交互式定位最慢用例的端到端流程。即使 Beam CI 已逐步迁移至 GitHub Actions,这套"用最小化 API 请求(tree 参数)守规矩地采集数据、用 DataFrame 做聚合统计、用分位数量化性能基线、用交互控件下钻到单测"的方法论,对任何需要分析 CI 效率的团队都具备直接的复用价值。

【免费下载链接】beam

Apache Beam is a unified programming model for Batch and Streaming data processing.

项目地址:https://gitcode.com/gh_mirrors/beam18/beam
点击查看免费下载

相关推荐

上一篇:JaCoCo代码覆盖率报告深度解析:看懂每一行数据的秘密
下一篇:【亲测免费】 JavaCC 使用及开发指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 6:14:55

C++ ODBC 开发历程:从踩坑到封装,一套可复用的连接骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 6:14:50

Java Kafka消息队列系统设计:核心链路、集群搭建与避坑实战

简介:一份基于Java的Kafka消息队列系统设计源码,面向需要构建实时数据管道、处理大数据传输与高并发消息场景的Java开发者。整个压缩包共42个文件,大小约77.3MB,其中27个Java源文件承载生产者、消费者及与Kafka集群交互的核心逻辑…

作者头像 李华
网站建设 2026/9/28 6:14:48

多输出BP神经网络回归预测:Matlab实现与调参实战

做回归预测的人早晚会遇到这么一个问题:系统输入是十几个传感器信号,输出却是一组多个目标——比如结构健康监测里的多测点应变、设备故障诊断里的多类特征值、工艺优化里的多个质量指标。用BP神经网络做多个输出数据的回归预测,在Matlab里其…

作者头像 李华
网站建设 2026/9/28 6:14:47

Matlab实现BP神经网络多输出回归预测全流程指南

做工程仿真和实验数据处理的同学,应该经常碰到这种场景:手里有一堆输入参数,需要同时预测好几个输出结果,比如根据材料配方同时预测强度和弹性模量,根据工艺参数同时预测温度和压力。这种问题在回归预测领域叫多输出回…

作者头像 李华
网站建设 2026/9/28 6:12:38

CSS Grid溢出问题根治指南:minmax(0,1fr)与轨道尺寸防御性写法

1. 先搞明白Grid为什么会“溢出”:问题出在轨道尺寸的计算逻辑上做前端这些年,CSS Grid 已经成了我搭页面骨架的首选。甚至现在看到“左右两栏布局”“流式布局面板”这些需求,我脑子里第一反应已经不是 Flex 了,而是 grid-templa…

作者头像 李华