@@ -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
107163fn 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
184252fn compare_benchmark ( list_reports : & [ Vec < Report > ] , compare_path_option : Option < & str > , threshold_option : Option < & str > ) {
0 commit comments