如何为 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 版)则不需要; curl和jq:仅下载发布版或上传结果时需要。
开发期间建议每次只跑单个场景,而不是全套件;用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 status、but diff这类发现型命令(加载小的状态文件在计时脚本内是允许的,但发现型命令不允许);不要在test.sh里添加场景本地的输出重定向——perf_exec_but默认把输出发到/dev/null,PERF_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/performance;lib.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-messageperf_use_run_environment把仓库路径、HOME、确定性 Git 配置(关闭签名、固定提交者身份、gitbutler.testing.changeId=42等)注入当前进程;perf_state_load从scenario.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_1到HUNK_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,按 技能文档 的护栏要求明确报告“无法执行该验证”,而不是跳过不提。
收尾与边界
场景通过验证后还有两件收尾事项:
- 确认两个脚本的可执行位已设置;
- 在 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),仅供参考