fn render()

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);
    }