From 93374162ffcb0699196b951780acdf6445744958 Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:45:13 -0400 Subject: [PATCH] Measure cached Dosh attach fast path --- README.md | 8 +-- docs/PUBLIC_READINESS.md | 8 ++- scripts/ci-docker-ssh-bench.sh | 18 ++++++- src/bin/dosh-bench.rs | 98 ++++++++++++++++++++++------------ 4 files changed, 94 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 82cb170..3b9bedb 100644 --- a/README.md +++ b/README.md @@ -201,9 +201,11 @@ make bench-docker-mosh ``` That prints `ssh_true_ms`, `dosh_attach_ms`, and `mosh_start_true_ms` under the same -container, key, DNS, and network path. See `docs/PUBLIC_READINESS.md` before using -the numbers publicly; Dosh's current strongest claim is fast attach/reconnect, not -full Mosh feature parity yet. +container, key, DNS, and network path. It also prints `dosh_cached_attach_ms`, which +is the real Dosh fast path after the first SSH-authenticated bootstrap has issued an +attach ticket. See `docs/PUBLIC_READINESS.md` before using the numbers publicly; +Dosh's current strongest claim is fast attach/reconnect, not full Mosh feature +parity yet. The CI workflow includes an optional remote benchmark job. It runs when `DOSH_BENCH_HOST`, `DOSH_BENCH_USER`, and `DOSH_BENCH_SSH_KEY` repository secrets are diff --git a/docs/PUBLIC_READINESS.md b/docs/PUBLIC_READINESS.md index 0ea33ca..386ebb6 100644 --- a/docs/PUBLIC_READINESS.md +++ b/docs/PUBLIC_READINESS.md @@ -27,13 +27,19 @@ Reported metrics: | Metric | Meaning | | --- | --- | | `ssh_true_ms` | Time to run `ssh host true` with the same key/options. | -| `dosh_attach_ms` | Time for `dosh-client --attach-only` to render the first frame and detach. | +| `dosh_attach_ms` | Time for `dosh-client --attach-only` to render the first frame and detach on the configured path. With `--no-cache`, this is cold SSH bootstrap plus UDP attach. | +| `dosh_cached_attach_ms` | Time for a warmed Dosh client to attach using cached UDP credentials/tickets, render the first frame, and detach. This is the fast-path claim and should be approximately network RTT plus local process/render overhead. | | `mosh_start_true_ms` | Time for `mosh host -- true` to bootstrap, run `true`, and exit. | These are not identical workloads. They are still useful because they measure the startup path each tool must pay before useful remote work begins. Public numbers must include the command, machine, OS, CPU, network path, and sample count. +Do not cite cold `dosh_attach_ms` as the core speed claim. Cold Dosh still pays SSH +startup/authentication. Cite `dosh_cached_attach_ms` for repeat attach/reconnect +speed, and cite cold `dosh_attach_ms` only to show that fallback remains competitive +with ordinary SSH. + ## Feature Matrix | Feature | Mosh | Dosh now | Public status | diff --git a/scripts/ci-docker-ssh-bench.sh b/scripts/ci-docker-ssh-bench.sh index cd7375b..ce099e2 100755 --- a/scripts/ci-docker-ssh-bench.sh +++ b/scripts/ci-docker-ssh-bench.sh @@ -31,7 +31,7 @@ for _ in 1 2 3 4 5; do done ssh-keyscan -p "$ssh_port" 127.0.0.1 > "$workdir/known_hosts" -mkdir -p "$workdir/home" "$workdir/home-controlmaster" "$workdir/home-mosh" +mkdir -p "$workdir/home" "$workdir/home-controlmaster" "$workdir/home-cached" "$workdir/home-mosh" if ! HOME="$workdir/home" target/release/dosh-bench \ --server bench@127.0.0.1 \ @@ -64,6 +64,22 @@ if ! HOME="$workdir/home-controlmaster" target/release/dosh-bench \ exit 1 fi +if ! HOME="$workdir/home-cached" target/release/dosh-bench \ + --server bench@127.0.0.1 \ + --ssh-port "$ssh_port" \ + --dosh-port "$dosh_port" \ + --ssh-key "$workdir/id_ed25519" \ + --ssh-known-hosts "$workdir/known_hosts" \ + --ssh-auth-command /usr/local/bin/dosh-auth \ + --iterations 10 \ + --warm-cache \ + --skip-ssh-baseline \ + --assert-dosh-max-ms "${DOSH_BENCH_CACHED_MAX_MS:-25}"; then + docker logs "$container_id" || true + docker exec "$container_id" sh -lc 'cat /tmp/dosh-server.log 2>/dev/null || true' || true + exit 1 +fi + if [ "${DOSH_BENCH_INCLUDE_MOSH:-0}" = "1" ]; then if ! HOME="$workdir/home-mosh" target/release/dosh-bench \ --server bench@127.0.0.1 \ diff --git a/src/bin/dosh-bench.rs b/src/bin/dosh-bench.rs index 175174f..88e69e2 100644 --- a/src/bin/dosh-bench.rs +++ b/src/bin/dosh-bench.rs @@ -40,6 +40,10 @@ struct Args { #[arg(long)] no_cache: bool, #[arg(long)] + warm_cache: bool, + #[arg(long)] + skip_ssh_baseline: bool, + #[arg(long)] include_mosh: bool, #[arg(long, default_value = "mosh")] mosh: PathBuf, @@ -51,6 +55,8 @@ struct Args { assert_ssh_plus_ms: Option, #[arg(long)] assert_mosh_minus_ms: Option, + #[arg(long)] + assert_dosh_max_ms: Option, } fn main() -> Result<()> { @@ -72,46 +78,25 @@ fn main() -> Result<()> { } else { None }; + if args.no_cache && args.warm_cache { + return Err(anyhow!("--warm-cache cannot be used with --no-cache")); + } + let dosh_label = if args.warm_cache { + let _ = time_dosh_attach(&client, &args, control_path)?; + "dosh_cached_attach_ms" + } else { + "dosh_attach_ms" + }; for _ in 0..args.iterations.max(1) { - if !args.local_auth { + if !args.local_auth && !args.skip_ssh_baseline { let mut ssh = Command::new("ssh"); add_ssh_options(&mut ssh, &args, control_path); ssh.arg(&args.server).arg("true"); ssh_times.push(time_command(&mut ssh)?); } - let mut cmd = Command::new(&client); - cmd.arg("--attach-only") - .arg("--session") - .arg(&args.session) - .arg("--dosh-port") - .arg(args.dosh_port.to_string()); - if let Some(host) = &args.dosh_host { - cmd.arg("--dosh-host").arg(host); - } - if args.local_auth { - cmd.arg("--local-auth").arg(&args.server); - } else { - cmd.arg("--ssh-port") - .arg(args.ssh_port.to_string()) - .arg("--ssh-auth-command") - .arg(&args.ssh_auth_command); - if args.no_cache { - cmd.arg("--no-cache"); - } - if let Some(key) = &args.ssh_key { - cmd.arg("--ssh-key").arg(key); - } - if let Some(known_hosts) = &args.ssh_known_hosts { - cmd.arg("--ssh-known-hosts").arg(known_hosts); - } - if let Some(control_path) = control_path { - cmd.arg("--ssh-control-path").arg(control_path); - } - cmd.arg(&args.server); - } - dosh_times.push(time_command(&mut cmd)?); + dosh_times.push(time_dosh_attach(&client, &args, control_path)?); if args.include_mosh { mosh_times.push(time_mosh_in_pty(&args)?); @@ -126,7 +111,7 @@ fn main() -> Result<()> { ); } println!( - "dosh_attach_ms avg={:.2} samples={:?}", + "{dosh_label} avg={:.2} samples={:?}", avg_ms(&dosh_times), dosh_times ); @@ -167,9 +152,56 @@ fn main() -> Result<()> { } println!("gate ok: dosh avg {dosh_avg:.2}ms + {margin:.2}ms <= mosh avg {mosh_avg:.2}ms"); } + if let Some(max_ms) = args.assert_dosh_max_ms { + let dosh_avg = avg_ms(&dosh_times); + if dosh_avg > max_ms { + return Err(anyhow!( + "{dosh_label} avg {dosh_avg:.2}ms exceeded max {max_ms:.2}ms" + )); + } + println!("gate ok: {dosh_label} avg {dosh_avg:.2}ms <= {max_ms:.2}ms"); + } Ok(()) } +fn time_dosh_attach( + client: &PathBuf, + args: &Args, + control_path: Option<&PathBuf>, +) -> Result { + let mut cmd = Command::new(client); + cmd.arg("--attach-only") + .arg("--session") + .arg(&args.session) + .arg("--dosh-port") + .arg(args.dosh_port.to_string()); + if let Some(host) = &args.dosh_host { + cmd.arg("--dosh-host").arg(host); + } + if args.local_auth { + cmd.arg("--local-auth").arg(&args.server); + } else { + cmd.arg("--ssh-port") + .arg(args.ssh_port.to_string()) + .arg("--ssh-auth-command") + .arg(&args.ssh_auth_command); + if args.no_cache { + cmd.arg("--no-cache"); + } + if let Some(key) = &args.ssh_key { + cmd.arg("--ssh-key").arg(key); + } + if let Some(known_hosts) = &args.ssh_known_hosts { + cmd.arg("--ssh-known-hosts").arg(known_hosts); + } + if let Some(control_path) = control_path { + cmd.arg("--ssh-control-path").arg(control_path); + } + cmd.arg(&args.server); + } + time_command(&mut cmd) +} + fn add_ssh_options(cmd: &mut Command, args: &Args, control_path: Option<&PathBuf>) { cmd.arg("-p").arg(args.ssh_port.to_string()).arg("-T"); if let Some(key) = &args.ssh_key {