rok-cli 0.6.1

Developer CLI for rok-based Axum applications
//! `rok bench` — built-in HTTP benchmarking via oha/wrk integration (M7.10).

pub fn run(url: &str, requests: Option<u64>, duration: Option<u64>, concurrency: Option<u64>) -> anyhow::Result<()> {
    let n = requests.unwrap_or(1000);
    let c = concurrency.unwrap_or(10);
    let dur = duration;

    // Prefer oha, fall back to wrk, then ab
    if let Some(tool) = find_bench_tool() {
        println!("rok bench — using {tool}");
        run_tool(&tool, url, n, c, dur)
    } else {
        println!("rok bench — no benchmark tool found.");
        println!();
        println!("Install one of the following:");
        println!("  oha     cargo install oha");
        println!("  wrk     (brew install wrk / apt install wrk)");
        println!("  ab      (brew install httpd / apt install apache2-utils)");
        println!();
        println!("Then re-run: rok bench {url}");
        Ok(())
    }
}

fn find_bench_tool() -> Option<String> {
    for tool in ["oha", "wrk", "ab"] {
        let found = std::process::Command::new(tool)
            .arg("--version")
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false);
        // For ab, version flag is different
        let found = found || std::process::Command::new(tool)
            .arg("-V")
            .output()
            .map(|o| o.status.success() || !o.stderr.is_empty())
            .unwrap_or(false);
        if found {
            return Some(tool.to_string());
        }
    }
    None
}

fn run_tool(tool: &str, url: &str, n: u64, c: u64, dur: Option<u64>) -> anyhow::Result<()> {
    let mut cmd = std::process::Command::new(tool);
    match tool {
        "oha" => {
            cmd.arg("-n").arg(n.to_string());
            cmd.arg("-c").arg(c.to_string());
            if let Some(d) = dur {
                cmd.arg("-z").arg(format!("{d}s"));
            }
            cmd.arg(url);
        }
        "wrk" => {
            cmd.arg("-t").arg((c / 4).max(1).to_string());
            cmd.arg("-c").arg(c.to_string());
            if let Some(d) = dur {
                cmd.arg("-d").arg(format!("{d}s"));
            } else {
                cmd.arg("-d").arg("10s");
            }
            cmd.arg(url);
        }
        "ab" => {
            cmd.arg("-n").arg(n.to_string());
            cmd.arg("-c").arg(c.to_string());
            cmd.arg(url);
        }
        _ => {}
    }

    let status = cmd.status()?;
    if !status.success() {
        anyhow::bail!("{tool} exited with status {status}");
    }
    Ok(())
}