use std::any::Any;
use yansi::Paint;
use super::{REPORTER_PLUGIN_NAME, avg_median_str, memory_str, min_max_str};
use crate::{
bench::BenchResult,
plugins::{EventListener, PluginEvents},
report::{PrintOnce, check_and_print},
};
#[derive(Clone)]
pub struct PlainReporter {
print_runner_name_once: Option<PrintOnce>,
print_num_iter: bool,
}
impl EventListener for PlainReporter {
fn as_any(&mut self) -> &mut dyn Any {
self
}
fn name(&self) -> &'static str {
REPORTER_PLUGIN_NAME
}
fn on_event(&mut self, event: PluginEvents) {
match event {
PluginEvents::BenchStart { bench_id: _ } => {}
PluginEvents::GroupStart {
runner_name,
group_name: Some(group_name),
output_value_column_title: _,
} => {
if let Some(runner_name) = runner_name {
check_and_print(&mut self.print_runner_name_once, runner_name);
}
println!("{}", group_name.black().on_yellow().invert().bold());
}
PluginEvents::GroupBenchNumIters { num_iter } if self.print_num_iter => {
println!("Num Iter Benches in Group {}", num_iter.bold());
}
PluginEvents::GroupNumIters { num_iter } if self.print_num_iter => {
println!("Num Iter Group {}", num_iter.bold());
}
PluginEvents::GroupStop {
runner_name: _,
group_name: _,
results,
output_value_column_title,
} => {
let mut table_data: Vec<Vec<String>> = Vec::new();
for result in results {
let perf_counter = &result.perf_counter;
let mut stats_columns = self.to_columns(result, output_value_column_title);
stats_columns.insert(0, result.bench_id.bench_name.to_string());
table_data.push(stats_columns);
if let Some(perf_counter) = perf_counter.as_ref() {
let mut columns = perf_counter.to_columns(result.old_perf_counter.as_ref());
columns.insert(0, "".to_string());
table_data.push(columns);
}
}
self.print_table(&table_data);
}
_ => {}
}
}
}
impl PlainReporter {
pub fn new() -> Self {
Self {
print_runner_name_once: None,
print_num_iter: false,
}
}
pub fn print_num_iter(mut self, print: bool) -> Self {
self.print_num_iter = print;
self
}
pub(crate) fn to_columns(
&self,
result: &BenchResult,
output_value_column_title: &'static str,
) -> Vec<String> {
let stats = result.stats;
let other = result.old_stats;
let input_size_in_bytes = result.input_size_in_bytes;
let (avg_str, median_str) = avg_median_str(&stats, input_size_in_bytes, other);
let avg_str = format!("Avg: {}", avg_str);
let median_str = format!("Median: {}", median_str);
let min_max = min_max_str(&stats, input_size_in_bytes);
let memory_string = memory_str(&stats, other, result.tracked_memory);
if let Some(output_value) = &result.output_value {
vec![
memory_string,
avg_str,
median_str,
min_max,
format!(
"{}: {}{}",
output_value_column_title,
output_value,
result
.output_value_delta
.as_deref()
.map(|delta| format!(" {delta}"))
.unwrap_or_default()
),
]
} else {
vec![memory_string, avg_str, median_str, min_max]
}
}
fn print_table(&self, table_data: &Vec<Vec<String>>) {
if table_data.is_empty() {
return;
}
let num_cols = table_data.iter().map(|row| row.len()).max().unwrap_or(0);
let mut column_width = vec![0; num_cols];
for row in table_data {
for (i, cell) in row.iter().enumerate() {
let cell = cell.resetting().to_string();
column_width[i] = column_width[i].max(cell.count_characters() + 4);
}
}
for row in table_data {
for (i, cell) in row.iter().enumerate() {
let padding = column_width[i] - cell.resetting().to_string().count_characters();
print!("{}{}", cell, " ".repeat(padding),);
}
println!(); }
}
}
impl Default for PlainReporter {
fn default() -> Self {
Self::new()
}
}
fn count_characters(input: &str) -> usize {
let mut count = 0;
let mut chars = input.chars().peekable();
while let Some(&ch) = chars.peek() {
if ch == '\x1B' {
chars.next();
chars.next();
while let Some(&ch) = chars.peek() {
chars.next();
if ch.is_ascii_alphabetic() {
break;
}
}
} else {
count += 1;
chars.next();
}
}
count
}
trait LenWithoutControl {
fn count_characters(&self) -> usize;
}
impl LenWithoutControl for str {
fn count_characters(&self) -> usize {
count_characters(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_test() {
assert_eq!(
"Memory: \u{1b}[1;96m786.5 KB\u{1b}[0m".count_characters(),
"Memory: 786.5 KB".count_characters()
);
}
#[test]
fn test_print_table() {
let data = vec![
vec![
"TurboBuckets",
"Memory: \u{1b}[1;96m786.5 KB\u{1b}[0m",
"Avg: 3.4791ms \u{1b}[31m (+18.96%)\u{1b}[0m",
"Median: 3.5334ms \u{1b}[31m (+24.46%)\u{1b}[0m",
"2.0247ms",
"5.0919ms",
]
.into_iter()
.map(|s| s.to_string())
.collect(),
vec![
"",
"\u{1b}[31mL1dA: 6926924.148 (0.87%)\u{1b}[0m\u{1b}[0m",
"\u{1b}[32mL1dM: 75340.273 (0.16%)\u{1b}[0m\u{1b}[0m",
"\u{1b}[34mBr: 2004614.883 (0.00%)\u{1b}[0m\u{1b}[0m",
"\u{1b}[31mBrM: 13.812 \u{1b}[31m (+113.53%)\u{1b}[0m\u{1b}[0m",
]
.into_iter()
.map(|s| s.to_string())
.collect(),
];
let reporter = PlainReporter::new();
reporter.print_table(&data);
}
}