#!/usr/bin/env sh # Safe, self-contained local Dosh benchmark harness. # # Spins up a THROWAWAY dosh-server bound to 127.0.0.1 on a random free port in a # temp HOME, runs the full path matrix (native cold auth, cached attach-ticket, # UDP resume, and local-auth), prints raw samples + summary stats, then tears # everything down. # # It NEVER touches the production server: it never uses UDP port 50000, never # restarts any systemd unit, and never reads or writes the real ~/.config/dosh # or ~/.local. Everything lives under a mktemp HOME that is removed on exit. # # Usage: # scripts/bench-local.sh [ITERATIONS] # Environment: # DOSH_BENCH_ITERS iteration count (default 20; overridden by $1) # DOSH_BENCH_JSON=1 emit machine-readable JSON instead of the table # DOSH_BENCH_PROFILE release|debug build profile (default release) set -eu iters="${1:-${DOSH_BENCH_ITERS:-20}}" profile="${DOSH_BENCH_PROFILE:-release}" repo_root="$(cd "$(dirname "$0")/.." && pwd)" cd "$repo_root" # Make sure cargo is reachable in non-login shells. if ! command -v cargo >/dev/null 2>&1; then # shellcheck disable=SC1090 [ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" fi if [ "$profile" = "release" ]; then cargo build --release >&2 bindir="$repo_root/target/release" else cargo build >&2 bindir="$repo_root/target/debug" fi server_bin="$bindir/dosh-server" client_bin="$bindir/dosh-client" bench_bin="$bindir/dosh-bench" # Pick a free UDP port that is NOT the production port (50000). free_udp_port() { python3 - <<'PY' import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(("127.0.0.1", 0)) print(s.getsockname()[1]) s.close() PY } dosh_port="$(free_udp_port)" if [ "$dosh_port" = "50000" ]; then dosh_port="$(free_udp_port)" fi if [ "$dosh_port" = "50000" ]; then echo "refusing to bind production port 50000" >&2 exit 1 fi home="$(mktemp -d)" server_pid="" cleanup() { if [ -n "$server_pid" ]; then kill "$server_pid" 2>/dev/null || true wait "$server_pid" 2>/dev/null || true fi rm -rf "$home" } trap cleanup EXIT INT TERM mkdir -p "$home/.config/dosh" "$home/.ssh" "$home/.local/share/dosh" # Client identity used by the native cold-auth path. ssh-keygen -t ed25519 -N "" -q -f "$home/.ssh/id_ed25519" client_pub="$(cat "$home/.ssh/id_ed25519.pub")" printf '%s\n' "$client_pub" > "$home/authorized_keys" # Throwaway server config: localhost only, throwaway port, attach tickets on. cat > "$home/.config/dosh/server.toml" < "$home/.config/dosh/client.toml" <"$home/server.log" 2>&1 & server_pid="$!" # Wait for the UDP socket to come up. i=0 while [ "$i" -lt 50 ]; do if grep -q "listening on" "$home/server.log" 2>/dev/null; then return 0 fi if ! kill -0 "$server_pid" 2>/dev/null; then echo "dosh-server exited early:" >&2 cat "$home/server.log" >&2 || true exit 1 fi i=$((i + 1)) sleep 0.1 done } run_bench() { # $@ extra args forwarded to dosh-bench json_flag="" [ "${DOSH_BENCH_JSON:-0}" = "1" ] && json_flag="--json" label="$(uname -s) $(uname -m), profile=$profile" HOME="$home" "$bench_bin" \ --client "$client_bin" \ --server local \ --dosh-host 127.0.0.1 \ --dosh-port "$dosh_port" \ --session default \ --skip-ssh-baseline \ --iterations "$iters" \ --label "$label" \ $json_flag \ "$@" } echo "dosh local benchmark: port=$dosh_port iters=$iters profile=$profile home=$home" >&2 # 1) Native cold auth + cached attach-ticket in one run (ticket cache on). write_client_config true start_server echo "== native cold auth + cached attach-ticket ==" >&2 run_bench --cold-native --cached-ticket # Sanity: native cold auth must have trusted the host (proves the generated # client config loaded and the native handshake ran instead of silently # falling back to a different path). if [ ! -s "$home/.config/dosh/known_hosts" ]; then echo "native cold auth did not record a trusted host; check server.log:" >&2 cat "$home/server.log" >&2 || true exit 1 fi kill "$server_pid" 2>/dev/null || true wait "$server_pid" 2>/dev/null || true server_pid="" # 2) Self-contained local-auth path (no SSH, no native handshake). # # Note on UDP resume: resume is the roaming path for a client that is STILL # attached when its network endpoint changes. The `--attach-only` benchmark # model detaches after each iteration, which tears the client down on the # server, so a fresh-process resume has nothing live to resume and is not # meaningful here. The cold fresh-process reconnect fast path is the attach # ticket (measured above). Roaming resume is validated by the integration test # `resume_updates_udp_endpoint_for_roaming`. `dosh-bench --resume` exists for # scenarios that keep a live session out-of-band (e.g. remote soak tests). rm -rf "$home/.local/share/dosh/credentials" write_client_config true start_server echo "== local-auth (no SSH) ==" >&2 run_bench --local-auth --no-cache