Skip to content

Commit

Permalink
Add additional statistics and histogram visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
naddeoa committed Feb 26, 2024
1 parent da4bd88 commit a68d6d0
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 9 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,14 @@ FLAGS:
-v, --verbose Toggle verbose output

OPTIONS:
-b, --benchmark <benchmark> Sets the benchmark file
-c, --compare <compare> Sets a compare file
-r, --report <report> Sets a report file
--skip-tags <skip-tags> Tags to exclude
--tags <tags> Tags to include
-t, --threshold <threshold> Sets a threshold value in ms amongst the compared file
-o, --timeout <timeout> Set timeout in seconds for all requests
-b, --benchmark <benchmark> Sets the benchmark file
-c, --compare <compare> Sets a compare file
-r, --report <report> Sets a report file
--skip-tags <skip-tags> Tags to exclude
--tags <tags> Tags to include
-t, --threshold <threshold> Sets a threshold value in ms amongst the compared file
-o, --timeout <timeout> Set timeout in seconds for all requests
-w, --histogram-max-width <int> Set the max width of the request histogram
```
## Roadmap
Expand Down
65 changes: 63 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn main() {
let benchmark_file = matches.value_of("benchmark").unwrap();
let report_path_option = matches.value_of("report");
let stats_option = matches.is_present("stats");
let histogram_max_width = matches.value_of("histogram-max-width").unwrap_or("100").parse::<usize>().unwrap();
let compare_path_option = matches.value_of("compare");
let threshold_option = matches.value_of("threshold");
let no_check_certificate = matches.is_present("no-check-certificate");
Expand Down Expand Up @@ -54,7 +55,7 @@ fn main() {
let list_reports = benchmark_result.reports;
let duration = benchmark_result.duration;

show_stats(&list_reports, stats_option, nanosec, duration);
show_stats(&list_reports, stats_option, nanosec, duration, histogram_max_width);
compare_benchmark(&list_reports, compare_path_option, threshold_option);

process::exit(0)
Expand All @@ -77,6 +78,7 @@ fn app_args<'a>() -> clap::ArgMatches<'a> {
.arg(Arg::with_name("list-tasks").long("list-tasks").help("List benchmark tasks (executes --tags/--skip-tags filter)").takes_value(false))
.arg(Arg::with_name("quiet").short("q").long("quiet").help("Disables output").takes_value(false))
.arg(Arg::with_name("timeout").short("o").long("timeout").help("Set timeout in seconds for all requests").takes_value(true))
.arg(Arg::with_name("histogram-max-width").short("w").long("histogram-max-width").help("Set the maximum width of the histogram").takes_value(true))
.arg(Arg::with_name("nanosec").short("n").long("nanosec").help("Shows statistics in nanoseconds").takes_value(false))
.arg(Arg::with_name("verbose").short("v").long("verbose").help("Toggle verbose output").takes_value(false))
.get_matches()
Expand All @@ -96,12 +98,59 @@ impl DrillStats {
fn median_duration(&self) -> f64 {
self.hist.value_at_quantile(0.5) as f64 / 1_000.0
}
fn max_duration(&self) -> f64 {
self.hist.max() as f64 / 1_000.0
}
fn min_duration(&self) -> f64 {
self.hist.min() as f64 / 1_000.0
}
fn stdev_duration(&self) -> f64 {
self.hist.stdev() / 1_000.0
}
fn value_at_quantile(&self, quantile: f64) -> f64 {
self.hist.value_at_quantile(quantile) as f64 / 1_000.0
}
fn print_histogram(&self, max_symbols: usize) {
let max_value = self.hist.max();
let min_value = self.hist.min();
let bin_size = if max_value == min_value {
1
} else {
(max_value - min_value) / 10
};
let max_range_string_length = format!("[{} - {}]", max_value / 1_000, (max_value + bin_size) / 1_000).len();

// Collect counts for each bin
let mut counts = vec![];
for i in 0..10 {
let lower_bound = min_value + i * bin_size;
let upper_bound = std::cmp::min(lower_bound + bin_size, max_value + 1); // Ensure last bin includes max_value
let count = self.hist.iter_recorded().filter(|v| v.value_iterated_to() >= lower_bound && v.value_iterated_to() < upper_bound).count();
counts.push(count);
}

// Normalize counts
let max_count = *counts.iter().max().unwrap_or(&1);
let factor = if max_count > max_symbols {
max_count as f64 / max_symbols as f64
} else {
1.0
};

for (i, &count) in counts.iter().enumerate() {
let lower_bound = min_value + (i as u64) * bin_size;
let upper_bound = std::cmp::min(lower_bound + bin_size, max_value + 1);
let normalized_count = if factor > 1.0 {
(count as f64 / factor).round() as usize
} else {
count
};

let range_string = format!("[{} - {}]", lower_bound / 1_000, upper_bound / 1_000);
let range_string_padded = format!("{:width$}", range_string, width = max_range_string_length).yellow();
println!("{}: {}", range_string_padded, "█".repeat(normalized_count).purple());
}
}
}

fn compute_stats(sub_reports: &[Report]) -> DrillStats {
Expand Down Expand Up @@ -136,7 +185,7 @@ fn format_time(tdiff: f64, nanosec: bool) -> String {
}
}

fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, duration: f64) {
fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, duration: f64, histogram_max_width: usize) {
if !stats_option {
return;
}
Expand All @@ -157,6 +206,10 @@ fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, d
println!("{:width$} {:width2$} {}", name.green(), "Median time per request".yellow(), format_time(substats.median_duration(), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "Average time per request".yellow(), format_time(substats.mean_duration(), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "Sample standard deviation".yellow(), format_time(substats.stdev_duration(), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "Min time per request".yellow(), format_time(substats.min_duration(), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "Max time per request".yellow(), format_time(substats.max_duration(), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "50.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.5), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "95.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.95), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "99.0'th percentile".yellow(), format_time(substats.value_at_quantile(0.99), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "99.5'th percentile".yellow(), format_time(substats.value_at_quantile(0.995), nanosec).purple(), width = 25, width2 = 25);
println!("{:width$} {:width2$} {}", name.green(), "99.9'th percentile".yellow(), format_time(substats.value_at_quantile(0.999), nanosec).purple(), width = 25, width2 = 25);
Expand All @@ -176,9 +229,17 @@ fn show_stats(list_reports: &[Vec<Report>], stats_option: bool, nanosec: bool, d
println!("{:width2$} {}", "Median time per request".yellow(), format_time(global_stats.median_duration(), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "Average time per request".yellow(), format_time(global_stats.mean_duration(), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "Sample standard deviation".yellow(), format_time(global_stats.stdev_duration(), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "Min time per request".yellow(), format_time(global_stats.min_duration(), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "Max time per request".yellow(), format_time(global_stats.max_duration(), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "50.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.5), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "95.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.95), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "99.0'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.99), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "99.5'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.995), nanosec).purple(), width2 = 25);
println!("{:width2$} {}", "99.9'th percentile".yellow(), format_time(global_stats.value_at_quantile(0.999), nanosec).purple(), width2 = 25);
println!();
println!("{}", "Request Histogram".yellow());
println!();
global_stats.print_histogram(histogram_max_width);
}

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

0 comments on commit a68d6d0

Please sign in to comment.