diff --git a/Cargo.lock b/Cargo.lock index 55d0441f5..014eab7a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.99" @@ -366,7 +381,7 @@ version = "4.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -405,6 +420,7 @@ dependencies = [ "log", "miow", "object", + "ratatui", "rayon", "regex", "reqwest", @@ -425,6 +441,20 @@ dependencies = [ "xz2", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.7" @@ -534,6 +564,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio 1.0.1", + "parking_lot", + "rustix 0.38.37", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -738,6 +784,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1055,6 +1111,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1247,7 +1309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ "bitflags 2.6.0", - "crossterm", + "crossterm 0.25.0", "dyn-clone", "fuzzy-matcher", "fxhash", @@ -1257,6 +1319,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn 2.0.41", +] + [[package]] name = "instant" version = "0.1.12" @@ -1303,7 +1375,7 @@ checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" dependencies = [ "hermit-abi", "io-lifetimes", - "rustix", + "rustix 0.36.10", "windows-sys 0.45.0", ] @@ -1316,6 +1388,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1379,9 +1460,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -1406,6 +1487,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.9" @@ -1544,6 +1631,7 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -1987,6 +2075,27 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "strum_macros", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "rayon" version = "1.10.0" @@ -2205,13 +2314,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778" dependencies = [ "bitflags 1.3.2", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", "windows-sys 0.45.0", ] +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "errno 0.3.9", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ruzstd" version = "0.7.0" @@ -2385,6 +2513,7 @@ checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", + "mio 1.0.1", "signal-hook", ] @@ -2430,7 +2559,7 @@ dependencies = [ "humansize", "hyper", "inferno", - "itertools", + "itertools 0.10.5", "jemalloc-ctl", "jemallocator", "lazy_static", @@ -2542,6 +2671,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.41", +] + [[package]] name = "subtle" version = "2.4.1" @@ -2588,7 +2739,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -2615,7 +2766,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix", + "rustix 0.36.10", "windows-sys 0.42.0", ] @@ -2956,6 +3107,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.13" diff --git a/collector/Cargo.toml b/collector/Cargo.toml index d34b023be..c75e674fc 100644 --- a/collector/Cargo.toml +++ b/collector/Cargo.toml @@ -45,6 +45,7 @@ inquire = "0.7.5" benchlib = { path = "benchlib" } database = { path = "../database" } +ratatui = "0.28.1" [target.'cfg(windows)'.dependencies] miow = "0.3" diff --git a/collector/src/compare.rs b/collector/src/compare.rs index b7b21305b..7d1370dac 100644 --- a/collector/src/compare.rs +++ b/collector/src/compare.rs @@ -5,7 +5,12 @@ use database::{ selector::{BenchmarkQuery, CompileBenchmarkQuery}, ArtifactId, Connection, }; -use tabled::{Table, Tabled}; +use ratatui::{ + crossterm::event::{self, Event, KeyCode}, + prelude::*, + widgets::{Block, Paragraph, Row, Table, TableState}, +}; +use tabled::{Table as TTable, Tabled}; static ALL_METRICS: &[Metric] = &[ Metric::InstructionsUser, @@ -106,9 +111,9 @@ pub async fn compare_artifacts( .unwrap(); let tuple_pstats = resp - .into_iter() + .iter() .map(|resp| { - let points = resp.series.points.collect::>(); + let points = resp.series.points.clone().collect::>(); (points[0], points[1]) }) .collect::>(); @@ -199,7 +204,121 @@ pub async fn compare_artifacts( }) .collect::>(); - println!("{}", Table::new(regressions)); + let regressions_table = TTable::new(regressions).to_string(); + + #[derive(Default)] + struct State { + table_state: TableState, + } + + let mut terminal = ratatui::init(); + let mut state = State::default(); + state.table_state.select(Some(0)); + loop { + terminal + .draw(|frame: &mut Frame| { + let layout = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints( + [ + Constraint::Min(regressions_table.lines().count() as u16), + Constraint::Percentage(100), + ] + .as_ref(), + ) + .split(frame.area()); + frame.render_widget( + Paragraph::new(Text::raw(regressions_table.clone())) + .block(Block::bordered().title("Regression")), + layout[0], + ); + let header = Row::new(vec![ + "Benchmark", + "Profile", + "Scenario", + "Backend", + "Change", + ]); + let widths = [30, 10, 40, 20, 20]; + let mut rows = vec![]; + for x in resp.iter() { + let points = x.series.points.clone().collect::>(); + let change = match (points[0], points[1]) { + (Some(base), Some(modified)) => { + if base == 0.0 { + None + } else { + Some((modified - base) / base * 100.0) + } + } + (_, _) => None, + }; + let profile = x.test_case.profile.to_string(); + let scenario = x.test_case.scenario.to_string(); + let backend = x.test_case.backend.to_string(); + rows.push(vec![ + x.test_case.benchmark.to_string(), + profile, + scenario, + backend, + change + .map(|c| format!("{:+.2}%", c)) + .unwrap_or("-".to_string()), + ]); + } + + rows.sort_by(|a, b| { + let a = a[4].trim_end_matches("%").parse::().unwrap_or(0.0); + let b = b[4].trim_end_matches("%").parse::().unwrap_or(0.0); + b.abs().partial_cmp(&a.abs()).unwrap() + }); + + frame.render_stateful_widget( + Table::new( + rows.into_iter() + .map(|v| { + let change = + v[4].trim_end_matches("%").parse::().unwrap_or(0.0); + let mut cells = + v.into_iter().map(|c| Span::from(c)).collect::>(); + if change < 0.0 { + cells[4] = cells[4].clone().green(); + } else { + cells[4] = cells[4].clone().red(); + } + + Row::new(cells) + }) + .collect::>(), + widths, + ) + .header(header) + .block(Block::bordered().title("Benchmarks")) + .column_spacing(1) + .highlight_symbol("> ") + .highlight_style(Style::new().bg(Color::DarkGray).fg(Color::Black)), + layout[1], + &mut state.table_state, + ); + }) + .expect("failed to draw frame"); + + match event::read()? { + Event::Key(key_event) => match key_event.code { + KeyCode::Char('q') | KeyCode::Esc => break, + KeyCode::Down => { + state.table_state.select_next(); + } + KeyCode::Up => { + state.table_state.select_previous(); + } + _ => {} + }, + _ => {} + } + } + ratatui::restore(); Ok(()) }