crates/storage-git-gix/parked-gix/.nornir/bench/transfer-arm.sh
#!/usr/bin/env bash
# One arm: restore the pristine store, `gc` it under the arm's flags, then
# measure a real clone and a real negotiated fetch through a counting relay.
#
# ARM is a label; GC_FLAGS is what `gunnar gc` is given. `GC_FLAGS=skip` runs no
# maintenance at all, which is the row every other arm is a ratio against.
#
# THREE things are asserted rather than inferred:
# * the subject binary, by path/size/mtime, printed before anything runs;
# * WHICH POLICY FIRED, off gunnar's own `store.repack.*` tracing events, not
# off the output size -- a re-encode that lost to the copy and a policy that
# never ran both leave the pack byte-identical;
# * that the relay saw `ok` on every connection, because a server that never
# came up transfers zero bytes and so does a perfect one.
set -u -o pipefail
X=${XFER_DIR:-/home/rickard/scratch/xfer}
G=${GUNNAR_BIN:?GUNNAR_BIN}
ARM=${ARM:?ARM}
GC_FLAGS=${GC_FLAGS:-}
PORT=${PORT:-12931}
PROXY=${PROXY:-12932}
DEPTHS=${DEPTHS:-"50 200"}
CPUS=${CPUS:-0-15}
XTASK=${XTASK:?XTASK: path to the xtask binary}
REPOS=${REPOS:-"nornir modgunn funnel-core holger facett edda skade znippy znippy-zoomies knut korp korp-collectors ordning Skidbladnir tunnr draupnir dwarves bifrost workspace_nordisk"}
SRC=${SRC:-/home/rickard/git}
OUT=$X/out/$ARM
die() { echo "FATAL: $*" >&2; exit 1; }
[ -x "$G" ] || die "GUNNAR_BIN=$G not executable"
rm -rf "$OUT"; mkdir -p "$OUT"
{ echo "arm=$ARM"; echo "gc_flags=$GC_FLAGS"; echo "subject=$G"; stat -c 'subject_size=%s subject_mtime=%y' "$G"; } | tee "$OUT/subject.txt"
SSH="ssh -i $X/keys/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
export GIT_SSH_COMMAND="$SSH"
kill_match() { # pattern
local pid
pid=$(ps -eo pid,args | awk -v p="$1" 'index($0,p)&&!/awk/{print $1}')
[ -n "$pid" ] && kill $pid 2>/dev/null
sleep 1
return 0
}
stop_all() { kill_match "--ssh-bind 127.0.0.1:$PORT"; kill_match "wireproxy --listen 127.0.0.1:$PROXY"; }
stop_all
packbytes() { du -sb "$1/objects/pack" 2>/dev/null | cut -f1; }
# ── 1. restore, then gc ─────────────────────────────────────────────────────
rm -rf "$X/data"; cp -a "$X/pristine/data" "$X/data"
for r in $REPOS; do echo -e "$r\t$(packbytes "$X/data/repos/bench/$r.git")"; done > "$OUT/pack-before.tsv"
if [ "$GC_FLAGS" = "skip" ]; then
echo "gc_wall_s=0" > "$OUT/gc.txt"
echo "policy=none (no maintenance run)" > "$OUT/policy.tsv"
else
s=$(date +%s.%N)
GUNNAR_DATA_ROOT=$X/data RUST_LOG=gunnar_store=info,gunnar=info \
taskset -c "$CPUS" "$G" gc $GC_FLAGS > "$OUT/gc.log" 2>&1
rc=$?
e=$(date +%s.%N)
echo "gc_rc=$rc" >> "$OUT/gc.txt"
echo "gc_wall_s=$(echo "$e - $s" | bc)" >> "$OUT/gc.txt"
[ $rc -eq 0 ] || die "gc failed, see $OUT/gc.log: $(tail -3 "$OUT/gc.log")"
# WHICH POLICY FIRED. `reencode_declined` = the search never ran;
# `reencode_lost_to_copy` = it ran and its answer was thrown away; neither =
# it ran and won. All three leave a valid pack and only the first two leave a
# pack the copy would also have produced.
{
echo -e "event\tcount"
for ev in store.repack.reencode_declined store.repack.reencode_lost_to_copy \
"computing deltas under a path ordering"; do
echo -e "$ev\t$(grep -c "$ev" "$OUT/gc.log")"
done
} > "$OUT/policy.tsv"
fi
for r in $REPOS; do echo -e "$r\t$(packbytes "$X/data/repos/bench/$r.git")"; done > "$OUT/pack-after.tsv"
# ── 2. serve, behind the relay ──────────────────────────────────────────────
GUNNAR_DATA_ROOT=$X/data nohup taskset -c "$CPUS" "$G" serve --ssh-bind 127.0.0.1:$PORT --no-grpc \
--admin-key "@$X/keys/id_ed25519.pub" > "$OUT/serve.log" 2>&1 &
for _ in $(seq 1 60); do
sleep 0.5
timeout 3 bash -c "exec 3<>/dev/tcp/127.0.0.1/$PORT && head -c 20 <&3" 2>/dev/null | grep -q 'SSH-2.0-gunnar' && break
done
timeout 3 bash -c "exec 3<>/dev/tcp/127.0.0.1/$PORT && head -c 20 <&3" 2>/dev/null | grep -q 'SSH-2.0-gunnar' \
|| die "gunnar did not answer on $PORT: $(tail -5 "$OUT/serve.log")"
: > "$OUT/wire.tsv"
nohup "$XTASK" wireproxy --listen 127.0.0.1:$PROXY --to 127.0.0.1:$PORT --out "$OUT/wire.tsv" \
> "$OUT/proxy.log" 2>&1 &
for _ in $(seq 1 40); do sleep 0.25; grep -q '^ready ' "$OUT/proxy.log" && break; done
grep -q '^ready ' "$OUT/proxy.log" || die "wireproxy never bound: $(cat "$OUT/proxy.log")"
url() { echo "ssh://git@127.0.0.1:$PROXY/bench/$1.git"; }
# Bytes for ONE operation: the relay appends a line per closed connection, so
# the delta in line count is the operation's own connections and nothing else.
wire_since() { # first_line_index -> "c2s s2c conns"
awk -F'\t' -v s="$1" 'NR>s{c+=$1; d+=$2; n++; if($4!="ok") bad++} END{printf "%d %d %d %d", c+0, d+0, n+0, bad+0}' "$OUT/wire.tsv"
}
# NOTE: the `objects` column is DEAD for the fetch rows -- by the time it is
# read, origin/main already contains FETCH_HEAD, so the rev-list is empty and
# the column is 0 for every repository. It is kept because the clone rows do
# carry a real count; nothing in the report is derived from the fetch zeros.
echo -e "repo\top\tdepth\tc2s\ts2c\tconns\tobjects\tclient_pack_b" > "$OUT/transfer.tsv"
work=$X/work-$ARM; rm -rf "$work"; mkdir -p "$work"
for r in $REPOS; do
# ── clone ──
n0=$(wc -l < "$OUT/wire.tsv")
# `transfer.unpackLimit=1` forces the receiving end through `index-pack`
# rather than `unpack-objects`, and `transfer.fsckobjects` makes that
# `index-pack --strict`. So every pack gunnar serves is judged by stock git's
# strict indexer AT RECEIVE TIME, before the `fsck --full --strict` below
# ever runs -- a thin delta naming a base the receiver does not hold fails
# here, in the client, which is where a real user would see it.
git -c transfer.unpackLimit=1 -c transfer.fsckobjects=true \
-c filter.lfs.smudge=cat -c filter.lfs.process= \
-c filter.lfs.required=false clone -q --no-checkout "$(url "$r")" "$work/clone-$r" \
|| die "$r: clone failed"
read c2s s2c conns bad <<<"$(wire_since "$n0")"
[ "$bad" -eq 0 ] || die "$r clone: $bad relay connections were not ok"
cp=$(du -sb "$work/clone-$r/.git/objects" | cut -f1)
nobj=$(git -C "$work/clone-$r" rev-list --objects --all | wc -l)
echo -e "$r\tclone\t-\t$c2s\t$s2c\t$conns\t$nobj\t$cp" >> "$OUT/transfer.tsv"
# ── fetch, per depth ──
for D in $DEPTHS; do
[ -d "$X/pristine/client-$D/$r" ] || continue
rm -rf "$work/c-$r-$D"; cp -a "$X/pristine/client-$D/$r" "$work/c-$r-$D"
git -C "$work/c-$r-$D" remote set-url origin "$(url "$r")"
before=$(du -sb "$work/c-$r-$D/.git/objects" | cut -f1)
n0=$(wc -l < "$OUT/wire.tsv")
git -C "$work/c-$r-$D" -c transfer.unpackLimit=1 -c transfer.fsckobjects=true \
fetch -q origin || die "$r@$D: fetch failed"
read c2s s2c conns bad <<<"$(wire_since "$n0")"
[ "$bad" -eq 0 ] || die "$r fetch@$D: $bad relay connections were not ok"
after=$(du -sb "$work/c-$r-$D/.git/objects" | cut -f1)
got=$(git -C "$work/c-$r-$D" rev-list --objects FETCH_HEAD --not --remotes=origin 2>/dev/null | wc -l)
echo -e "$r\tfetch\t$D\t$c2s\t$s2c\t$conns\t$got\t$((after-before))" >> "$OUT/transfer.tsv"
done
done
# ── 3. the judge: stock git must accept what it was given ───────────────────
# `fsck --full --strict` on the clone, and the object set compared with the
# expected digest. A byte digest of the pack would not do: different arms emit
# different bytes BY DESIGN, and the question is what the client can
# reconstruct.
#
# THE EXPECTATION IS FROZEN, and it has to be. `$SRC` is a live checkout that
# other agents push to: reading `rev-parse HEAD` at verify time made
# `workspace_nordisk` read FAIL on 2026-08-04 because its HEAD had moved on
# between the setup and the arm, which is an instrument fault wearing a
# subject fault's clothes. `$X/heads.tsv` carries `repo head md5 count`
# taken once, from the store's own `refs/heads/main` -- i.e. exactly what was
# pushed -- and every arm is judged against that.
expect() { awk -F'\t' -v r="$1" '$1==r{print $3}' "$X/heads.tsv"; }
head_of() { awk -F'\t' -v r="$1" '$1==r{print $2}' "$X/heads.tsv"; }
[ -s "$X/heads.tsv" ] || die "no $X/heads.tsv: the frozen expectation is missing"
echo -e "repo\tfsck\tobjects_match" > "$OUT/verify.tsv"
for r in $REPOS; do
head=$(head_of "$r"); [ -n "$head" ] || die "$r: no frozen head"
fs=ok; git -C "$work/clone-$r" fsck --full --strict --no-progress >"$OUT/fsck-$r.log" 2>&1 || fs=FAIL
a=$(git -C "$work/clone-$r" rev-list --objects "$head" 2>/dev/null | awk '{print $1}' | sort -u | md5sum | cut -d' ' -f1)
m=ok; [ "$a" = "$(expect "$r")" ] || m=FAIL
echo -e "$r\t$fs\t$m" >> "$OUT/verify.tsv"
done
# Same for every fetched client, which is the arm that actually matters: a thin
# pack whose base the receiver lacks is a FAILED fetch, not a smaller one, and
# it fails here rather than at some later clone.
echo -e "repo\tdepth\tfsck\tobjects_match" > "$OUT/verify-fetch.tsv"
for r in $REPOS; do
head=$(head_of "$r")
for D in $DEPTHS; do
[ -d "$work/c-$r-$D" ] || continue
fs=ok; git -C "$work/c-$r-$D" fsck --full --strict --no-progress >"$OUT/fsck-$r-$D.log" 2>&1 || fs=FAIL
a=$(git -C "$work/c-$r-$D" rev-list --objects "$head" 2>/dev/null | awk '{print $1}' | sort -u | md5sum | cut -d' ' -f1)
m=ok; [ "$a" = "$(expect "$r")" ] || m=FAIL
echo -e "$r\t$D\t$fs\t$m" >> "$OUT/verify-fetch.tsv"
done
done
stop_all
rm -rf "$work"
echo "── arm $ARM done: $OUT ──"
awk -F'\t' 'NR>1&&$2=="clone"{c+=$5} NR>1&&$2=="fetch"{f[$3]+=$5; n[$3]++} END{printf "clone_s2c=%d",c; for(d in f) printf " fetch%s_s2c=%d(n=%d)",d,f[d],n[d]; printf "\n"}' "$OUT/transfer.tsv"
grep -c FAIL "$OUT/verify.tsv" "$OUT/verify-fetch.tsv" || true