1use std::collections::HashMap;
2use std::io::Write;
3
4use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
5
6use crate::SubBenchResult;
7
8#[derive(Default, Clone)]
9struct Delta {
10 throughput_change_str: String,
11 throughput_change: f64,
12 time_change_str: String,
13 time_change: f64,
14}
15
16impl Delta {
17 fn new(previous: &SubBenchResult, current: &SubBenchResult) -> Self {
18 let prev_throughput = previous.throughput_per_second();
19 let curr_throughput = current.throughput_per_second();
20 let delta_throughput = if prev_throughput != 0.0 {
21 (curr_throughput - prev_throughput) / prev_throughput * 100.0
22 } else {
23 0.0
24 };
25 let throughput_change = format!(" ({delta_throughput:+.2}%)");
26
27 let prev_time = previous.avg_duration_per_iteration;
28 let curr_time = current.avg_duration_per_iteration;
29 let delta_time = if prev_time.as_nanos() != 0 {
30 (curr_time.as_secs_f64() - prev_time.as_secs_f64()) / prev_time.as_secs_f64() * 100.0
31 } else {
32 0.0
33 };
34
35 let time_change = format!("{delta_time:+.2}%; ");
36
37 Delta {
38 throughput_change_str: throughput_change,
39 throughput_change: delta_throughput,
40 time_change_str: time_change,
41 time_change: delta_time,
42 }
43 }
44}
45
46fn get_change_color(percent_change: f64) -> ColorSpec {
52 let mut color_spec = ColorSpec::new();
53 if percent_change > 3.0 {
54 color_spec.set_fg(Some(Color::Red));
55 } else if percent_change < -3.0 {
56 color_spec.set_fg(Some(Color::Green));
57 } else {
58 color_spec.set_fg(Some(Color::Yellow));
59 }
60 if percent_change.abs() > 15.0 {
61 color_spec.set_intense(true);
62 }
63 color_spec
64}
65
66pub fn print_results(
67 stdout: &mut StandardStream,
68 results: &[SubBenchResult],
69 previous_results: Option<&[SubBenchResult]>,
70) {
71 let mut deltas = HashMap::new();
72 if let Some(previous_results) = previous_results {
73 for result in results {
74 if let Some(previous_result) = previous_results.iter().find(|r| r.name == result.name) {
75 deltas.insert(result.name.clone(), Delta::new(previous_result, result));
76 }
77 }
78 }
79
80 let longest_throughput_change_len = deltas
81 .values()
82 .map(|d| d.throughput_change_str.len())
83 .max()
84 .unwrap_or(0);
85 let longest_time_change_len = deltas
86 .values()
87 .map(|d| d.time_change_str.len())
88 .max()
89 .unwrap_or(0);
90
91 let longest_name_len = results.iter().map(|r| r.name.len()).max().unwrap_or(0);
92 let duration_strings: Vec<String> = results
93 .iter()
94 .map(|r| format!("{:.3?}", r.avg_duration_per_iteration))
95 .collect();
96 let longest_duration_len = duration_strings.iter().map(|s| s.len()).max().unwrap_or(0);
97
98 let iterations_strings: Vec<String> = results
99 .iter()
100 .map(|r| format!("{}", r.iterations))
101 .collect();
102 let longest_iterations_len = iterations_strings
103 .iter()
104 .map(|s| s.len())
105 .max()
106 .unwrap_or(0);
107
108 let throughput_strings: Vec<String> = results
109 .iter()
110 .map(|r| {
111 let throughput_per_second = r.throughput_count_per_iteration as f64
112 / r.avg_duration_per_iteration.as_secs_f64();
113 human_scale(throughput_per_second)
114 })
115 .collect();
116 let longest_throughput_len = throughput_strings
117 .iter()
118 .map(|s| s.len())
119 .max()
120 .unwrap_or(0);
121
122 let longest_throughput_unit_len = results
123 .iter()
124 .map(|r| r.throughput_unit.len())
125 .max()
126 .unwrap_or(0);
127
128 for (i, result) in results.iter().enumerate() {
129 let delta = deltas.get(&result.name).cloned().unwrap_or_default();
130 let time_color = get_change_color(delta.time_change);
131 let throughput_color = get_change_color(-delta.throughput_change);
132
133 stdout
134 .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))
135 .unwrap();
136 write!(stdout, " {:>longest_name_len$}: ", result.name).unwrap();
137
138 stdout.set_color(&time_color).unwrap();
139 write!(stdout, "{:>longest_duration_len$} ", duration_strings[i],).unwrap();
140 stdout.reset().unwrap();
141 write!(stdout, "(").unwrap();
142 stdout.set_color(&time_color).unwrap();
143 write!(
144 stdout,
145 "{:>longest_time_change_len$}",
146 delta.time_change_str
147 )
148 .unwrap();
149 stdout.reset().unwrap();
150
151 write!(
152 stdout,
153 "over {:>longest_iterations_len$} iter) ",
154 result.iterations,
155 )
156 .unwrap();
157
158 stdout.set_color(&throughput_color).unwrap();
159 write!(stdout, "{:>longest_throughput_len$}", throughput_strings[i]).unwrap();
160 stdout.reset().unwrap();
161 write!(
162 stdout,
163 " {:>longest_throughput_unit_len$}/s",
164 result.throughput_unit,
165 )
166 .unwrap();
167 stdout.set_color(&throughput_color).unwrap();
168 writeln!(
169 stdout,
170 "{:>longest_throughput_change_len$}",
171 delta.throughput_change_str
172 )
173 .unwrap();
174 }
175 println!();
176}
177
178fn human_scale(value: f64) -> String {
179 const PREFIXES: &[&str] = &["", "K", "M", "G", "T", "P"];
180
181 if value == 0.0 {
182 return "0".to_string();
183 }
184
185 let abs_value = value.abs();
186 let exponent = (abs_value.log10() / 3.0).floor() as usize;
187 let prefix_index = exponent.min(PREFIXES.len() - 1);
188
189 let scaled = value / 10_f64.powi((prefix_index * 3) as i32);
190
191 let decimal_places = if scaled.abs() >= 100.0 {
193 0
194 } else if scaled.abs() >= 10.0 {
195 1
196 } else {
197 2
198 };
199
200 format!(
201 "{:.prec$}{}",
202 scaled,
203 PREFIXES[prefix_index],
204 prec = decimal_places
205 )
206}