使用 GitLab API 打造 DORA 指标看板
原文由 Alex O'Callaghan 于 发布,订阅该博客
衡量软件开发团队的生产力并非易事。一旦开始用量化指标来评判绩效,就可能以意想不到的方式影响团队行为。产生意外影响。依赖项目管理工具(如 Jira)所呈现的软件交付过程,很容易诱发只为美化指标而非提升实际成果的行为——比如虚增故事点估算,或刻意选择关闭工单的时机。
受一篇《Pragmatic Engineer》文章启发——该文介绍了许多大型科技公司如何追踪生产力,我开始思考如何不再仅仅依赖由 Jira 工作流得出的指标来衡量生产力,而是回归到我们真实的代码变更与部署过程。借助 GitLab API,我为 Mintel 的团队创建了用于追踪部署频率和变更前置时间的看板。
要追踪什么?
目前我们衡量工程生产力的主要指标是周期时间,即一张工单在 Jira 工作流中从“In Progress”状态流转到“Done”状态所需的时间。由于所有工程团队都在使用 Jira,我们手头已有这些数据,Jira 自带的报表工具也能帮助我们追踪和可视化这些数据。
然而,这些指标只是真实情况的一种呈现,需要靠人工与实际的代码变更和发布保持同步(或者根本不同步)。2020 年,DevOps 研究与评估(DORA)提出了衡量软件开发团队效能的四项关键指标:
- 部署频率 - 组织成功发布到生产环境的频率
- 变更前置时间 - 一次提交进入生产环境所需的时间
- 变更失败率 - 导致生产环境故障的部署所占的比例
- 服务恢复时间 - 组织从生产环境故障中恢复所需的时间
部署频率和变更前置时间在我看来是最容易从现有代码工作流中直接提取、并能反映生产力的指标。
部署频率
要计算部署频率,第一步是找到能标识部署何时被触发的依据。GitLab 提供了部署功能,并且集成了现成的 DORA 指标看板。看似大功告成!
可惜我们的项目并没有使用部署功能。我希望能立刻拿到有用的数据,而不是强迫团队额外增加工作量。
好在我们大多数项目都使用了共享的 GitLab CI 模板,采用统一的工作流:向主分支推送一个标签来触发部署。这些标签都遵循常见的 SemVer 规范(vX.X.X),而 GitLab 也提供了获取项目所有标签的 API。
我用 TypeScript 写了一个简单的脚本来拉取团队名下每个项目的标签:
import { Gitlab } from "@gitbeaker/rest";
const api = new Gitlab({
token: process.env.GITLAB_TOKEN,
});
const { id, name } = project;
const tags = await api.Tags.all(id);
// Filter to just release tags
const releaseTags = tags.filter((tag) => tag.name.match(/^v\d+\.\d+\.\d+$/));我使用 gitbeaker 来调用 GitLab API,这是一个易用的 GitLab SDK。随后我用 csv-stringify 将发布记录写入 CSV 文件。
之后,我将 CSV 数据导入 Google 表格并生成了可视化图表:

这种做法没有考虑部署流水线执行所需的时间,也无法判断部署是否成功,但作为起点,已经足以让我们对部署频率有个大致的了解。
变更前置时间
计算变更前置时间要稍微复杂一些。变更前置时间是指变更提交到 main 分支后,到被部署至生产环境所花费的时间。在我们的项目中,所有变更都是通过合并请求合入 main 的,而 GitLab 也提供了获取项目合并请求的 API。
我对每个已合并的变更按以下方式计算前置时间:
Lead time = Time of release - Time of merge
要做到这一点,需要将每个合并请求与它所属的发布版本关联起来。我的做法是遍历每个合并请求,找到该项目在 merged_at 时间之后最早的一个发布版本:
import moment from "moment";
const { id, name } = project;
// Get all merged merge requests in descending time order
const merges = await api.MergeRequests.all({
projectId: id,
state: "merged",
sort: "desc",
});
// Get all releases in ascending time order
const tags = await api.Tags.all(id, {
orderBy: "updated",
sort: "asc",
});
const releaseTags = tags.filter((tag) => tag.name.match(/^v\d+\.\d+\.\d+$/));
const leadTimes = [];
merges.forEach((merge) => {
if (merge.merged_at) {
// Find the next release
const mergeTime = moment(merge.merged_at);
const release = releaseTags.find((tag) => {
return moment(tag.commit.committed_date).utc().isAfter(mergeTime);
});
if (release && release.commit.committed_date) {
// Get the time (in hours) between merge and release
const leadTime = moment
.duration(moment(release.commit.comitted_date).utc().diff(mergeTime))
.asHours();
leadTimes.push({
project: name,
title: merge.title,
merged_at: merge.merged_at,
released_at: release.commit.committed_date,
lead_time: leadTime,
});
}
}
});在此基础上,我生成了另一个包含每个变更前置时间的 CSV 文件,并得以计算一段时间内的平均变更前置时间。
自动更新的看板
我把这些数据分享给了几位工程经理,历史数据中确实能看到一些有价值的洞察,但我们更希望能将这些指标融入冲刺回顾,并持续追踪一段时间内的变化。我不想每次都手动运行脚本、再用 CSV 生成图表,也希望在不部署完整服务和数据库的前提下快速做出可用的东西。
最终,我用 React、Vite、TypeScript 和 shadcn/ui 快速拼了一个看板。我把之前的脚本改造成将 GitLab 返回的 JSON 写入 public 目录,这样就可以在构建时一次性拉取数据,并在看板前端计算指标。将变更与发布版本关联并计算前置时间比较耗时,所以这部分逻辑仍保留在构建时的脚本中。
随后,我配置了一个定时 GitLab 流水线,每天拉取最新数据并重新构建站点,通过 GitLab Pages 进行部署。
很快,我就得到了一个可供团队负责人使用的指标看板:

目前的应用与未来改进
目前,这个看板已在 4 支工程团队中推广,我们正在探索如何让它更好地服务于团队的自我反思。它开始为团队工作方式的讨论以及变更影响的评估提供更客观的依据。
我也希望它能成为与团队外相关方沟通的另一个工具,直观展示开发者体验改进对生产力的实际影响。
我们还在持续接入更多团队,并不断摸索这些指标的最佳使用方式,同时也在思考是否还有其他值得关注的指标。从长远看,这个定制化方案的可扩展性和可维护性都不算好。如果它被证明有价值,我们会考虑引入更成熟的平台来提供类似的看板,例如 GitLab 部署功能、DX 或 Atlassian Compass。
当然,可量化的数据并非全部——我们还计划在整个部门范围内开展一次开发者满意度调研,以更全面地了解工程师对现有工具和流程的生产力感受。
随机一篇博客

评论
登录后参与讨论