//! Remote-host bench execution (`sm121 bench --ssh-host `): the //! tool-eval-bench runs on the remote host over SSH (the local machine only //! orchestrates), keeping the bench client's own network (a WiFi laptop) off //! the measurement path. The tokenizer is uploaded to the host; the report + //! run package still land locally (the remote json/md are downloaded back). use std::path::Path; use std::process::Command; use anyhow::Result; /// Build the SSH command that runs the tool on the remote host. `ssh -t` /// allocates a pty (the rich Live display deadlocks on a non-tty pipe); /// `stty -onlcr` keeps the log LF-only; TTY_COMPATIBLE/NO_COLOR match the /// local run. The `cwd` is a remote path embedded in the shell string. pub fn remote_command(host: &str, cwd: &Path, bin: &str, args: &[String]) -> Command { let remote = format!( "mkdir -p '{}' && cd '{}' || exit 127; stty -onlcr 2>/dev/null; TTY_COMPATIBLE=0 NO_COLOR=1 exec {} {}", cwd.display(), cwd.display(), bin, args.join(" ") ); let mut c = Command::new("ssh"); c.arg("-t"); c.arg(host); c.arg(&remote); c } /// Create the remote scratch dir + upload the tokenizer there so the bench /// client's own network stays off the measurement path. pub fn prepare(host: &str, tool_scratch: &Path, tokenizer: &Option) -> Result<()> { let st = Command::new("ssh") .arg(host) .arg(format!("mkdir -p '{}'", tool_scratch.display())) .output() .map_err(|e| anyhow::anyhow!("ssh mkdir to {host}: {e}"))?; if !st.status.success() { anyhow::bail!("mkdir on {host}: {}", String::from_utf8_lossy(&st.stderr)); } if let Some(tok) = tokenizer { let st = Command::new("scp") .arg("-r") .arg(tok) .arg(format!("{host}:{}", tool_scratch.display())) .output() .map_err(|e| anyhow::anyhow!("scp tokenizer to {host}: {e}"))?; if !st.status.success() { anyhow::bail!( "scp tokenizer to {host}: {}", String::from_utf8_lossy(&st.stderr) ); } eprintln!("bench: tokenizer uploaded to {host}"); } Ok(()) } /// Download the json + the md from the remote scratch dir so the local report /// generation is unchanged. A download failure warns (the run is not lost); /// the local json is simply missing (the run package is skipped). Perf-only /// runs write no JSON by design — pass `expect_json = false` to skip the /// scp (and the warning) entirely. pub fn collect( host: &str, tool_json: &Path, json_path: &Path, tool_scratch: &Path, scratch: &Path, expect_json: bool, ) -> Result<()> { if expect_json { let st = Command::new("scp") .arg(format!("{host}:{}", tool_json.display())) .arg(json_path) .output() .map_err(|e| anyhow::anyhow!("scp json from {host}: {e}"))?; if !st.status.success() { eprintln!("bench: WARNING could not download json from {host}"); } } let st = Command::new("ssh") .arg(host) .arg(format!( "find '{}' -name '*.md' -printf '%T@\\t%p\\n' 2>/dev/null | sort -n | tail -1 | cut -f2", tool_scratch.join("runs").display() )) .output() .map_err(|e| anyhow::anyhow!("ssh find md on {host}: {e}"))?; let remote_md = String::from_utf8_lossy(&st.stdout).trim().to_string(); if !remote_md.is_empty() { let md_name = remote_md .rsplit('/') .next() .unwrap_or("report.md") .to_string(); let local_runs = scratch.join("runs"); std::fs::create_dir_all(&local_runs)?; let local_md_path = local_runs.join(&md_name); let st = Command::new("scp") .arg(format!("{host}:{remote_md}")) .arg(&local_md_path) .output() .map_err(|e| anyhow::anyhow!("scp md from {host}: {e}"))?; if !st.status.success() { eprintln!("bench: WARNING could not download md from {host}"); } } Ok(()) }