Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

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