Skip to content

Commit 108b2b7

Browse files
orhunKobzol
authored andcommitted
Reimplement artifact selection in bench_cmp using ratatui
1 parent 3982771 commit 108b2b7

File tree

2 files changed

+161
-140
lines changed

2 files changed

+161
-140
lines changed

collector/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ object = "0.36.0"
3838
tabled = { version = "0.17.0", features = ["ansi-str"] }
3939
humansize = "2.1.3"
4040
regex = "1.7.1"
41-
4241
analyzeme = "12.0.0"
43-
inquire = "0.7.5"
42+
ratatui = "0.29"
4443

4544
benchlib = { path = "benchlib" }
4645
database = { path = "../database" }

collector/src/compare.rs

Lines changed: 160 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use std::{str::FromStr, sync::Arc};
2-
3-
use database::{
4-
metric::Metric,
5-
selector::{BenchmarkQuery, CompileBenchmarkQuery},
6-
ArtifactId, Connection,
1+
use database::{metric::Metric, Commit, Connection, Index};
2+
use ratatui::widgets::{List, ListState};
3+
use ratatui::{
4+
crossterm::event::{self, Event, KeyCode},
5+
prelude::*,
6+
widgets::Block,
77
};
8-
use tabled::{Table, Tabled};
98

109
static ALL_METRICS: &[Metric] = &[
1110
Metric::InstructionsUser,
@@ -46,160 +45,183 @@ pub async fn compare_artifacts(
4645
) -> anyhow::Result<()> {
4746
let index = database::Index::load(&mut *conn).await;
4847

49-
let metric = match metric {
50-
Some(v) => v,
51-
None => {
52-
let metric_str = inquire::Select::new(
53-
"Choose metric:",
54-
ALL_METRICS.iter().map(|m| m.as_str()).collect::<Vec<_>>(),
55-
)
56-
.prompt()?;
57-
Metric::from_str(metric_str).map_err(|e| anyhow::anyhow!(e))?
58-
}
59-
};
48+
let metric = metric.unwrap_or(Metric::InstructionsUser);
6049

61-
let mut aids = index.artifacts().map(str::to_string).collect::<Vec<_>>();
50+
let mut aids = index.commits();
6251
if aids.len() < 2 {
6352
return Err(anyhow::anyhow!(
6453
"There are not enough artifacts to compare, at least two are needed"
6554
));
6655
}
6756

68-
let select_artifact_id = |name: &str, aids: &Vec<String>| {
69-
anyhow::Ok(
70-
inquire::Select::new(
71-
&format!("Choose {} artifact to compare:", name),
72-
aids.clone(),
73-
)
74-
.prompt()?,
75-
)
76-
};
57+
// Error if the selected base/modified commits were not found
58+
fn check_commit(
59+
aids: &[Commit],
60+
commit: Option<String>,
61+
label: &str,
62+
) -> anyhow::Result<Option<Commit>> {
63+
Ok(commit
64+
.map(|commit| {
65+
aids.iter()
66+
.find(|c| c.sha == commit)
67+
.cloned()
68+
.ok_or_else(|| anyhow::anyhow!("{label} commit {commit} not found"))
69+
})
70+
.transpose()?)
71+
}
7772

78-
let base = match base {
79-
Some(v) => v,
80-
None => select_artifact_id("base", &aids)?.to_string(),
81-
};
82-
aids.retain(|id| id != &base);
83-
let modified = if let [new_modified] = &aids[..] {
84-
let new_modified = new_modified.clone();
85-
println!(
86-
"Only 1 artifact remains, automatically selecting: {}",
87-
new_modified
88-
);
89-
90-
new_modified
91-
} else {
92-
match modified {
93-
Some(v) => v,
94-
None => select_artifact_id("modified", &aids)?.to_string(),
73+
let base: Option<Commit> = check_commit(&aids, base, "Base")?;
74+
if let Some(ref base) = base {
75+
aids.retain(|c| c.sha != base.sha);
76+
}
77+
let modified: Option<Commit> = check_commit(&aids, modified, "Modified")?;
78+
79+
let mut terminal = ratatui::init();
80+
81+
let mut screen: Box<dyn Screen> = match (base, modified) {
82+
(Some(base), Some(modified)) => Box::new(CompareScreen { base, modified }),
83+
(Some(base), None) => next_selection_screen(base, aids),
84+
(None, None) => Box::new(SelectArtifactScreen::new(aids, SelectState::SelectingBase)),
85+
(None, Some(_)) => {
86+
return Err(anyhow::anyhow!(
87+
"If modified commit is pre-selected, base commit must also be pre-selected"
88+
));
9589
}
9690
};
97-
98-
let query = CompileBenchmarkQuery::default().metric(database::selector::Selector::One(metric));
99-
let resp = query
100-
.execute(
101-
&mut *conn,
102-
&index,
103-
Arc::new(vec![ArtifactId::Tag(base), ArtifactId::Tag(modified)]),
104-
)
105-
.await
106-
.unwrap();
107-
108-
let tuple_pstats = resp
109-
.into_iter()
110-
.map(|resp| {
111-
let points = resp.series.points.collect::<Vec<_>>();
112-
(points[0], points[1])
113-
})
114-
.collect::<Vec<_>>();
115-
116-
#[derive(Tabled)]
117-
struct Regression {
118-
count: usize,
119-
#[tabled(display_with = "display_range")]
120-
range: (Option<f64>, Option<f64>),
121-
#[tabled(display_with = "display_mean")]
122-
mean: Option<f64>,
123-
}
124-
125-
fn format_value(value: Option<f64>) -> String {
126-
match value {
127-
Some(value) => format!("{:+.2}%", value),
128-
None => "-".to_string(),
91+
let mut state = State { metric, index };
92+
93+
loop {
94+
terminal.draw(|frame| {
95+
screen.draw(frame, &state);
96+
})?;
97+
match event::read()? {
98+
Event::Key(key_event) => match key_event.code {
99+
KeyCode::Char('q') | KeyCode::Esc => break,
100+
key => {
101+
if let Some(action) = screen.handle_key(key, &mut state) {
102+
match action {
103+
Action::ChangeScreen(next_screen) => {
104+
screen = next_screen;
105+
}
106+
}
107+
}
108+
}
109+
},
110+
_ => {}
129111
}
130112
}
113+
ratatui::restore();
131114

132-
fn display_range(&(min, max): &(Option<f64>, Option<f64>)) -> String {
133-
format!("[{}, {}]", &format_value(min), &format_value(max))
134-
}
115+
Ok(())
116+
}
135117

136-
fn display_mean(value: &Option<f64>) -> String {
137-
match value {
138-
Some(value) => format!("{:+.2}%", value),
139-
None => "-".to_string(),
140-
}
141-
}
118+
struct State {
119+
metric: Metric,
120+
index: Index,
121+
}
142122

143-
impl From<&Vec<f64>> for Regression {
144-
fn from(value: &Vec<f64>) -> Self {
145-
let min = value.iter().copied().min_by(|a, b| a.total_cmp(b));
146-
let max = value.iter().copied().max_by(|a, b| a.total_cmp(b));
147-
let count = value.len();
148-
149-
Regression {
150-
range: (min, max),
151-
count,
152-
mean: if count == 0 {
153-
None
154-
} else {
155-
Some(value.iter().sum::<f64>() / count as f64)
156-
},
157-
}
123+
enum Action {
124+
ChangeScreen(Box<dyn Screen>),
125+
}
126+
127+
trait Screen {
128+
fn draw(&mut self, frame: &mut Frame, state: &State);
129+
fn handle_key(&mut self, key: KeyCode, state: &mut State) -> Option<Action>;
130+
}
131+
132+
enum SelectState {
133+
SelectingBase,
134+
SelectingModified { base: Commit },
135+
}
136+
137+
struct SelectArtifactScreen {
138+
aids: Vec<Commit>,
139+
select_state: SelectState,
140+
list_state: ListState,
141+
}
142+
143+
impl SelectArtifactScreen {
144+
fn new(aids: Vec<Commit>, select_state: SelectState) -> Self {
145+
Self {
146+
aids,
147+
select_state,
148+
list_state: ListState::default().with_selected(Some(0)),
158149
}
159150
}
151+
}
160152

161-
let change = tuple_pstats
162-
.iter()
163-
.filter_map(|&(a, b)| match (a, b) {
164-
(Some(a), Some(b)) => {
165-
if a == 0.0 {
166-
None
153+
impl Screen for SelectArtifactScreen {
154+
fn draw(&mut self, frame: &mut Frame, _state: &State) {
155+
let items = self
156+
.aids
157+
.iter()
158+
.map(|commit| format!("{} ({})", commit.sha, commit.date))
159+
.collect::<Vec<_>>();
160+
let list = List::new(items)
161+
.block(Block::bordered().title(format!(
162+
"Select {} artifact",
163+
if matches!(self.select_state, SelectState::SelectingBase) {
164+
"base"
167165
} else {
168-
Some((b - a) / a * 100.0)
166+
"modified"
169167
}
168+
)))
169+
.style(Style::new().white())
170+
.highlight_style(Style::new().bold())
171+
.highlight_symbol("> ");
172+
frame.render_stateful_widget(list, frame.area(), &mut self.list_state);
173+
}
174+
175+
fn handle_key(&mut self, key: KeyCode, _state: &mut State) -> Option<Action> {
176+
match key {
177+
KeyCode::Down => self.list_state.select_next(),
178+
KeyCode::Up => self.list_state.select_previous(),
179+
KeyCode::Enter => {
180+
let mut aids = self.aids.clone();
181+
let index = self.list_state.selected().unwrap();
182+
let selected = aids.remove(index);
183+
184+
let next_screen: Box<dyn Screen> = match &self.select_state {
185+
SelectState::SelectingBase => next_selection_screen(selected, aids),
186+
SelectState::SelectingModified { base } => Box::new(CompareScreen {
187+
base: base.clone(),
188+
modified: selected,
189+
}),
190+
};
191+
return Some(Action::ChangeScreen(next_screen));
170192
}
171-
(_, _) => None,
172-
})
173-
.collect::<Vec<_>>();
174-
let negative_change = change
175-
.iter()
176-
.copied()
177-
.filter(|&c| c < 0.0)
178-
.collect::<Vec<_>>();
179-
let positive_change = change
180-
.iter()
181-
.copied()
182-
.filter(|&c| c > 0.0)
183-
.collect::<Vec<_>>();
184-
185-
#[derive(Tabled)]
186-
struct NamedRegression {
187-
name: String,
188-
#[tabled(inline)]
189-
regression: Regression,
193+
_ => {}
194+
};
195+
None
190196
}
197+
}
191198

192-
let regressions = [negative_change, positive_change, change]
193-
.into_iter()
194-
.map(|c| Regression::from(&c))
195-
.zip(["❌", "✅", "✅, ❌"])
196-
.map(|(c, label)| NamedRegression {
197-
name: label.to_string(),
198-
regression: c,
199-
})
200-
.collect::<Vec<_>>();
199+
/// Directly goes to comparison if there is only a single artifact ID left, otherwise
200+
/// opens the selection screen for the modified artifact.
201+
fn next_selection_screen(base: Commit, aids: Vec<Commit>) -> Box<dyn Screen> {
202+
match aids.as_slice() {
203+
[commit] => Box::new(CompareScreen {
204+
base,
205+
modified: commit.clone(),
206+
}),
207+
_ => Box::new(SelectArtifactScreen::new(
208+
aids,
209+
SelectState::SelectingModified { base },
210+
)),
211+
}
212+
}
201213

202-
println!("{}", Table::new(regressions));
214+
struct CompareScreen {
215+
base: Commit,
216+
modified: Commit,
217+
}
203218

204-
Ok(())
219+
impl Screen for CompareScreen {
220+
fn draw(&mut self, frame: &mut Frame, state: &State) {
221+
todo!()
222+
}
223+
224+
fn handle_key(&mut self, key: KeyCode, state: &mut State) -> Option<Action> {
225+
todo!()
226+
}
205227
}

0 commit comments

Comments
 (0)