in src/app.rs [272:420]
fn render(self, area: Rect, buf: &mut Buffer) {
let state = self.store.lock().unwrap().get_state();
let data = self.create_datasets(state.clone());
let main_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(20)])
.split(area);
let bottom_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(main_layout[1]);
let steps_graph_layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
.split(bottom_layout[0]);
// LOGS
let logs_title = Line::from("Logs".bold()).centered();
let logs_block = Block::bordered()
.title_top(logs_title)
.border_set(border::THICK);
List::new(
state
.messages
.iter()
.rev()
.map(|m| {
let level_span = match m.level {
LogLevel::Info => {
Span::raw(m.level.to_string().to_uppercase()).green().bold()
}
LogLevel::Warning => Span::raw(m.level.to_string().to_uppercase())
.yellow()
.bold(),
LogLevel::Error => {
Span::raw(m.level.to_string().to_uppercase()).red().bold()
}
};
let content = Line::from(vec![
m.formatted_timestamp().clone().gray(),
Span::raw(" "),
level_span,
Span::raw(" "),
Span::raw(m.message.to_string()).bold(),
]);
ListItem::new(content)
})
.collect::<Vec<_>>(),
)
.direction(BottomToTop)
.block(logs_block)
.render(bottom_layout[1], buf);
// BENCHMARK config
let rate_mode = match self.benchmark_config.rates {
None => "Automatic".to_string(),
Some(_) => "Manual".to_string(),
};
let config_text = Text::from(vec![Line::from(vec![
format!("Profile: {profile} | Benchmark: {kind} | Max VUs: {max_vus} | Duration: {duration} sec | Rates: {rates} | Warmup: {warmup} sec",
profile = self.benchmark_config.profile.clone().unwrap_or("N/A".to_string()),
kind = self.benchmark_config.benchmark_kind,
max_vus = self.benchmark_config.max_vus,
duration = self.benchmark_config.duration.as_secs_f64(),
rates = rate_mode,
warmup = self.benchmark_config.warmup_duration.as_secs_f64()).white().bold(),
])]);
Paragraph::new(config_text.clone()).render(main_layout[0], buf);
// STEPS
let steps_block_title = Line::from("Benchmark steps".bold()).centered();
let steps_block = Block::bordered()
.title(steps_block_title.alignment(Alignment::Center))
.border_set(border::THICK);
let step_rows = state
.benchmarks
.iter()
.map(|b| {
let error_rate = if b.failed_requests > 0 {
format!(
"{:4.0}%",
b.failed_requests as f64
/ (b.failed_requests + b.successful_requests) as f64
* 100.
)
.light_red()
.bold()
} else {
format!("{:4.0}%", 0).to_string().white()
};
let cells = vec![
b.id.clone().white(),
b.status.to_string().white(),
format!("{:4.0}%", b.progress).white(),
error_rate,
format!("{:>6.6} req/sec avg", b.throughput).green().bold(),
];
Row::new(cells)
})
.collect::<Vec<_>>();
let widths = [
Constraint::Length(30),
Constraint::Length(10),
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(20),
];
// steps table
Table::new(step_rows, widths)
.header(Row::new(vec![
Cell::new(Line::from("Bench").alignment(Alignment::Left)),
Cell::new(Line::from("Status").alignment(Alignment::Left)),
Cell::new(Line::from("%").alignment(Alignment::Left)),
Cell::new(Line::from("Err").alignment(Alignment::Left)),
Cell::new(Line::from("Throughput").alignment(Alignment::Left)),
]))
.block(steps_block)
.render(steps_graph_layout[0], buf);
// CHARTS
let graphs_block_title = Line::from("Token throughput rate".bold()).centered();
let graphs_block = Block::bordered()
.title(graphs_block_title.alignment(Alignment::Center))
.border_set(border::THICK);
let binding = data.get("token_throughput_rate").unwrap().clone();
let datasets = vec![Dataset::default()
.name("Token throughput rate".to_string())
.marker(symbols::Marker::Dot)
.graph_type(ratatui::widgets::GraphType::Scatter)
.style(ratatui::style::Style::default().fg(ratatui::style::Color::LightMagenta))
.data(&binding)];
let (xmax, ymax) = get_max_bounds(&binding, (10.0, 100.0));
let x_axis = ratatui::widgets::Axis::default()
.title("Arrival rate (req/s)".to_string())
.style(ratatui::style::Style::default().white())
.bounds([0.0, xmax])
.labels(get_axis_labels(0.0, xmax, 5));
let y_axis = ratatui::widgets::Axis::default()
.title("Throughput (tokens/s)".to_string())
.style(ratatui::style::Style::default().white())
.bounds([0.0, ymax])
.labels(get_axis_labels(0.0, ymax, 5));
ratatui::widgets::Chart::new(datasets)
.x_axis(x_axis)
.y_axis(y_axis)
.block(graphs_block)
.legend_position(None)
.render(steps_graph_layout[1], buf);
}