Skip to content

Commit ea5ccb2

Browse files
committed
refactor: extract handle_event method misc
Signed-off-by: Wenxuan Zhang <[email protected]>
1 parent 3ca6a6e commit ea5ccb2

File tree

1 file changed

+159
-133
lines changed

1 file changed

+159
-133
lines changed

src/collector/tui.rs

Lines changed: 159 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ cfg_if::cfg_if! {
3131
}
3232

3333
use crate::{
34+
clock::Clock,
3435
collector::ReportCollector,
3536
duration::DurationExt,
3637
histogram::{LatencyHistogram, PERCENTAGES},
@@ -120,17 +121,31 @@ impl Drop for Terminal {
120121
#[async_trait]
121122
impl ReportCollector for TuiCollector {
122123
async fn run(&mut self) -> Result<BenchReport> {
123-
let mut terminal = Terminal::new()?;
124-
124+
let clock = self.bench_opts.clock.clone();
125125
let mut hist = LatencyHistogram::new();
126126
let mut stats = IterStats::new();
127127
let mut status_dist = HashMap::new();
128128
let mut error_dist = HashMap::new();
129129

130-
let mut current_tw = TimeWindow::Second;
131-
let mut auto_tw = true;
130+
self.collect(clock.clone(), &mut hist, &mut stats, &mut status_dist, &mut error_dist)
131+
.await?;
132132

133-
let mut clock = self.bench_opts.clock.clone();
133+
let elapsed = clock.elapsed();
134+
let concurrency = self.bench_opts.concurrency;
135+
Ok(BenchReport { concurrency, hist, stats, status_dist, error_dist, elapsed })
136+
}
137+
}
138+
139+
impl TuiCollector {
140+
async fn collect(
141+
&mut self,
142+
mut clock: Clock,
143+
hist: &mut LatencyHistogram,
144+
stats: &mut IterStats,
145+
status_dist: &mut HashMap<Status, u64>,
146+
error_dist: &mut HashMap<String, u64>,
147+
) -> Result<()> {
148+
let mut terminal = Terminal::new()?;
134149

135150
let mut latest_iters = RotateWindowGroup::new(nonzero!(60usize));
136151
let mut latest_iters_ticker = clock.ticker(SECOND);
@@ -141,75 +156,17 @@ impl ReportCollector for TuiCollector {
141156
let mut ui_ticker = tokio::time::interval(SECOND / self.fps.get() as u32);
142157
ui_ticker.set_missed_tick_behavior(MissedTickBehavior::Burst);
143158

159+
let mut tm_win = TimeWindow::Second;
144160
#[cfg(feature = "log")]
145161
let mut show_logs = false;
146162

147-
let mut elapsed;
148-
'outer: loop {
163+
loop {
149164
loop {
150165
tokio::select! {
151166
biased;
152-
_ = ui_ticker.tick() => {
153-
while crossterm::event::poll(Duration::from_secs(0))? {
154-
use KeyCode::*;
155-
if let Event::Key(KeyEvent { code, modifiers, .. }) = crossterm::event::read()? {
156-
match (code, modifiers) {
157-
(Char('+'), _) => {
158-
current_tw = current_tw.prev();
159-
auto_tw = false;
160-
}
161-
(Char('-'), _) => {
162-
current_tw = current_tw.next();
163-
auto_tw = false;
164-
}
165-
(Char('a'), _) => auto_tw = true,
166-
(Char('q'), _) | (Char('c'), KeyModifiers::CONTROL) => {
167-
self.cancel.cancel();
168-
break 'outer;
169-
}
170-
(Char('p') | Pause, _) => {
171-
let pause = !*self.pause.borrow();
172-
if pause {
173-
clock.pause();
174-
} else {
175-
clock.resume();
176-
}
177-
self.pause.send_replace(pause);
178-
}
179-
#[cfg(feature = "log")]
180-
(Char('l'), _) => show_logs = !show_logs,
181-
#[cfg(feature = "log")]
182-
(code, _) if show_logs => {
183-
use TuiWidgetEvent::*;
184-
let mut txn = |e| self.log_state.transition(e);
185-
match code {
186-
Char(' ') => txn(HideKey),
187-
PageDown | Char('f') => txn(NextPageKey),
188-
PageUp | Char('b') => txn(PrevPageKey),
189-
Up => txn(UpKey),
190-
Down => txn(DownKey),
191-
Left => txn(LeftKey),
192-
Right => txn(RightKey),
193-
Enter => txn(FocusKey),
194-
Esc => txn(EscapeKey),
195-
_ => (),
196-
}
197-
}
198-
_ => (),
199-
}
200-
}
201-
}
202-
203-
elapsed = clock.elapsed();
204-
current_tw = if auto_tw {
205-
*TimeWindow::variants().iter().rfind(|&&ts| elapsed > ts.into()).unwrap_or(&TimeWindow::Second)
206-
} else {
207-
current_tw
208-
};
209-
break;
210-
}
167+
_ = ui_ticker.tick() => break,
211168
_ = latest_stats_ticker.tick() => {
212-
latest_stats.rotate(&stats);
169+
latest_stats.rotate(stats);
213170
continue;
214171
}
215172
_ = latest_iters_ticker.tick() => {
@@ -221,14 +178,28 @@ impl ReportCollector for TuiCollector {
221178
*status_dist.entry(report.status).or_default() += 1;
222179
hist.record(report.duration)?;
223180
latest_iters.push(&report);
224-
stats += &report;
181+
*stats += &report;
225182
}
226183
Some(Err(e)) => *error_dist.entry(e.to_string()).or_default() += 1,
227-
None => break 'outer,
184+
None => return Ok(()),
228185
}
229186
};
230187
}
231188

189+
let elapsed = clock.elapsed();
190+
let exit = self
191+
.handle_event(
192+
elapsed,
193+
&mut tm_win,
194+
&mut clock,
195+
#[cfg(feature = "log")]
196+
&mut show_logs,
197+
)
198+
.await?;
199+
if exit {
200+
return Ok(());
201+
}
202+
232203
terminal.draw(|f| {
233204
let progress_height = 3;
234205
let stats_height = 5;
@@ -266,23 +237,79 @@ impl ReportCollector for TuiCollector {
266237
let paused = *self.pause.borrow();
267238
render_process_gauge(f, rows[3], &stats.counter, elapsed, &self.bench_opts, paused);
268239
render_stats_overall(f, mid[1], &stats.counter, elapsed);
269-
render_stats_timewin(f, mid[0], &latest_stats, current_tw);
270-
render_status_dist(f, mid[2], &status_dist);
271-
render_error_dist(f, rows[1], &error_dist);
272-
render_iter_hist(f, bot[0], &latest_iters, current_tw);
273-
render_latency_hist(f, bot[1], &hist, 7);
240+
render_stats_timewin(f, mid[0], &latest_stats, tm_win);
241+
render_status_dist(f, mid[2], status_dist);
242+
render_error_dist(f, rows[1], error_dist);
243+
render_iter_hist(f, bot[0], &latest_iters, tm_win);
244+
render_latency_hist(f, bot[1], hist, 7);
274245
render_tips(f, rows[4]);
275246

276247
#[cfg(feature = "log")]
277-
if show_logs {
278-
tui_log::render_logs(f, &self.log_state);
279-
}
248+
render_logs(f, &self.log_state, show_logs);
280249
})?;
281250
}
251+
}
282252

283-
let elapsed = clock.elapsed();
284-
let concurrency = self.bench_opts.concurrency;
285-
Ok(BenchReport { concurrency, hist, stats, status_dist, error_dist, elapsed })
253+
async fn handle_event(
254+
&mut self,
255+
elapsed: Duration,
256+
tm_win: &mut TimeWindow,
257+
clock: &mut Clock,
258+
#[cfg(feature = "log")] show_logs: &mut bool,
259+
) -> Result<bool> {
260+
while crossterm::event::poll(Duration::from_secs(0))? {
261+
use KeyCode::*;
262+
if let Event::Key(KeyEvent { code, modifiers, .. }) = crossterm::event::read()? {
263+
match (code, modifiers) {
264+
(Char('+'), _) => {
265+
*tm_win = tm_win.prev();
266+
}
267+
(Char('-'), _) => {
268+
*tm_win = tm_win.next();
269+
}
270+
(Char('a'), _) => {
271+
*tm_win = *TimeWindow::variants()
272+
.iter()
273+
.rfind(|&&ts| elapsed > ts.into())
274+
.unwrap_or(&TimeWindow::Second)
275+
}
276+
(Char('q'), _) | (Char('c'), KeyModifiers::CONTROL) => {
277+
self.cancel.cancel();
278+
return Ok(true);
279+
}
280+
(Char('p') | Pause, _) => {
281+
let pause = !*self.pause.borrow();
282+
if pause {
283+
clock.pause();
284+
} else {
285+
clock.resume();
286+
}
287+
self.pause.send_replace(pause);
288+
}
289+
#[cfg(feature = "log")]
290+
(Char('l'), _) => *show_logs = !*show_logs,
291+
#[cfg(feature = "log")]
292+
(code, _) if *show_logs => {
293+
use TuiWidgetEvent::*;
294+
let mut txn = |e| self.log_state.transition(e);
295+
match code {
296+
Char(' ') => txn(HideKey),
297+
PageDown | Char('f') => txn(NextPageKey),
298+
PageUp | Char('b') => txn(PrevPageKey),
299+
Up => txn(UpKey),
300+
Down => txn(DownKey),
301+
Left => txn(LeftKey),
302+
Right => txn(RightKey),
303+
Enter => txn(FocusKey),
304+
Esc => txn(EscapeKey),
305+
_ => (),
306+
}
307+
}
308+
_ => (),
309+
}
310+
}
311+
}
312+
Ok(false)
286313
}
287314
}
288315

@@ -625,61 +652,60 @@ impl TimeWindow {
625652
}
626653

627654
#[cfg(feature = "log")]
628-
mod tui_log {
629-
use super::*;
655+
pub(crate) fn render_logs(frame: &mut Frame, log_state: &TuiWidgetState, show_logs: bool) {
656+
if !show_logs {
657+
return;
658+
}
630659

631-
#[cfg(feature = "log")]
632-
pub(crate) fn render_logs(frame: &mut Frame, log_state: &TuiWidgetState) {
633-
let log_widget = TuiLoggerSmartWidget::default()
634-
.style_error(Style::default().fg(Color::Red))
635-
.style_debug(Style::default().fg(Color::Green))
636-
.style_warn(Style::default().fg(Color::Yellow))
637-
.style_trace(Style::default().fg(Color::Magenta))
638-
.style_info(Style::default().fg(Color::Cyan))
639-
.border_type(ratatui::widgets::BorderType::Rounded)
640-
.output_separator('|')
641-
.output_level(Some(TuiLoggerLevelOutput::Abbreviated))
642-
.output_target(true)
643-
.output_file(true)
644-
.output_line(true)
645-
.title_log("Logs")
646-
.title_target("Selector")
647-
.state(log_state);
648-
649-
let area = centered_rect(80, 80, frame.size());
650-
let rows = Layout::default()
651-
.direction(Direction::Vertical)
652-
.constraints([Constraint::Percentage(100), Constraint::Min(1)])
653-
.split(area.inner(&Margin::new(1, 1)));
654-
let tips = gen_tips([
655-
("Enter", "Focus target"),
656-
("↑/↓", "Select target"),
657-
("←/→", "Display level"),
658-
("f/b", "Scroll"),
659-
("Esc", "Cancel scroll"),
660-
("Space", "Hide selector"),
661-
])
662-
.right_aligned();
660+
let log_widget = TuiLoggerSmartWidget::default()
661+
.style_error(Style::default().fg(Color::Red))
662+
.style_debug(Style::default().fg(Color::Green))
663+
.style_warn(Style::default().fg(Color::Yellow))
664+
.style_trace(Style::default().fg(Color::Magenta))
665+
.style_info(Style::default().fg(Color::Cyan))
666+
.border_type(ratatui::widgets::BorderType::Rounded)
667+
.output_separator('|')
668+
.output_level(Some(TuiLoggerLevelOutput::Abbreviated))
669+
.output_target(true)
670+
.output_file(true)
671+
.output_line(true)
672+
.title_log("Logs")
673+
.title_target("Selector")
674+
.state(log_state);
675+
676+
let area = centered_rect(80, 80, frame.size());
677+
let rows = Layout::default()
678+
.direction(Direction::Vertical)
679+
.constraints([Constraint::Percentage(100), Constraint::Min(1)])
680+
.split(area.inner(&Margin::new(1, 1)));
681+
let tips = gen_tips([
682+
("Enter", "Focus target"),
683+
("↑/↓", "Select target"),
684+
("←/→", "Display level"),
685+
("f/b", "Scroll"),
686+
("Esc", "Cancel scroll"),
687+
("Space", "Hide selector"),
688+
])
689+
.right_aligned();
663690

664-
frame.render_widget(Clear, area);
665-
frame.render_widget(log_widget, rows[0]);
666-
frame.render_widget(tips, rows[1].inner(&Margin::new(1, 0)));
667-
}
691+
frame.render_widget(Clear, area);
692+
frame.render_widget(log_widget, rows[0]);
693+
frame.render_widget(tips, rows[1].inner(&Margin::new(1, 0)));
694+
}
668695

669-
#[cfg(feature = "log")]
670-
pub(crate) fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
671-
let popup_layout = Layout::vertical([
672-
Constraint::Percentage((100 - percent_y) / 2),
673-
Constraint::Percentage(percent_y),
674-
Constraint::Percentage((100 - percent_y) / 2),
675-
])
676-
.split(r);
696+
#[cfg(feature = "log")]
697+
pub(crate) fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
698+
let popup_layout = Layout::vertical([
699+
Constraint::Percentage((100 - percent_y) / 2),
700+
Constraint::Percentage(percent_y),
701+
Constraint::Percentage((100 - percent_y) / 2),
702+
])
703+
.split(r);
677704

678-
Layout::horizontal([
679-
Constraint::Percentage((100 - percent_x) / 2),
680-
Constraint::Percentage(percent_x),
681-
Constraint::Percentage((100 - percent_x) / 2),
682-
])
683-
.split(popup_layout[1])[1]
684-
}
705+
Layout::horizontal([
706+
Constraint::Percentage((100 - percent_x) / 2),
707+
Constraint::Percentage(percent_x),
708+
Constraint::Percentage((100 - percent_x) / 2),
709+
])
710+
.split(popup_layout[1])[1]
685711
}

0 commit comments

Comments
 (0)