Skip to content

Commit 526b01f

Browse files
author
Anthony Naddeo
committed
Add additional statistics and histogram visualization
1 parent da4bd88 commit 526b01f

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,14 @@ FLAGS:
291291
-v, --verbose Toggle verbose output
292292

293293
OPTIONS:
294-
-b, --benchmark <benchmark> Sets the benchmark file
295-
-c, --compare <compare> Sets a compare file
296-
-r, --report <report> Sets a report file
297-
--skip-tags <skip-tags> Tags to exclude
298-
--tags <tags> Tags to include
299-
-t, --threshold <threshold> Sets a threshold value in ms amongst the compared file
300-
-o, --timeout <timeout> Set timeout in seconds for all requests
294+
-b, --benchmark <benchmark> Sets the benchmark file
295+
-c, --compare <compare> Sets a compare file
296+
-r, --report <report> Sets a report file
297+
--skip-tags <skip-tags> Tags to exclude
298+
--tags <tags> Tags to include
299+
-t, --threshold <threshold> Sets a threshold value in ms amongst the compared file
300+
-o, --timeout <timeout> Set timeout in seconds for all requests
301+
-w, --histogram-max-width <int> Set the max width of the request histogram
301302
```
302303
303304
## Roadmap

src/main.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ fn main() {
2222
let benchmark_file = matches.value_of("benchmark").unwrap();
2323
let report_path_option = matches.value_of("report");
2424
let stats_option = matches.is_present("stats");
25+
let histogram_max_width = matches.value_of("histogram-max-width").unwrap_or("100").parse::<usize>().unwrap();
2526
let compare_path_option = matches.value_of("compare");
2627
let threshold_option = matches.value_of("threshold");
2728
let no_check_certificate = matches.is_present("no-check-certificate");
@@ -54,7 +55,7 @@ fn main() {
5455
let list_reports = benchmark_result.reports;
5556
let duration = benchmark_result.duration;
5657

57-
show_stats(&list_reports, stats_option, nanosec, duration);
58+
show_stats(&list_reports, stats_option, nanosec, duration, histogram_max_width);
5859
compare_benchmark(&list_reports, compare_path_option, threshold_option);
5960

6061
process::exit(0)
@@ -77,6 +78,7 @@ fn app_args<'a>() -> clap::ArgMatches<'a> {
7778
.arg(Arg::with_name("list-tasks").long("list-tasks").help("List benchmark tasks (executes --tags/--skip-tags filter)").takes_value(false))
7879
.arg(Arg::with_name("quiet").short("q").long("quiet").help("Disables output").takes_value(false))
7980
.arg(Arg::with_name("timeout").short("o").long("timeout").help("Set timeout in seconds for all requests").takes_value(true))
81+
.arg(Arg::with_name("histogram-max-width").short("w").long("histogram-max-width").help("Set the maximum width of the histogram").takes_value(true))
8082
.arg(Arg::with_name("nanosec").short("n").long("nanosec").help("Shows statistics in nanoseconds").takes_value(false))
8183
.arg(Arg::with_name("verbose").short("v").long("verbose").help("Toggle verbose output").takes_value(false))
8284
.get_matches()
@@ -96,12 +98,66 @@ impl DrillStats {
9698
fn median_duration(&self) -> f64 {
9799
self.hist.value_at_quantile(0.5) as f64 / 1_000.0
98100
}
101+
fn max_duration(&self) -> f64 {
102+
self.hist.max() as f64 / 1_000.0
103+
}
104+
fn min_duration(&self) -> f64 {
105+
self.hist.min() as f64 / 1_000.0
106+
}
99107
fn stdev_duration(&self) -> f64 {
100108
self.hist.stdev() / 1_000.0
101109
}
102110
fn value_at_quantile(&self, quantile: f64) -> f64 {
103111
self.hist.value_at_quantile(quantile) as f64 / 1_000.0
104112
}
113+
fn print_histogram(&self, max_symbols: usize) {
114+
let max_value = self.hist.max();
115+
let min_value = self.hist.min();
116+
let bin_size = if max_value == min_value {
117+
1
118+
} else {
119+
(max_value - min_value) / 10
120+
};
121+
let max_range_string_length = format!("[{} - {}]", max_value / 1_000, (max_value + bin_size) / 1_000).len();
122+
123+
// Collect counts for each bin
124+
let mut counts = vec![];
125+
for i in 0..10 {
126+
let lower_bound = min_value + i * bin_size;
127+
let upper_bound = std::cmp::min(lower_bound + bin_size, max_value + 1); // Ensure last bin includes max_value
128+
let count = self.hist.iter_recorded().filter(|v| v.value_iterated_to() >= lower_bound && v.value_iterated_to() < upper_bound).count();
129+
counts.push(count);
130+
}
131+
132+
// Normalize counts
133+
let max_count = *counts.iter().max().unwrap_or(&1);
134+
let factor = if max_count > max_symbols {
135+
max_count as f64 / max_symbols as f64
136+
} else {
137+
1.0
138+
};
139+
140+
for (i, &count) in counts.iter().enumerate() {
141+
let lower_bound = min_value + (i as u64) * bin_size;
142+
143+
// If this is the last bin then the upper bound is max_value
144+
let upper_bound = if i == 9 {
145+
max_value + 1000
146+
} else {
147+
lower_bound + bin_size
148+
};
149+
150+
let normalized_count = if factor > 1.0 {
151+
(count as f64 / factor).round() as usize
152+
} else {
153+
count
154+
};
155+
156+
let range_string = format!("[{} - {}]", lower_bound / 1_000, upper_bound / 1_000);
157+
let range_string_padded = format!("{:width$}", range_string, width = max_range_string_length).yellow();
158+
println!("{}: {}", range_string_padded, "█".repeat(normalized_count).purple());
159+
}
160+
}
105161
}
106162

107163
fn compute_stats(sub_reports: &[Report]) -> DrillStats {
@@ -136,7 +192,7 @@ fn format_time(tdiff: f64, nanosec: bool) -> String {
136192
}
137193
}
138194

139-
fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, duration: f64) {
195+
fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, duration: f64, histogram_max_width: usize) {
140196
if !stats_option {
141197
return;
142198
}
@@ -157,6 +213,10 @@ fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, d
157213
println!("{:width$} {:width2$} {}", name.green(), "Median time per request".yellow(), format_time(substats.median_duration(), nanosec).purple(), width = 25, width2 = 25);
158214
println!("{:width$} {:width2$} {}", name.green(), "Average time per request".yellow(), format_time(substats.mean_duration(), nanosec).purple(), width = 25, width2 = 25);
159215
println!("{:width$} {:width2$} {}", name.green(), "Sample standard deviation".yellow(), format_time(substats.stdev_duration(), nanosec).purple(), width = 25, width2 = 25);
216+
println!("{:width$} {:width2$} {}", name.green(), "Min time per request".yellow(), format_time(substats.min_duration(), nanosec).purple(), width = 25, width2 = 25);
217+
println!("{:width$} {:width2$} {}", name.green(), "Max time per request".yellow(), format_time(substats.max_duration(), nanosec).purple(), width = 25, width2 = 25);
218+
println!("{:width$} {:width2$} {}", name.green(), "50.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.5), nanosec).purple(), width = 25, width2 = 25);
219+
println!("{:width$} {:width2$} {}", name.green(), "95.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.95), nanosec).purple(), width = 25, width2 = 25);
160220
println!("{:width$} {:width2$} {}", name.green(), "99.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.99), nanosec).purple(), width = 25, width2 = 25);
161221
println!("{:width$} {:width2$} {}", name.green(), "99.5'th percentile".yellow(), format_time(substats.value_at_quantile(0.995), nanosec).purple(), width = 25, width2 = 25);
162222
println!("{:width$} {:width2$} {}", name.green(), "99.9'th percentile".yellow(), format_time(substats.value_at_quantile(0.999), nanosec).purple(), width = 25, width2 = 25);
@@ -176,9 +236,17 @@ fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, d
176236
println!("{:width2$} {}", "Median time per request".yellow(), format_time(global_stats.median_duration(), nanosec).purple(), width2 = 25);
177237
println!("{:width2$} {}", "Average time per request".yellow(), format_time(global_stats.mean_duration(), nanosec).purple(), width2 = 25);
178238
println!("{:width2$} {}", "Sample standard deviation".yellow(), format_time(global_stats.stdev_duration(), nanosec).purple(), width2 = 25);
239+
println!("{:width2$} {}", "Min time per request".yellow(), format_time(global_stats.min_duration(), nanosec).purple(), width2 = 25);
240+
println!("{:width2$} {}", "Max time per request".yellow(), format_time(global_stats.max_duration(), nanosec).purple(), width2 = 25);
241+
println!("{:width2$} {}", "50.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.5), nanosec).purple(), width2 = 25);
242+
println!("{:width2$} {}", "95.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.95), nanosec).purple(), width2 = 25);
179243
println!("{:width2$} {}", "99.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.99), nanosec).purple(), width2 = 25);
180244
println!("{:width2$} {}", "99.5'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.995), nanosec).purple(), width2 = 25);
181245
println!("{:width2$} {}", "99.9'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.999), nanosec).purple(), width2 = 25);
246+
println!();
247+
println!("{}", "Request Histogram".yellow());
248+
println!();
249+
global_stats.print_histogram(histogram_max_width);
182250
}
183251

184252
fn compare_benchmark(list_reports: &[Vec<Report>], compare_path_option: Option<&str>, threshold_option: Option<&str>) {

0 commit comments

Comments
 (0)