From ed9d1b4ee5241554030b67424faf5cc40e1634c7 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Sat, 14 Jan 2023 02:50:39 -0500 Subject: More improvements - Better printing out of header and columns, we now align to terminal size - Don't panic on broken pipe - Print out more info when testing first/last commits Signed-off-by: Kent Overstreet --- Cargo.toml | 1 + src/main.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1cd4414..5815e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ nom = "7.1.2" pager = "0.16.1" serde = "1.0.152" serde_derive = "1.0.152" +termsize = "0.1.6" toml = "0.5.10" diff --git a/src/main.rs b/src/main.rs index a51c03a..4328808 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::BTreeSet; use std::io; use std::io::Write; use std::error::Error; @@ -14,12 +14,8 @@ use toml; const RESULTS: &str = "perf-results.toml"; +/* Results for each test, for a particular commit */ type DataPoints = HashMap; -type DataPointsDeltas = HashMap; - -fn dps_to_dpdeltas(d: &DataPoints) -> DataPointsDeltas { - d.iter().map(|x| (x.0.clone(), (*x.1, 0.0)) ).collect() -} struct TestResult { commit: String, @@ -29,6 +25,13 @@ struct TestResult { type TestResultsVec = Vec; +/* Test results and delta (ratio) from previous commit */ +type DataPointsDeltas = HashMap; + +fn dps_to_dpdeltas(d: &DataPoints) -> DataPointsDeltas { + d.iter().map(|x| (x.0.clone(), (*x.1, 0.0)) ).collect() +} + struct TestResultDeltas { commit: String, commitmsg: String, @@ -39,6 +42,7 @@ type TestResultsDeltasVec = Vec; #[derive(Serialize, Deserialize)] struct TestResultsMap { + #[serde(flatten)] d: HashMap, } @@ -101,12 +105,24 @@ fn results_merge(r1: &mut TestResultsVec, r2: &TestResultsMap) { fn pick_commit_to_test(results: &TestResultsVec, test: &str) -> Option { let i = results.first().unwrap(); if i.data_points.get(test).is_none() { - return Some(0); + let idx = 0; + + println!("Testing first commit"); + println!("{} {} {:89}", idx, + results[idx].commit[..10].yellow(), + results[idx].commitmsg); + return Some(idx); } let i = results.last().unwrap(); if i.data_points.get(test).is_none() { - return Some(results.len() - 1); + let idx = results.len() - 1; + + println!("Testing last commit"); + println!("{} {} {:89}", idx, + results[idx].commit[..10].yellow(), + results[idx].commitmsg); + return Some(idx); } let mut last_idx: usize = 0; @@ -169,7 +185,7 @@ fn parse_test_output(output: &str) -> Option { None } -fn run_tests(repo: &git2::Repository, range: &str, test: &str) { +fn cmd_run(repo: &git2::Repository, range: &str, test: &str) { let mut results = results_new(&repo, range).unwrap(); let mut existing = results_read(RESULTS).unwrap_or(TestResultsMap { d: HashMap::new() }); @@ -280,30 +296,53 @@ fn results_to_results_with_deltas(results: TestResultsVec) -> TestResultsDeltasV results } -fn list_tests(repo: &git2::Repository, head: &Option) { +fn cmd_log(repo: &git2::Repository, head: &Option) -> io::Result<()> { + /* We use write! to stdout instead of print! to avoid panicing on a broken pipe: */ + let mut stdout = io::stdout(); + colored::control::set_override(ShouldColorize::from_env().should_colorize()); Pager::with_pager("less -FRX").setup(); - let results = results_read(RESULTS).unwrap(); + let results = results_read(RESULTS); + if results.is_err() { + eprintln!("No results found"); + std::process::exit(1); + } + let results = results.unwrap(); + let log = log_with_results(repo, head, &results).unwrap(); let log = results_to_results_with_deltas(log); - let mut columns = HashSet::new(); + let mut columns = BTreeSet::new(); for r in log.iter() { for e in r.data_points.iter() { columns.insert(e.0); } } - print!("{:100}", ""); - for e in columns.iter() { - print!("{:20}", e); + let term_cols = termsize::get().map(|x| x.cols); + let msg_width = if let Some(term_cols) = term_cols { + term_cols as usize - 11 - columns.len() * 20 + } else { + 89 + }; + + for (i, e) in columns.iter().enumerate() { + if i != 0 && e.len() >= 20 { + writeln!(stdout, "")?; + } + + if i == 0 || e.len() >= 20 { + write!(stdout, "{:>1$}", e, msg_width + 11 + (i + 1) * 20)?; + } else { + write!(stdout, "{:>1$}", e, 20)?; + } } - println!(""); + writeln!(stdout, "")?; for i in log.iter() { - print!("{} {:89}", &i.commit[..10].yellow(), i.commitmsg); + write!(stdout, "{} {:1$.*}", &i.commit[..10].yellow(), msg_width, i.commitmsg)?; for e in columns.iter() { if let Some(v) = i.data_points.get(e.clone()) { @@ -314,17 +353,22 @@ fn list_tests(repo: &git2::Repository, head: &Option) { f.normal() }; - print!("{}", f); + write!(stdout, "{}", f)?; } else { - print!("{:20}", ""); + write!(stdout, "{:20}", "")?; } } - println!(""); + writeln!(stdout, "")?; } + + Ok(()) } #[derive(Parser)] -#[command(author, version, about, long_about = None)] +#[command(name = "bisect-perf-regressions")] +#[command(author = "Kent Overstreet ")] +#[command(version = "0.1")] +#[command(about = "Bisect performance regressions and provide results in a git log view")] struct Args { #[command(subcommand)] command: Option, @@ -336,7 +380,7 @@ enum Commands { range: String, test: String, }, - List { + Log { head: Option }, } @@ -346,10 +390,10 @@ fn main() { let repo = git2::Repository::open(".").unwrap(); match &args.command { - Some(Commands::List { head } ) - => list_tests(&repo, head), + Some(Commands::Log { head } ) + => { cmd_log(&repo, head).ok(); }, Some(Commands::Run { range, test} ) - => run_tests(&repo, range, test), + => cmd_run(&repo, range, test), None => {} } -- cgit v1.2.3