| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- //! Remote-host bench execution (`sm121 bench --ssh-host <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<String>) -> 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(())
- }
|