aprender-cgp 0.34.0

Compute-GPU-Profile: Unified performance analysis CLI for scalar, SIMD, wgpu, and CUDA workloads
Documentation
//! `cgp compete` — Head-to-head competitor comparison.
//! Spec section 2.5: run two or more commands, measure wall time,
//! compute TFLOP/s, and produce a comparison table.

use anyhow::Result;
use serde::Serialize;
use std::process::Command;
use std::time::Instant;

/// Result of running one competitor.
#[derive(Debug, Clone, Serialize)]
pub struct CompetitorResult {
    pub label: String,
    pub command: String,
    pub wall_time_ms: f64,
    pub exit_code: i32,
}

/// Run a command and measure wall time.
fn run_timed(command: &str) -> Result<CompetitorResult> {
    // Split command into program + args (basic shell-like splitting)
    let parts: Vec<&str> = command.split_whitespace().collect();
    if parts.is_empty() {
        anyhow::bail!("Empty command");
    }

    let start = Instant::now();
    let status = Command::new(parts[0])
        .args(&parts[1..])
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .map_err(|e| anyhow::anyhow!("Failed to run '{}': {}", parts[0], e))?;
    let elapsed = start.elapsed();

    Ok(CompetitorResult {
        label: String::new(),
        command: command.to_string(),
        wall_time_ms: elapsed.as_secs_f64() * 1000.0,
        exit_code: status.code().unwrap_or(-1),
    })
}

/// Run the `cgp compete` command.
pub fn run_compete(
    workload: &str,
    ours: &str,
    theirs: &[String],
    label: Option<&str>,
    json: bool,
) -> Result<()> {
    let labels = build_labels(label, theirs.len());

    println!("\n=== CGP Head-to-Head: {workload} ===\n");

    let mut results: Vec<CompetitorResult> = Vec::new();
    let default_ours = "ours".to_string();
    let ours_label = labels.first().unwrap_or(&default_ours).clone();
    results.push(run_and_record(&ours_label, ours));

    for (i, cmd) in theirs.iter().enumerate() {
        let default_theirs = format!("theirs-{}", i + 1);
        let lbl = labels.get(i + 1).unwrap_or(&default_theirs).clone();
        results.push(run_and_record(&lbl, cmd));
    }

    if json {
        println!("{}", serde_json::to_string_pretty(&results)?);
        return Ok(());
    }

    print_compete_table(&results);
    print_compete_winner(&results);
    println!();
    Ok(())
}

/// Build label vector: parse comma-separated overrides, or synthesize defaults.
fn build_labels(label: Option<&str>, theirs_len: usize) -> Vec<String> {
    match label {
        Some(l) => l.split(',').map(String::from).collect(),
        None => {
            let mut v = vec!["ours".to_string()];
            v.extend((0..theirs_len).map(|i| format!("theirs-{}", i + 1)));
            v
        }
    }
}

/// Run a single competitor and push a `CompetitorResult` with failure handling.
fn run_and_record(label: &str, command: &str) -> CompetitorResult {
    eprint!("  Running: {label} ...");
    match run_timed(command) {
        Ok(mut r) => {
            r.label = label.to_string();
            eprintln!(" {:.1}ms", r.wall_time_ms);
            r
        }
        Err(e) => {
            eprintln!(" FAILED: {e}");
            CompetitorResult {
                label: label.to_string(),
                command: command.to_string(),
                wall_time_ms: f64::INFINITY,
                exit_code: -1,
            }
        }
    }
}

fn best_finite_time(results: &[CompetitorResult]) -> f64 {
    results
        .iter()
        .filter(|r| r.wall_time_ms.is_finite())
        .map(|r| r.wall_time_ms)
        .fold(f64::INFINITY, f64::min)
}

fn print_compete_table(results: &[CompetitorResult]) {
    let best_time = best_finite_time(results);
    println!();
    println!(
        "  {:20} {:>12} {:>8} {:>10}",
        "Competitor", "Time (ms)", "Exit", "vs Best"
    );
    println!("  {}", "-".repeat(54));
    for r in results {
        let ratio = if best_time > 0.0 && r.wall_time_ms.is_finite() {
            format!("{:.2}x", r.wall_time_ms / best_time)
        } else {
            "N/A".to_string()
        };
        let time_str = if r.wall_time_ms.is_finite() {
            format!("{:.1}", r.wall_time_ms)
        } else {
            "FAILED".to_string()
        };
        println!(
            "  {:20} {:>12} {:>8} {:>10}",
            r.label, time_str, r.exit_code, ratio
        );
    }
}

fn print_compete_winner(results: &[CompetitorResult]) {
    let Some(winner) = results
        .iter()
        .filter(|r| r.wall_time_ms.is_finite())
        .min_by(|a, b| {
            a.wall_time_ms
                .partial_cmp(&b.wall_time_ms)
                .unwrap_or(std::cmp::Ordering::Equal)
        })
    else {
        return;
    };
    println!(
        "\n  Winner: {} ({:.1}ms)",
        winner.label, winner.wall_time_ms
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_run_timed_true() {
        let result = run_timed("true").unwrap();
        assert_eq!(result.exit_code, 0);
        assert!(result.wall_time_ms < 1000.0);
    }

    #[test]
    fn test_run_timed_false() {
        let result = run_timed("false").unwrap();
        assert_ne!(result.exit_code, 0);
    }

    #[test]
    fn test_run_timed_nonexistent() {
        let result = run_timed("nonexistent_binary_xyz_123");
        assert!(result.is_err());
    }
}