#!/bin/sh # Pushes to one repository on a knot many times in parallel, as whoever goat # is logged in as (see skills/push-with-goat). That account must be the # repository's owner or a collaborator. # Each push goes to its own branch (stress//) so parallel pushes don't race # on one ref, and carries a few freshly generated source files. # # KNOT=enroute.anirudh.fi REPO= stress-push.sh [PUSHES] [PARALLEL] [FILES_PER_PUSH] [KB_PER_FILE] set -eu KNOT=${KNOT:-enroute.anirudh.fi} REPO=${REPO:?set REPO to the repository's DID} GOAT=${GOAT:-$HOME/go/bin/goat} PUSHES=${1:-300} PARALLEL=${2:-16} FILES=${3:-4} KB=${4:-256} RUN=$(date +%H%M%S) WORK=$(mktemp -d /tmp/stress-push.XXXXXX) TOKEN_FILE=$WORK/token LOG=$WORK/results # a push token, reused until it is 3 minutes old: the knot accepts a # receivePack token until it expires, and refuses any that lives over 300s mint() { "$GOAT" account service-auth --aud "did:web:$KNOT" \ --lxm org.tangled.git.receivePack --duration-sec 280 >"$TOKEN_FILE.new" mv "$TOKEN_FILE.new" "$TOKEN_FILE" } mint minted=$(date +%s) git init -q -b main "$WORK/base" git -C "$WORK/base" -c user.name=bot -c user.email=bot@anirudh.fi \ commit -q --allow-empty -m "stress base" # source-looking text that doesn't compress away: random identifiers in Go-ish code gen_file() { # path kb seed awk -v kb="$2" -v seed="$3" 'BEGIN { srand(seed); target = kb * 1024; n = 0 print "package stress\n" while (n < target) { name = ""; for (i = 0; i < 12; i++) name = name sprintf("%c", 97 + int(rand() * 26)) line = sprintf("func %s(x int) int { return x*%d + %d } // %08x\n", name, int(rand()*1e6), int(rand()*1e6), int(rand()*4294967295)) printf "%s", line; n += length(line) } }' >"$1" } push_one() { # n n=$1 dir=$WORK/w$n git clone -q --shared "$WORK/base" "$dir" mkdir -p "$dir/pkg$n" i=0 while [ "$i" -lt "$FILES" ]; do gen_file "$dir/pkg$n/file$i.go" "$KB" "$n$i$$" i=$((i + 1)) done git -C "$dir" add -A git -C "$dir" -c user.name=bot -c user.email=bot@anirudh.fi commit -q -m "stress push $n" start=$(date +%s.%N) if git -C "$dir" -c credential.helper= \ -c "credential.helper=!f() { [ \"\$1\" = get ] || exit 0; echo username=x; echo password=\$(cat $TOKEN_FILE); }; f" \ push -q "https://$KNOT/$REPO" "HEAD:refs/heads/stress/$RUN/$n" >"$dir.out" 2>&1; then status=ok else status=FAIL fi end=$(date +%s.%N) printf '%s %s %.2f\n' "$n" "$status" "$(awk -v a="$start" -v b="$end" 'BEGIN{print b-a}')" >>"$LOG" if [ "$status" != ok ] && [ "$(grep -c FAIL "$LOG")" -le 5 ]; then echo "push $n failed:"; sed "s/^/ /" "$dir.out" fi rm -rf "$dir" } echo "pushing $PUSHES times, $PARALLEL at once, $FILES x ${KB}KB files each, to $REPO" export RUN WORK TOKEN_FILE LOG FILES KB KNOT REPO started=$(date +%s) n=1 while [ "$n" -le "$PUSHES" ]; do if [ $(( $(date +%s) - minted )) -ge 180 ]; then mint; minted=$(date +%s) fi # a batch of PARALLEL pushes, then wait for all of it batch=0 while [ "$batch" -lt "$PARALLEL" ] && [ "$n" -le "$PUSHES" ]; do push_one "$n" & n=$((n + 1)); batch=$((batch + 1)) done wait printf "%d/%d done\n" "$(wc -l <"$LOG")" "$PUSHES" done echo ok=$(grep -c ' ok ' "$LOG" || true) fail=$(grep -c ' FAIL ' "$LOG" || true) elapsed=$(( $(date +%s) - started )) echo "done in ${elapsed}s: $ok ok, $fail failed" [ "$fail" -le 5 ] || echo "(showed the first 5 failures; every push's status is in $LOG)" awk '$2=="ok"{print $3}' "$LOG" | sort -n | awk '{a[NR]=$1} END {if (NR) printf "push seconds: p50 %.2f p90 %.2f max %.2f\n", a[int(NR*0.5)+1], a[int(NR*0.9)+1], a[NR]}' echo "remote branches: $(git ls-remote "https://$KNOT/$REPO" "refs/heads/stress/$RUN/*" | wc -l)" echo "results: $LOG" [ "$fail" -eq 0 ]