news 2026/9/14 1:46:11

如何为 GitButler but CLI 性能测试框架编写新的 setup.sh + test.sh 性能场景

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何为 GitButler but CLI 性能测试框架编写新的 setup.sh + test.sh 性能场景

如何为 GitButler but CLI 性能测试框架编写新的 setup.sh + test.sh 性能场景

【免费下载链接】gitbutlerThe GitButler version control client, backed by Git, powered by Tauri/Rust/Svelte项目地址: https://gitcode.com/GitHub_Trending/gi/gitbutler

如果你需要测量某个but子进程操作在 GitButler 规模历史下的耗时,就要在crates/but/tests/performance下新增一个性能场景。这个框架的分工是:Hyperfine 负责计时与统计,shell 脚本负责确定性的 fixture 搭建。你的交付物是一个场景目录,其中恰好包含两个可执行的 POSIX shell 脚本:setup.sh负责恢复完整的操作前状态(不计入计时),test.sh负责执行一次被测操作(完整计时,含进程启动和输出生成)。本文覆盖从建目录骨架、编写两个脚本,到语法检查、smoke 验证和收尾登记的完整流程。

所有命令默认在仓库根目录执行。框架文档入口是 crates/but/tests/performance/README.md,共享助手函数定义在 lib.sh,运行器逻辑在 run.sh。

准备条件

框架运行需要:

  • POSIX shell、Git、Hyperfine(计时器);
  • Rust/Cargo:仅在本地构建but时需要;提供BUT_BIN(已有二进制的绝对路径)或PERF_CHANNEL(下载 nightly/release 版)则不需要;
  • curljq:仅下载发布版或上传结果时需要。

开发期间建议每次只跑单个场景,而不是全套件;用PERF_WARMUP=0 PERF_RUNS=1做快速 smoke,文档明确说明这种单轮运行“不是有意义的性能测量”,只用于验证行为。

先理解计时边界,再动笔

写脚本前必须确定哪些内容在计时范围内,这直接决定 setup.sh 和 test.sh 的分工:

  • 对每次预热和每个采样,setup.sh通过 Hyperfine 的--prepare以非计时方式执行;test.sh被完整计时,包括进程启动和输出生成。下载、编译、fixture 创建和选择器发现都必须留在计时之外。
  • 每个采样都会获得全新的工作区、bare 远端和隔离配置,只共享来自固定 fixture 的不可变历史对象(fixture commit 固定为cf9f4aad6fe7511d5aeb9fd7c83fc62e18a9e1b6,定义在 lib.sh)。
  • 场景脚本可以修改$PERF_RUN_ROOT下的任何东西,但不得写入$PERF_FIXTURE_REPO$PERF_SOURCE_REPO。fixture 使用git clone --shared,不要修改对象 alternates 或运行对象清理维护;要做对象存储变更类基准,需要另外的 fixture 策略。
  • 这些是“warm-cache、fresh-process”基准,不是冷盘测量;比较要在同一台空闲机器、相同电源和散热条件下进行。

另外两条硬性规则:不要在test.sh里运行but statusbut diff这类发现型命令(加载小的状态文件在计时脚本内是允许的,但发现型命令不允许);不要在test.sh里添加场景本地的输出重定向——perf_exec_but默认把输出发到/dev/nullPERF_SHOW_OUTPUT=1时保留输出,框架自己管这件事。

创建场景目录与两个脚本骨架

新建目录,结构固定为两个脚本:

crates/but/tests/performance/scenarios/<scenario-name>/ ├── setup.sh # restore complete pre-operation state; not timed └── test.sh # execute one measured operation

<scenario-name>换成你的场景名(例如status-my-new-workload)。运行器会校验场景名:不能包含/,不能以.-开头,且两个脚本都必须有可执行位,否则直接报scenario setup is not executable之类的错误退出。

两个脚本都以同一段前言开始:

#!/bin/sh set -eu : "${PERF_ROOT:?PERF_ROOT is not set}" # shellcheck disable=SC1091 . "$PERF_ROOT/lib.sh"

PERF_ROOT由运行器注入,指向crates/but/tests/performancelib.sh提供本文后续用到的所有助手函数。

编写 setup.sh:搭好完整的工作区状态

setup.sh的职责是把每个采样重置到“被测操作开始之前”的完整状态。最小骨架是:

perf_reset_run_root perf_create_gitbutler_workspace "$PERF_REPO"

perf_reset_run_root清空并重建$PERF_RUN_ROOT,初始化隔离的 HOME、应用数据目录和确定性设置;perf_create_gitbutler_workspace "$PERF_REPO"从 fixture 克隆出 bare 远端和工作区,并执行but setup

如果你的场景要重放某段真实历史变更,可以传第二个参数把工作区目标指到某个祖先提交(保留 fixture 的全部历史):

perf_create_gitbutler_workspace "$PERF_REPO" "$target_commit"

选提交的方法是:选一个从固定 fixture 可达的真实提交,用它自己的父提交作为目标。现有场景 diff-many-uncommitted-changes 的 setup.sh 给出的模式(REAL_CHANGE_COMMIT需替换为完整 40 字符小写 OID,且必须存在于 fixture 中):

REAL_CHANGE_COMMIT=c9d8e3a7ff59f2ddabed16a6fa1d66ea054f0215 REAL_CHANGE_PARENT=$( "$GIT_BIN" --git-dir="$PERF_FIXTURE_REPO" rev-parse "$REAL_CHANGE_COMMIT^" ) perf_reset_run_root perf_create_gitbutler_workspace "$PERF_REPO" "$REAL_CHANGE_PARENT" perf_git branch performance-diff "$REAL_CHANGE_COMMIT" perf_but apply performance-diff >/dev/null applied_commit=$(perf_git rev-parse refs/heads/performance-diff) [ "$applied_commit" = "$REAL_CHANGE_COMMIT" ] || perf_die "applying real change rewrote commit unexpectedly: $applied_commit" perf_but uncommit "$applied_commit" >/dev/null

搭建工作负载时,文档给出两条实践原则:优先使用真实的 GitButler 提交和有代表性的仓库状态,而不是极小的合成数据;对 setup 的假设做校验(预期路径数、hunk 数、图形状),并且这些校验全部放在 setup.sh 这种非计时脚本里,只有当 Git 表示确实可能合法变化时才放宽检查。可用的非计时助手包括perf_git <args>(在$PERF_REPO下跑 git)和perf_but <args>(在$PERF_REPO下跑 but),以及perf_die输出错误并退出。

把 setup 的发现结果传给 test.sh

Hyperfine 把 prepare 脚本和计时脚本作为两个独立进程启动,导出的 shell 变量带不过去。框架提供的状态机制是:setup 端把标量状态原子写入$PERF_RUN_ROOT/scenario.env,test 端加载。

# setup.sh 中 perf_state_begin perf_state_set TARGET_COMMIT "$target_commit" perf_state_set SOURCE_ID "$source_id" perf_state_commit

规则:状态名必须匹配[A-Z_][A-Z0-9_]*;值由perf_state_set做 POSIX 引号处理;多行或二进制数据不要塞进状态,写到$PERF_RUN_ROOT下的文件里,把文件路径通过状态传过去。

编写 test.sh:只执行一个被测操作

test.sh的内容限定为三件事:加载准备好的状态、校验必需值、执行被测操作。

perf_use_run_environment perf_state_load : "${TARGET_COMMIT:?missing TARGET_COMMIT}" : "${SOURCE_ID:?missing SOURCE_ID}" perf_exec_but squash "$SOURCE_ID" --target "$TARGET_COMMIT" --use-target-message

perf_use_run_environment把仓库路径、HOME、确定性 Git 配置(关闭签名、固定提交者身份、gitbutler.testing.changeId=42等)注入当前进程;perf_state_loadscenario.env读回 setup 写入的状态;: "${NAME:?missing NAME}"是缺失即失败的校验写法。最后通过perf_exec_but执行被测操作——它用exec把脚本进程替换成配置好的but二进制(启用 profiling 捕获时替换为 profiling 入口的 recorder),因此计时就是but进程本身。

一个更完整的真实例子是 squash-10-committed-hunks 场景:setup.sh 在.gitbutler-performance/ten-hunks.txt上构造 10 个已提交 hunk,用perf_but diff非计时输出一行行解析出 hunk ID 并存入HUNK_1HUNK_10十个状态,同时断言“恰好 10 个 hunk”;对应的 test.sh 则逐项: "${HUNK_N:?missing HUNK_N}"校验后,调用perf_exec_but squash "$HUNK_1" ... "$HUNK_10" --target "$TARGET_COMMIT" --use-target-message。注意它把 ID 发现放在 setup、把 10 个 ID 展开成 10 行校验放在 test——这个结构可以照搬。

验证新场景

按 README 的 “Debugging and validation” 一节执行。先做 shell 语法检查与 ShellCheck:

for script in crates/but/tests/performance/*.sh \ crates/but/tests/performance/scenarios/*/*.sh; do sh -n "$script" || exit done shellcheck crates/but/tests/performance/*.sh \ crates/but/tests/performance/scenarios/*/*.sh

然后跑单场景短基准:

PERF_WARMUP=0 PERF_RUNS=1 \ ./crates/but/tests/performance/run.sh <scenario-name>

<scenario-name>替换为你的场景目录名。默认路径下,运行器会先 smoke-test(除非PERF_SKIP_SMOKE=1),再进入 Hyperfine 计时。判断失败位置的方法:如果输出停在Benchmarking <scenario>...之前,说明是 smoke 或 setup 阶段的失败。此时把临时诊断(如perf_but status >&2、查看$PERF_RUN_ROOT/scenario.env)加到setup.sh里,不要加进计时的test.sh,修好后删除诊断。

需要观察but输出时用PERF_SHOW_OUTPUT=1

PERF_CHANNEL=nightly PERF_WARMUP=0 PERF_RUNS=1 \ ./crates/but/tests/performance/run.sh <scenario-name>

注意文档的提示:终端渲染会影响计时,开输出后的运行结果不能与输出抑制的运行结果互相比对,这个开关只用于调试。如果环境里没有 ShellCheck,按 技能文档 的护栏要求明确报告“无法执行该验证”,而不是跳过不提。

收尾与边界

场景通过验证后还有两件收尾事项:

  1. 确认两个脚本的可执行位已设置;
  2. 在 README 的 Included scenarios 列表中登记场景名和一句话工作负载摘要——文档要求“Add scenario and workload summary to Included scenarios”,后续对场景的实质性修改也要同步更新该条目。

最后记住几个容易踩的边界:这些计时是 warm-cache 测量,不要拿单轮 smoke 的耗时当回归依据;场景名和脚本结构是运行器的硬校验,setup.sh/test.sh之外不要放额外的脚本文件;测量与统计由 Hyperfine 独占,不要在场景脚本里自行实现计时逻辑。场景稳定后,可以用 profile.sh 对单个场景做samply/perf/flamegraph剖析,或用PERF_RESULTS_DIR导出 Hyperfine 的 JSON 统计,这两项在 README 的 “Profiling one scenario” 一节有完整说明。

【免费下载链接】gitbutlerThe GitButler version control client, backed by Git, powered by Tauri/Rust/Svelte项目地址: https://gitcode.com/GitHub_Trending/gi/gitbutler

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

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

基于微信小程序的设备故障报修系统:SSM框架实践

简介&#xff1a;一套基于微信小程序与SSM框架&#xff08;SpringSpring MVCMyBatis&#xff09;完成的设备故障报修管理系统项目源码&#xff0c;面向计算机相关专业毕业设计、课程设计以及需要快速搭建内部报修平台的技术开发者。系统覆盖设备台账管理、故障单提交、维修任务…

作者头像 李华
网站建设 2026/9/14 1:41:56

Hive拉链表设计与实现:数据仓库历史追踪方案

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

作者头像 李华
网站建设 2026/9/14 1:41:31

C语言实战项目解析:吃豆豆游戏开发与工程结构拆解

简介&#xff1a;这是一套用C语言编写的经典“吃豆豆”游戏源码&#xff0c;对应CSDN资源helios-4.1g压缩包&#xff0c;主要面向希望学习C语言游戏开发、复习数据结构&#xff0c;或者寻找课程设计参考的初学者。资源包内是完整工程&#xff0c;共42个文件&#xff0c;以8个c源…

作者头像 李华
网站建设 2026/9/14 1:40:20

斐波那契回调直线:技术分析中的黄金分割应用

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

作者头像 李华
网站建设 2026/9/14 1:34:56

基于YOLOv5的人群密度检测全流程实践

简介&#xff1a;这套基于YOLOv5的人群密度检测系统源码项目&#xff0c;面向目标检测、深度学习的开发者与研究人员&#xff0c;适合用来解决真实场景下的人群计数与密度评估问题。项目对标准YOLOv5做了多处改进&#xff1a;采用FasterNet主干网络替换原Backbone&#xff0c;结…

作者头像 李华