use super::{
console_ui::{
clear_lines_from, color_txt, get_cursor_position, queue_msg, ColoredText, CursorPos,
TextColor, ToColorize,
},
file::write,
histogram::draw_histogram,
runner::{
BenchmarkResult, FuncThreadMessage, ThreadLifecycleMessage, ThreadLifecycleMsgType,
ThreadMessageType, NB_TESTS,
},
table::print_table,
time_utils::seconds_to_hr,
};
use std::{
mem::ManuallyDrop,
process::exit,
sync::mpsc::Receiver,
time::{Duration, Instant},
};
pub fn handle_messages(
receiver: Receiver<FuncThreadMessage>,
func_names: Vec<String>,
start: Instant,
nb_buckets_arround: u128,
file_path: Option<String>,
nb_iterations: u16,
) {
let mut started: Vec<u8> = vec![0; func_names.len()];
let mut stopped: Vec<u8> = vec![0; func_names.len()];
let mut results: Vec<Option<BenchmarkResult>> = vec![None; func_names.len()];
let mut is_first_lifecycle = false;
let base_cursor_pos = get_cursor_position();
let mut after_table_cursor_pos: Option<CursorPos> = None;
for received in receiver {
let FuncThreadMessage {
func,
msg_type,
msg,
} = received;
unsafe {
match msg_type {
ThreadMessageType::Lifecycle => {
let mut _parsed_msg: ManuallyDrop<ThreadLifecycleMessage> = msg.lifecycle_msg;
let parsed_msg = ManuallyDrop::into_inner(_parsed_msg);
let func_index = func_names.iter().position(|r| *r == func).unwrap();
if !is_first_lifecycle {
is_first_lifecycle = true;
} else {
clear_lines_from(base_cursor_pos);
}
handle_progress(
parsed_msg,
func,
&mut started,
&mut stopped,
&func_names,
func_index,
);
if after_table_cursor_pos.is_none() {
after_table_cursor_pos = Some(get_cursor_position());
}
handle_clock(start, after_table_cursor_pos.unwrap());
}
ThreadMessageType::Result => {
let mut _parsed_msg: ManuallyDrop<BenchmarkResult> = msg.result_msg;
let parsed_msg = ManuallyDrop::into_inner(_parsed_msg);
let func_index = func_names.iter().position(|r| *r == func).unwrap();
results[func_index] = Some(parsed_msg);
let all_done = results.iter().all(|e| e.is_some());
if all_done {
clear_lines_from(base_cursor_pos);
print_table_results(
results.clone(),
&func_names,
file_path.clone(),
nb_iterations,
);
print_histograms_results(
results.clone(),
&func_names,
nb_buckets_arround,
file_path,
);
exit(0);
}
}
ThreadMessageType::Tick => {
if let Some(pos) = after_table_cursor_pos {
handle_clock(start, pos);
}
}
}
}
}
}
fn handle_progress(
message: ThreadLifecycleMessage,
func: String,
started: &mut [u8],
stopped: &mut [u8],
func_names: &[String],
func_index: usize,
) {
let ThreadLifecycleMessage {
msg_type: lifecycle_type,
..
} = message;
match lifecycle_type {
ThreadLifecycleMsgType::Start => started[func_index] += 1,
ThreadLifecycleMsgType::Stop => stopped[func_index] += 1,
}
let mut data: Vec<Vec<ColoredText>> = vec![];
for (i, f_name) in func_names.iter().enumerate() {
let nb_started = started[i];
let nb_stopped = stopped[i];
let is_current_func = *f_name == func;
let is_start = lifecycle_type == ThreadLifecycleMsgType::Start;
let is_curr_func_done = nb_stopped == NB_TESTS;
let f_text = match is_curr_func_done {
true => color_txt(ToColorize::Str(f_name.to_string()), TextColor::Green),
false => color_txt(ToColorize::Str(f_name.to_string()), TextColor::Normal),
};
let started_txt = match is_start & is_current_func & !is_curr_func_done {
true => color_txt(ToColorize::U8(nb_started), TextColor::Yellow),
false => color_txt(ToColorize::U8(nb_started), TextColor::Normal),
};
let ended_txt = match !is_start & is_current_func & !is_curr_func_done {
true => color_txt(ToColorize::U8(nb_stopped), TextColor::Yellow),
false => color_txt(ToColorize::U8(nb_stopped), TextColor::Normal),
};
data.push(vec![f_text, started_txt, ended_txt]);
}
print_table(
"Benchmarking ...".to_string(),
vec![
"Function".to_string(),
"Started".to_string(),
"Done".to_string(),
],
data,
None,
);
}
fn print_table_results(
results: Vec<Option<BenchmarkResult>>,
func_names: &[String],
file_path: Option<String>,
nb_iterations: u16,
) {
let mut data: Vec<Vec<ColoredText>> = vec![];
for (i, result) in results.iter().enumerate() {
match result {
None => panic!("Results not found"),
Some(val) => {
let BenchmarkResult {
slowest,
average,
fastest,
std_dev,
..
} = val;
data.push(vec![
color_txt(ToColorize::Str(func_names[i].to_string()), TextColor::Cyan),
color_txt(ToColorize::Dur(*average), TextColor::Yellow),
color_txt(ToColorize::Dur(*slowest), TextColor::Normal),
color_txt(ToColorize::Dur(*fastest), TextColor::Normal),
color_txt(ToColorize::Dur(*std_dev), TextColor::Normal),
]);
}
}
}
print_table(
format!("Benchmark with {nb_iterations} iterations"),
vec![
color_txt(ToColorize::Str("Function".to_string()), TextColor::Cyan).to_string(),
color_txt(ToColorize::Str("Average".to_string()), TextColor::Yellow).to_string(),
"Slowest".to_string(),
"Fastest".to_string(),
"Std dev".to_string(),
],
data,
file_path,
);
}
fn print_histograms_results(
results: Vec<Option<BenchmarkResult>>,
func_names: &[String],
nb_buckets_arround: u128,
file_path: Option<String>,
) {
for (i, result) in results.iter().enumerate() {
match result {
None => panic!("Results not found"),
Some(res) => {
let f_name = &func_names[i];
let txt =
color_txt(ToColorize::Str(f_name.to_string()), TextColor::Cyan).to_string();
queue_msg(txt.clone());
if let Some(ref path) = file_path {
write(path.to_string(), vec![txt, "".to_string()]);
}
println!();
draw_histogram(res, nb_buckets_arround, file_path.clone());
if i != results.len() - 1 {
println!();
if let Some(ref path) = file_path {
write(path.to_string(), vec!["".to_string()]);
}
}
}
}
}
}
fn handle_clock(start: Instant, after_table_cursor_pos: CursorPos) {
let time = start.elapsed();
let wait_a_bit = Duration::from_secs(5);
if time > wait_a_bit {
clear_lines_from(after_table_cursor_pos);
let formatted = seconds_to_hr(time);
queue_msg(format!(
"Running ... {}",
color_txt(ToColorize::Str(formatted), TextColor::Yellow)
));
}
}