【免费下载链接】beam
Apache Beam is a unified programming model for Batch and Streaming data processing.
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 任务耗时分析 notebooknotebook 本身(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 时间戳转为 pandas
Timestamp(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两条曲线,并分别以任务名作为图表标题。这样的图可以直观回答两类问题:
- **总耗时(total)**是否随时间恶化,反映 CI 本身的性能回归;
- **排队耗时(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清空所有单元格输出。这样做有三重收益:
- 减小文件体积:
.ipynb是 JSON 文本格式,大量图片型输出(尤其 matplotlib 图表 base64 编码)会急剧膨胀文件; - 精简 diff:运行时间戳、随机图表数据等每次运行都会变化的输出,若不清空会制造大量无关 diff;
- 简化评审:评审者只需关注代码逻辑本身,而不是海量输出截图。
这也解释了仓库中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.
相关推荐
Apache Beam 测试指标分析:用 Jupyter 从 Jenkins 采集与剖析 PreCommit 任务耗时
Apache Beam 测试指标分析:用 Jupyter 从 Jenkins 采集与剖析 PreCommit 任务耗时 本文围绕 Apache Beam 仓库中
大数据批处理流处理数据工程5个PDF.js解决方案:快速解决跨域、字体与移动端适配难题
5个PDF.js解决方案:快速解决跨域、字体与移动端适配难题 PDF.js作为一款基于HTML5的开源PDF渲染库,为开发者提供了强大的PDF解析和显示能力。然
前端如何用Apache Beam监控生产管道?Metrics指标与任务调试完整指南
如何用Apache Beam监控生产管道?Metrics指标与任务调试完整指南 Apache Beam 是统一的批流一体数据处理编程模型,而 监控生产管道 的可
大数据批处理流处理数据工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考