From a6a68297fbe30775e4ed321888614a434ef50b95 Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 8 Apr 2024 18:07:48 +0900 Subject: [PATCH 1/8] wip --- src/monitor.rs | 11 +-- src/printer.rs | 174 +++++++++++++++------------------------------ src/result_data.rs | 117 ++++++++++++++++++++++++++++-- 3 files changed, 174 insertions(+), 128 deletions(-) diff --git a/src/monitor.rs b/src/monitor.rs index dc3e6a9a..4772c3ba 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -134,7 +134,7 @@ impl Monitor { let mut bar_num_req = vec![0u64; count]; let short_bin = (now - self.start).as_secs_f64() % bin; - for r in all.success.iter().rev() { + for r in all.success().iter().rev() { let past = (now - r.end).as_secs_f64(); let i = if past <= short_bin { 0 @@ -185,7 +185,7 @@ impl Monitor { [ Constraint::Length(3), Constraint::Length(8), - Constraint::Length(all.error.len() as u16 + 2), + Constraint::Length(all.error_distribution().len() as u16 + 2), Constraint::Fill(1), ] .as_ref(), @@ -220,7 +220,7 @@ impl Monitor { f.render_widget(gauge, row4[0]); let last_1_timescale = all - .success + .success() .iter() .rev() .take_while(|r| (now - r.end).as_secs_f64() <= timescale.as_secs_f64()) @@ -312,7 +312,8 @@ impl Monitor { ); f.render_widget(stats2, mid[1]); - let mut error_v: Vec<(String, usize)> = all.error.clone().into_iter().collect(); + let mut error_v: Vec<(String, usize)> = + all.error_distribution().clone().into_iter().collect(); error_v.sort_by_key(|t| std::cmp::Reverse(t.1)); let errors_text = error_v .into_iter() @@ -366,7 +367,7 @@ impl Monitor { } .max(2); let values = all - .success + .success() .iter() .rev() .take_while(|r| (now - r.end).as_secs_f64() < timescale.as_secs_f64()) diff --git a/src/printer.rs b/src/printer.rs index 07d2b858..de01e481 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -205,18 +205,21 @@ fn print_json( } let summary = Summary { - success_rate: calculate_success_rate(res), + success_rate: res.success_rate(), total: total_duration.as_secs_f64(), - slowest: calculate_slowest_request(res), - fastest: calculate_fastest_request(res), - average: calculate_average_request(res), + slowest: res.slowest_latency(), + fastest: res.fastest_latency(), + average: res.average_latency(), requests_per_sec: calculate_requests_per_sec(res, total_duration), - total_data: calculate_total_data(res), - size_per_request: calculate_size_per_request(res), - size_per_sec: (calculate_size_per_sec(res, total_duration)), + total_data: res.total_data() as u64, + size_per_request: res.size_per_request(), + size_per_sec: res.total_data() as f64 / total_duration.as_secs_f64(), }; - let mut durations = get_durations_all(res); + let mut durations = res + .duration_all() + .map(|d| d.as_secs_f64()) + .collect::>(); let response_time_histogram = histogram(&durations, 11) .into_iter() @@ -231,7 +234,10 @@ fn print_json( let mut latency_percentiles_not_successful: Option> = None; if stats_success_breakdown { - let mut durations_successful = get_durations_successful(res); + let mut durations_successful = res + .duration_successful() + .map(|d| d.as_secs_f64()) + .collect::>(); response_time_histogram_successful = Some( histogram(&durations_successful, 11) @@ -242,7 +248,10 @@ fn print_json( latency_percentiles_successful = Some(percentiles(&mut durations_successful)); - let mut durations_not_successful = get_durations_not_successful(res); + let mut durations_not_successful = res + .duration_not_successful() + .map(|d| d.as_secs_f64()) + .collect::>(); response_time_histogram_not_successful = Some( histogram(&durations_not_successful, 11) @@ -255,9 +264,8 @@ fn print_json( } let mut ends = res - .success - .iter() - .map(|r| (r.end - start).as_secs_f64()) + .end_times_from_start() + .map(|d| d.as_secs_f64()) .collect::>(); ends.push(0.0); float_ord::sort(&mut ends); @@ -297,14 +305,10 @@ fn print_json( percentiles: rps_percentiles, }; - let mut status_code_distribution: BTreeMap = Default::default(); - - for s in res.success.iter().map(|r| r.status) { - *status_code_distribution.entry(s).or_default() += 1; - } + let mut status_code_distribution = res.status_code_distribution(); let connection_times: Vec<(std::time::Instant, ConnectionTime)> = - calculate_connection_times_base(res); + res.connection_times_base().collect(); let details = Details { dns_dialup: Triple { average: calculate_connection_times_dns_dialup_average(&connection_times), @@ -334,7 +338,7 @@ fn print_json( .into_iter() .map(|(k, v)| (k.as_u16().to_string(), v)) .collect(), - error_distribution: res.error.clone(), + error_distribution: res.error_distribution().clone(), }, ) } @@ -351,7 +355,7 @@ fn print_summary( color_enabled: !disable_color, }; writeln!(w, "{}", style.heading("Summary:"))?; - let success_rate = 100.0 * calculate_success_rate(res); + let success_rate = 100.0 * res.success_rate(); writeln!( w, "{}", @@ -364,26 +368,17 @@ fn print_summary( writeln!( w, "{}", - style.slowest(&format!( - " Slowest:\t{:.4} secs", - calculate_slowest_request(res) - )) + style.slowest(&format!(" Slowest:\t{:.4} secs", res.slowest_latency())) )?; writeln!( w, "{}", - style.fastest(&format!( - " Fastest:\t{:.4} secs", - calculate_fastest_request(res) - )) + style.fastest(&format!(" Fastest:\t{:.4} secs", res.fastest_latency())) )?; writeln!( w, "{}", - style.average(&format!( - " Average:\t{:.4} secs", - calculate_average_request(res) - )) + style.average(&format!(" Average:\t{:.4} secs", res.average_latency())) )?; writeln!( w, @@ -394,9 +389,10 @@ fn print_summary( writeln!( w, " Total data:\t{:.2}", - Byte::from_u64(calculate_total_data(res)).get_appropriate_unit(byte_unit::UnitType::Binary) + Byte::from_u64(res.total_data() as u64).get_appropriate_unit(byte_unit::UnitType::Binary) )?; - if let Some(size) = calculate_size_per_request(res) + if let Some(size) = res + .size_per_request() .map(|n| Byte::from_u64(n).get_appropriate_unit(byte_unit::UnitType::Binary)) { writeln!(w, " Size/request:\t{size:.2}")?; @@ -406,12 +402,15 @@ fn print_summary( writeln!( w, " Size/sec:\t{:.2}", - Byte::from_u64((calculate_size_per_sec(res, total_duration)) as u64) + Byte::from_u64((res.total_data() as f64 / total_duration.as_secs_f64()) as u64) .get_appropriate_unit(byte_unit::UnitType::Binary) )?; writeln!(w)?; - let mut durations = get_durations_all(res); + let mut durations = res + .duration_all() + .map(|d| d.as_secs_f64()) + .collect::>(); writeln!(w, "{}", style.heading("Response time histogram:"))?; print_histogram(w, &durations, style)?; @@ -422,7 +421,10 @@ fn print_summary( writeln!(w)?; if stats_success_breakdown { - let mut durations_successful = get_durations_successful(res); + let mut durations_successful = res + .duration_successful() + .map(|d| d.as_secs_f64()) + .collect::>(); writeln!( w, @@ -440,7 +442,10 @@ fn print_summary( print_distribution(w, &mut durations_successful, style)?; writeln!(w)?; - let mut durations_not_successful = get_durations_not_successful(res); + let mut durations_not_successful = res + .duration_not_successful() + .map(|d| d.as_secs_f64()) + .collect::>(); writeln!( w, @@ -461,7 +466,7 @@ fn print_summary( writeln!(w)?; let connection_times: Vec<(std::time::Instant, ConnectionTime)> = - calculate_connection_times_base(res); + res.connection_times_base().collect(); writeln!( w, "{}", @@ -484,11 +489,7 @@ fn print_summary( )?; writeln!(w)?; - let mut status_dist: BTreeMap = Default::default(); - - for s in res.success.iter().map(|r| r.status) { - *status_dist.entry(s).or_default() += 1; - } + let mut status_dist: BTreeMap = res.status_code_distribution(); let mut status_v: Vec<(http::StatusCode, usize)> = status_dist.into_iter().collect(); status_v.sort_by_key(|t| std::cmp::Reverse(t.1)); @@ -506,8 +507,11 @@ fn print_summary( )?; } - let mut error_v: Vec<(String, usize)> = - res.error.iter().map(|(k, v)| (k.clone(), *v)).collect(); + let mut error_v: Vec<(String, usize)> = res + .error_distribution() + .iter() + .map(|(k, v)| (k.clone(), *v)) + .collect(); error_v.sort_by_key(|t| std::cmp::Reverse(t.1)); if !error_v.is_empty() { @@ -607,80 +611,10 @@ fn percentiles(values: &mut [f64]) -> BTreeMap { .collect() } -fn calculate_success_rate(res: &ResultData) -> f64 { - let dead_line = ClientError::Deadline.to_string(); - // We ignore deadline errors which are because of `-z` option, not because of the server - let denominator = res.success.len() - + res - .error - .iter() - .filter_map(|(k, v)| if k == &dead_line { None } else { Some(v) }) - .sum::(); - let numerator = res.success.len(); - - numerator as f64 / denominator as f64 -} - -fn calculate_slowest_request(res: &ResultData) -> f64 { - res.success - .iter() - .map(|r| r.duration().as_secs_f64()) - .collect::() - .max() -} - -fn calculate_fastest_request(res: &ResultData) -> f64 { - res.success - .iter() - .map(|r| r.duration().as_secs_f64()) - .collect::() - .min() -} - -fn calculate_average_request(res: &ResultData) -> f64 { - let mean = res - .success - .iter() - .map(|r| r.duration().as_secs_f64()) - .collect::(); - if mean.is_empty() { - f64::NAN - } else { - mean.mean() - } -} - fn calculate_requests_per_sec(res: &ResultData, total_duration: Duration) -> f64 { res.len() as f64 / total_duration.as_secs_f64() } -fn calculate_total_data(res: &ResultData) -> u64 { - res.success.iter().map(|r| r.len_bytes as u64).sum::() -} - -fn calculate_size_per_request(res: &ResultData) -> Option { - res.success - .iter() - .map(|r| r.len_bytes as u64) - .sum::() - .checked_div(res.success.len() as u64) -} - -fn calculate_size_per_sec(res: &ResultData, total_duration: Duration) -> f64 { - res.success - .iter() - .map(|r| r.len_bytes as u128) - .sum::() as f64 - / total_duration.as_secs_f64() -} - -fn calculate_connection_times_base(res: &ResultData) -> Vec<(Instant, ConnectionTime)> { - res.success - .iter() - .filter_map(|r| r.connection_time.map(|c| (r.start, c))) - .collect() -} - fn calculate_connection_times_dns_dialup_average( connection_times: &[(Instant, ConnectionTime)], ) -> f64 { @@ -741,6 +675,7 @@ fn calculate_connection_times_dns_lookup_slowest( .max() } +/* fn get_durations_all(res: &ResultData) -> Vec { res.success .iter() @@ -763,6 +698,9 @@ fn get_durations_not_successful(res: &ResultData) -> Vec { .map(|r| r.duration().as_secs_f64()) .collect::>() } +*/ + +/* #[cfg(test)] mod tests { @@ -1038,3 +976,5 @@ mod tests { assert_eq!(durations.get(2), None); } } + +*/ diff --git a/src/result_data.rs b/src/result_data.rs index 13217b20..306e0add 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -1,14 +1,19 @@ -use std::collections::BTreeMap; +use std::{ + collections::BTreeMap, + time::{Duration, Instant}, +}; -use crate::client::{ClientError, RequestResult}; +use hyper::StatusCode; + +use crate::client::{ClientError, ConnectionTime, RequestResult}; /// Data container for the results of the all requests /// When a request is successful, the result is pushed to the `success` vector and the memory consumption will not be a problem because the number of successful requests is limited by network overhead. /// When a request fails, the error message is pushed to the `error` map because the number of error messages may huge. #[derive(Debug, Default)] pub struct ResultData { - pub success: Vec, - pub error: BTreeMap, + success: Vec, + error_distribution: BTreeMap, } impl ResultData { @@ -16,13 +21,113 @@ impl ResultData { match result { Ok(result) => self.success.push(result), Err(err) => { - let count = self.error.entry(err.to_string()).or_insert(0); + let count = self.error_distribution.entry(err.to_string()).or_insert(0); *count += 1; } } } pub fn len(&self) -> usize { - self.success.len() + self.error.values().sum::() + self.success.len() + self.error_distribution.values().sum::() + } + + pub fn success(&self) -> &[RequestResult] { + &self.success + } + + pub fn success_rate(&self) -> f64 { + let dead_line = ClientError::Deadline.to_string(); + // We ignore deadline errors which are because of `-z` option, not because of the server + let denominator = self.success.len() + + self + .error_distribution + .iter() + .filter_map(|(k, v)| if k == &dead_line { None } else { Some(v) }) + .sum::(); + let numerator = self.success.len(); + + numerator as f64 / denominator as f64 + } + + pub fn slowest_latency(&self) -> f64 { + self.success + .iter() + .map(|result| result.duration().as_secs_f64()) + .collect::() + .max() + } + + pub fn fastest_latency(&self) -> f64 { + self.success + .iter() + .map(|result| result.duration().as_secs_f64()) + .collect::() + .min() + } + + pub fn average_latency(&self) -> f64 { + let mean = self + .success + .iter() + .map(|r| r.duration().as_secs_f64()) + .collect::(); + if mean.is_empty() { + f64::NAN + } else { + mean.mean() + } + } + + pub fn error_distribution(&self) -> &BTreeMap { + &self.error_distribution + } + + pub fn end_times_from_start(&self) -> impl Iterator + '_ { + self.success.iter().map(|result| result.end - result.start) + } + + pub fn status_code_distribution(&self) -> BTreeMap { + let mut dist = BTreeMap::new(); + for result in &self.success { + let count = dist.entry(result.status).or_insert(0); + *count += 1; + } + dist + } + + pub fn connection_times_base(&self) -> impl Iterator + '_ { + self.success + .iter() + .filter_map(|r| r.connection_time.map(|ct| (r.start, ct))) + } + + pub fn total_data(&self) -> usize { + self.success.iter().map(|r| r.len_bytes).sum() + } + + pub fn size_per_request(&self) -> Option { + self.success + .iter() + .map(|r| r.len_bytes as u64) + .sum::() + .checked_div(self.success.len() as u64) + } + + pub fn duration_all(&self) -> impl Iterator + '_ { + self.success.iter().map(|r| r.duration()) + } + + pub fn duration_successful(&self) -> impl Iterator + '_ { + self.success + .iter() + .filter(|r| r.status.is_success()) + .map(|r| r.duration()) + } + + pub fn duration_not_successful(&self) -> impl Iterator + '_ { + self.success + .iter() + .filter(|r| !r.status.is_success()) + .map(|r| r.duration()) } } From 413ff42f38a7d0ccbf4b72e5b3e43744bd965142 Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 8 Apr 2024 18:08:48 +0900 Subject: [PATCH 2/8] cargo fix --- src/printer.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index de01e481..027b50d0 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,8 +1,4 @@ -use crate::{ - client::{ClientError, ConnectionTime}, - histogram::histogram, - result_data::ResultData, -}; +use crate::{client::ConnectionTime, histogram::histogram, result_data::ResultData}; use average::{Max, Variance}; use byte_unit::Byte; use crossterm::style::{StyledContent, Stylize}; @@ -121,7 +117,7 @@ pub fn print_result( /// Print all summary as JSON fn print_json( w: &mut W, - start: Instant, + _start: Instant, res: &ResultData, total_duration: Duration, stats_success_breakdown: bool, @@ -305,7 +301,7 @@ fn print_json( percentiles: rps_percentiles, }; - let mut status_code_distribution = res.status_code_distribution(); + let status_code_distribution = res.status_code_distribution(); let connection_times: Vec<(std::time::Instant, ConnectionTime)> = res.connection_times_base().collect(); @@ -489,7 +485,7 @@ fn print_summary( )?; writeln!(w)?; - let mut status_dist: BTreeMap = res.status_code_distribution(); + let status_dist: BTreeMap = res.status_code_distribution(); let mut status_v: Vec<(http::StatusCode, usize)> = status_dist.into_iter().collect(); status_v.sort_by_key(|t| std::cmp::Reverse(t.1)); From 271ca2ee67f14480dc70e1b9a7a4fc4ae2e8c1d2 Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 8 Apr 2024 18:18:34 +0900 Subject: [PATCH 3/8] comment --- src/result_data.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/result_data.rs b/src/result_data.rs index 306e0add..41a537e9 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -17,6 +17,7 @@ pub struct ResultData { } impl ResultData { + #[inline] pub fn push(&mut self, result: Result) { match result { Ok(result) => self.success.push(result), @@ -31,10 +32,14 @@ impl ResultData { self.success.len() + self.error_distribution.values().sum::() } + // An existence of this method doesn't prevent us to using hdrhistogram. + // Because this is only called from `monitor` and `monitor` can collect own data. pub fn success(&self) -> &[RequestResult] { &self.success } + // It's very happy if you can provide all below methods without array (= non liner memory consumption) and fast `push` runtime. + pub fn success_rate(&self) -> f64 { let dead_line = ClientError::Deadline.to_string(); // We ignore deadline errors which are because of `-z` option, not because of the server From 051ea080a307c0ea3aad43643974f867ea905de7 Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 8 Apr 2024 18:37:10 +0900 Subject: [PATCH 4/8] MinMax( error_distribution: BTreeMap, } + let latency_stat = res.latency_stat(); + let summary = Summary { success_rate: res.success_rate(), total: total_duration.as_secs_f64(), - slowest: res.slowest_latency(), - fastest: res.fastest_latency(), - average: res.average_latency(), + slowest: latency_stat.max(), + fastest: latency_stat.min(), + average: latency_stat.mean(), requests_per_sec: calculate_requests_per_sec(res, total_duration), total_data: res.total_data() as u64, size_per_request: res.size_per_request(), @@ -361,20 +363,21 @@ fn print_summary( ) )?; writeln!(w, " Total:\t{:.4} secs", total_duration.as_secs_f64())?; + let latency_stat = res.latency_stat(); writeln!( w, "{}", - style.slowest(&format!(" Slowest:\t{:.4} secs", res.slowest_latency())) + style.slowest(&format!(" Slowest:\t{:.4} secs", latency_stat.max())) )?; writeln!( w, "{}", - style.fastest(&format!(" Fastest:\t{:.4} secs", res.fastest_latency())) + style.fastest(&format!(" Fastest:\t{:.4} secs", latency_stat.min())) )?; writeln!( w, "{}", - style.average(&format!(" Average:\t{:.4} secs", res.average_latency())) + style.average(&format!(" Average:\t{:.4} secs", latency_stat.mean())) )?; writeln!( w, diff --git a/src/result_data.rs b/src/result_data.rs index 41a537e9..aedf452b 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -3,6 +3,7 @@ use std::{ time::{Duration, Instant}, }; +use average::{concatenate, Estimate, Max, Mean, Min}; use hyper::StatusCode; use crate::client::{ClientError, ConnectionTime, RequestResult}; @@ -16,6 +17,25 @@ pub struct ResultData { error_distribution: BTreeMap, } +// https://github.com/vks/average/issues/19 +// can't define pub struct for now. +concatenate!(MinMaxMeanInner, [Min, min], [Max, max], [Mean, mean]); +pub struct MinMaxMean(MinMaxMeanInner); + +impl MinMaxMean { + pub fn min(&self) -> f64 { + self.0.min() + } + + pub fn max(&self) -> f64 { + self.0.max() + } + + pub fn mean(&self) -> f64 { + self.0.mean() + } +} + impl ResultData { #[inline] pub fn push(&mut self, result: Result) { @@ -54,33 +74,13 @@ impl ResultData { numerator as f64 / denominator as f64 } - pub fn slowest_latency(&self) -> f64 { - self.success - .iter() - .map(|result| result.duration().as_secs_f64()) - .collect::() - .max() - } - - pub fn fastest_latency(&self) -> f64 { - self.success - .iter() - .map(|result| result.duration().as_secs_f64()) - .collect::() - .min() - } - - pub fn average_latency(&self) -> f64 { - let mean = self + pub fn latency_stat(&self) -> MinMaxMean { + let latencies = self .success .iter() - .map(|r| r.duration().as_secs_f64()) - .collect::(); - if mean.is_empty() { - f64::NAN - } else { - mean.mean() - } + .map(|result| result.duration().as_secs_f64()) + .collect::(); + MinMaxMean(latencies) } pub fn error_distribution(&self) -> &BTreeMap { From 8a455522798ea8a1c3001ff9bbcd9d835b18ef08 Mon Sep 17 00:00:00 2001 From: hatoo Date: Tue, 9 Apr 2024 21:16:34 +0900 Subject: [PATCH 5/8] refactor dns print --- src/printer.rs | 34 ++++++++++++++++++---------------- src/result_data.rs | 24 +++++++++++++++++++++--- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 74c230d5..b7d27e21 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -305,18 +305,19 @@ fn print_json( let status_code_distribution = res.status_code_distribution(); - let connection_times: Vec<(std::time::Instant, ConnectionTime)> = - res.connection_times_base().collect(); + let dns_dialup_stat = res.dns_dialup_stat(); + let dns_lookup_stat = res.dns_lookup_stat(); + let details = Details { dns_dialup: Triple { - average: calculate_connection_times_dns_dialup_average(&connection_times), - fastest: calculate_connection_times_dns_dialup_fastest(&connection_times), - slowest: calculate_connection_times_dns_dialup_slowest(&connection_times), + average: dns_dialup_stat.mean(), + fastest: dns_dialup_stat.min(), + slowest: dns_dialup_stat.max(), }, dns_lookup: Triple { - average: calculate_connection_times_dns_lookup_average(&connection_times), - fastest: calculate_connection_times_dns_lookup_fastest(&connection_times), - slowest: calculate_connection_times_dns_lookup_slowest(&connection_times), + average: dns_lookup_stat.mean(), + fastest: dns_lookup_stat.min(), + slowest: dns_lookup_stat.max(), }, }; @@ -464,8 +465,9 @@ fn print_summary( } writeln!(w)?; - let connection_times: Vec<(std::time::Instant, ConnectionTime)> = - res.connection_times_base().collect(); + let dns_dialup_stat = res.dns_dialup_stat(); + let dns_lookup_stat = res.dns_lookup_stat(); + writeln!( w, "{}", @@ -475,16 +477,16 @@ fn print_summary( writeln!( w, " DNS+dialup:\t{:.4} secs, {:.4} secs, {:.4} secs", - calculate_connection_times_dns_dialup_average(&connection_times), - calculate_connection_times_dns_dialup_fastest(&connection_times), - calculate_connection_times_dns_dialup_slowest(&connection_times), + dns_dialup_stat.mean(), + dns_dialup_stat.min(), + dns_dialup_stat.max() )?; writeln!( w, " DNS-lookup:\t{:.4} secs, {:.4} secs, {:.4} secs", - calculate_connection_times_dns_lookup_average(&connection_times), - calculate_connection_times_dns_lookup_fastest(&connection_times), - calculate_connection_times_dns_lookup_slowest(&connection_times), + dns_lookup_stat.mean(), + dns_lookup_stat.min(), + dns_lookup_stat.max() )?; writeln!(w)?; diff --git a/src/result_data.rs b/src/result_data.rs index aedf452b..dc2637e2 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -100,10 +100,28 @@ impl ResultData { dist } - pub fn connection_times_base(&self) -> impl Iterator + '_ { - self.success + pub fn dns_dialup_stat(&self) -> MinMaxMean { + let dns_dialup = self + .success + .iter() + .filter_map(|r| { + r.connection_time + .map(|ct| (ct.dialup - r.start).as_secs_f64()) + }) + .collect::(); + MinMaxMean(dns_dialup) + } + + pub fn dns_lookup_stat(&self) -> MinMaxMean { + let dns_lookup = self + .success .iter() - .filter_map(|r| r.connection_time.map(|ct| (r.start, ct))) + .filter_map(|r| { + r.connection_time + .map(|ct| (ct.dns_lookup - r.start).as_secs_f64()) + }) + .collect::(); + MinMaxMean(dns_lookup) } pub fn total_data(&self) -> usize { From a640ccf00d807565e09b2d4a02d18e9e041810ab Mon Sep 17 00:00:00 2001 From: hatoo Date: Tue, 9 Apr 2024 21:18:47 +0900 Subject: [PATCH 6/8] remove func --- src/printer.rs | 95 ++-------------------------------------------- src/result_data.rs | 7 +--- 2 files changed, 5 insertions(+), 97 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index b7d27e21..690ddc94 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,4 +1,4 @@ -use crate::{client::ConnectionTime, histogram::histogram, result_data::ResultData}; +use crate::{histogram::histogram, result_data::ResultData}; use average::{Max, Variance}; use byte_unit::Byte; use crossterm::style::{StyledContent, Stylize}; @@ -208,7 +208,7 @@ fn print_json( slowest: latency_stat.max(), fastest: latency_stat.min(), average: latency_stat.mean(), - requests_per_sec: calculate_requests_per_sec(res, total_duration), + requests_per_sec: res.len() as f64 / total_duration.as_secs_f64(), total_data: res.total_data() as u64, size_per_request: res.size_per_request(), size_per_sec: res.total_data() as f64 / total_duration.as_secs_f64(), @@ -383,7 +383,7 @@ fn print_summary( writeln!( w, " Requests/sec:\t{:.4}", - calculate_requests_per_sec(res, total_duration) + res.len() as f64 / total_duration.as_secs_f64() )?; writeln!(w)?; writeln!( @@ -612,95 +612,6 @@ fn percentiles(values: &mut [f64]) -> BTreeMap { .collect() } -fn calculate_requests_per_sec(res: &ResultData, total_duration: Duration) -> f64 { - res.len() as f64 / total_duration.as_secs_f64() -} - -fn calculate_connection_times_dns_dialup_average( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dialup - *s).as_secs_f64()) - .collect::() - .mean() -} - -fn calculate_connection_times_dns_dialup_fastest( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dialup - *s).as_secs_f64()) - .collect::() - .min() -} - -fn calculate_connection_times_dns_dialup_slowest( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dialup - *s).as_secs_f64()) - .collect::() - .max() -} - -fn calculate_connection_times_dns_lookup_average( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dns_lookup - *s).as_secs_f64()) - .collect::() - .mean() -} - -fn calculate_connection_times_dns_lookup_fastest( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dns_lookup - *s).as_secs_f64()) - .collect::() - .min() -} - -fn calculate_connection_times_dns_lookup_slowest( - connection_times: &[(Instant, ConnectionTime)], -) -> f64 { - connection_times - .iter() - .map(|(s, c)| (c.dns_lookup - *s).as_secs_f64()) - .collect::() - .max() -} - -/* -fn get_durations_all(res: &ResultData) -> Vec { - res.success - .iter() - .map(|r| r.duration().as_secs_f64()) - .collect::>() -} - -fn get_durations_successful(res: &ResultData) -> Vec { - res.success - .iter() - .filter(|r| r.status.is_success()) - .map(|r| r.duration().as_secs_f64()) - .collect::>() -} - -fn get_durations_not_successful(res: &ResultData) -> Vec { - res.success - .iter() - .filter(|r| r.status.is_client_error() || r.status.is_server_error()) - .map(|r| r.duration().as_secs_f64()) - .collect::>() -} -*/ - /* #[cfg(test)] diff --git a/src/result_data.rs b/src/result_data.rs index dc2637e2..4e06b65f 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -1,12 +1,9 @@ -use std::{ - collections::BTreeMap, - time::{Duration, Instant}, -}; +use std::{collections::BTreeMap, time::Duration}; use average::{concatenate, Estimate, Max, Mean, Min}; use hyper::StatusCode; -use crate::client::{ClientError, ConnectionTime, RequestResult}; +use crate::client::{ClientError, RequestResult}; /// Data container for the results of the all requests /// When a request is successful, the result is pushed to the `success` vector and the memory consumption will not be a problem because the number of successful requests is limited by network overhead. From f962a93ccf796b9a3b5fd3747fa533c5f65dc1a1 Mon Sep 17 00:00:00 2001 From: hatoo Date: Tue, 9 Apr 2024 21:52:11 +0900 Subject: [PATCH 7/8] test --- Cargo.lock | 1 + Cargo.toml | 1 + src/result_data.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 435daf93..af18c17a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1792,6 +1792,7 @@ dependencies = [ "bytes", "clap", "crossterm", + "float-cmp", "float-ord", "flume", "futures", diff --git a/Cargo.toml b/Cargo.toml index ff51e61d..1294a4ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ aws-lc-rs = { version = "1.0.0", features = ["bindgen"] } assert_cmd = "2.0.2" axum = { version = "0.7", features = ["http2"] } bytes = "1.0" +float-cmp = "0.9.0" lazy_static = "1.4.0" predicates = "3.1.0" regex = "1.9.6" diff --git a/src/result_data.rs b/src/result_data.rs index 4e06b65f..4689cdc3 100644 --- a/src/result_data.rs +++ b/src/result_data.rs @@ -151,3 +151,163 @@ impl ResultData { .map(|r| r.duration()) } } + +#[cfg(test)] +mod tests { + use float_cmp::assert_approx_eq; + + use super::*; + use crate::client::{ClientError, ConnectionTime, RequestResult}; + use std::time::{Duration, Instant}; + + fn build_mock_request_result( + status: StatusCode, + request_time: u64, + connection_time_dns_lookup: u64, + connection_time_dialup: u64, + size: usize, + ) -> Result { + let now = Instant::now(); + Ok(RequestResult { + start_latency_correction: None, + start: now, + connection_time: Some(ConnectionTime { + dns_lookup: now + .checked_add(Duration::from_millis(connection_time_dns_lookup)) + .unwrap(), + dialup: now + .checked_add(Duration::from_millis(connection_time_dialup)) + .unwrap(), + }), + end: now + .checked_add(Duration::from_millis(request_time)) + .unwrap(), + status, + len_bytes: size, + }) + } + + fn build_mock_request_results() -> ResultData { + let mut results = ResultData::default(); + + results.push(build_mock_request_result( + StatusCode::OK, + 1000, + 200, + 50, + 100, + )); + results.push(build_mock_request_result( + StatusCode::BAD_REQUEST, + 100000, + 250, + 100, + 200, + )); + results.push(build_mock_request_result( + StatusCode::INTERNAL_SERVER_ERROR, + 1000000, + 300, + 150, + 300, + )); + results + } + + #[test] + fn test_calculate_success_rate() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.success_rate(), 1.0); + } + + #[test] + fn test_calculate_slowest_request() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.latency_stat().max(), 1000.0); + } + + #[test] + fn test_calculate_average_request() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.latency_stat().mean(), 367.0); + } + + #[test] + fn test_calculate_total_data() { + let res = build_mock_request_results(); + assert_eq!(res.total_data(), 600); + } + + #[test] + fn test_calculate_size_per_request() { + let res = build_mock_request_results(); + assert_eq!(res.size_per_request(), Some(200)); + } + + #[test] + fn test_calculate_connection_times_dns_dialup_average() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_dialup_stat().mean(), 0.1); + } + + #[test] + fn test_calculate_connection_times_dns_dialup_fastest() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_dialup_stat().min(), 0.05); + } + + #[test] + fn test_calculate_connection_times_dns_dialup_slowest() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_dialup_stat().max(), 0.15); + } + + #[test] + fn test_calculate_connection_times_dns_lookup_average() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_lookup_stat().mean(), 0.25); + } + + #[test] + fn test_calculate_connection_times_dns_lookup_fastest() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_lookup_stat().min(), 0.2); + } + + #[test] + fn test_calculate_connection_times_dns_lookup_slowest() { + let res = build_mock_request_results(); + assert_approx_eq!(f64, res.dns_lookup_stat().max(), 0.3); + } + + #[test] + fn test_get_durations_all() { + let res = build_mock_request_results(); + let durations: Vec<_> = res.duration_all().map(|d| d.as_secs_f64()).collect(); + assert_approx_eq!(f64, durations[0], 1.0); + assert_approx_eq!(f64, durations[1], 100.0); + assert_approx_eq!(f64, durations[2], 1000.0); + } + + #[test] + fn test_get_durations_successful() { + let res = build_mock_request_results(); + let durations: Vec<_> = res.duration_successful().map(|d| d.as_secs_f64()).collect(); + // Round the calculations to 4 decimal places to remove imprecision + assert_approx_eq!(f64, durations[0], 1.0); + assert_eq!(durations.get(1), None); + } + + #[test] + fn test_get_durations_not_successful() { + let res = build_mock_request_results(); + let durations: Vec<_> = res + .duration_not_successful() + .map(|d| d.as_secs_f64()) + .collect(); + // Round the calculations to 4 decimal places to remove imprecision + assert_approx_eq!(f64, durations[0], 100.0); + assert_approx_eq!(f64, durations[1], 1000.0); + assert_eq!(durations.get(2), None); + } +} From 7c9b8da02517e34d78ff5fba56e1f6edb69c9954 Mon Sep 17 00:00:00 2001 From: hatoo Date: Tue, 9 Apr 2024 22:01:42 +0900 Subject: [PATCH 8/8] test --- src/printer.rs | 275 ++----------------------------------------------- 1 file changed, 11 insertions(+), 264 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 690ddc94..5830f29e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -612,73 +612,11 @@ fn percentiles(values: &mut [f64]) -> BTreeMap { .collect() } -/* - #[cfg(test)] mod tests { - use super::*; - use crate::client::{ClientError, RequestResult}; - use std::time::Duration; - - fn build_mock_request_result( - status: StatusCode, - request_time: u64, - connection_time_dns_lookup: u64, - connection_time_dialup: u64, - size: usize, - ) -> Result { - let now = Instant::now(); - Ok(RequestResult { - start_latency_correction: None, - start: now, - connection_time: Some(ConnectionTime { - dns_lookup: Instant::now() - .checked_add(Duration::from_millis(connection_time_dns_lookup)) - .unwrap(), - dialup: Instant::now() - .checked_add(Duration::from_millis(connection_time_dialup)) - .unwrap(), - }), - end: Instant::now() - .checked_add(Duration::from_millis(request_time)) - .unwrap(), - status, - len_bytes: size, - }) - } - - fn build_mock_request_results() -> ResultData { - let mut results = ResultData::default(); - - results.push(build_mock_request_result( - StatusCode::OK, - 1000, - 200, - 50, - 100, - )); - results.push(build_mock_request_result( - StatusCode::BAD_REQUEST, - 100000, - 250, - 100, - 200, - )); - results.push(build_mock_request_result( - StatusCode::INTERNAL_SERVER_ERROR, - 1000000, - 300, - 150, - 300, - )); - results - } + use float_cmp::assert_approx_eq; - fn fp_round(value: f64, places: f64) -> f64 { - let base: f64 = 10.0; - let multiplier = base.powf(places); - (value * multiplier).round() / multiplier - } + use super::*; #[test] fn test_percentile_iter() { @@ -688,205 +626,14 @@ mod tests { 12.0, 15.0, 15.0, 15.0, 15.0, 15.0, 20.0, 20.0, 20.0, 25.0, 30.0, ]; let result: Vec<(f64, f64)> = percentile_iter(&mut values).collect(); - assert_eq!(result[0], (10.0, 5_f64)); - assert_eq!(result[1], (25.0, 11_f64)); - assert_eq!(result[2], (50.0, 12_f64)); - assert_eq!(result[3], (75.0, 15_f64)); - assert_eq!(result[4], (90.0, 20_f64)); - assert_eq!(result[5], (95.0, 25_f64)); - assert_eq!(result[6], (99.0, 30_f64)); - assert_eq!(result[7], (99.9, 30_f64)); - assert_eq!(result[8], (99.99, 30_f64)); - } - - #[test] - fn test_calculate_success_rate() { - let res = build_mock_request_results(); - assert_eq!(calculate_success_rate(&res), 1.0); - } - - #[test] - fn test_calculate_slowest_request() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_slowest_request(&build_mock_request_results()), - 4.0 - ), - 1000_f64 - ); - } - - #[test] - fn test_calculate_fastest_request() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_fastest_request(&build_mock_request_results()), - 4.0 - ), - 1_f64 - ); - } - - #[test] - fn test_calculate_average_request() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_average_request(&build_mock_request_results()), - 4.0 - ), - 367_f64 - ); - } - - #[test] - fn test_calculate_requests_per_sec() { - assert_eq!( - calculate_requests_per_sec(&build_mock_request_results(), Duration::from_secs(1)), - 3.0 - ); - } - - #[test] - fn test_calculate_total_data() { - assert_eq!(calculate_total_data(&build_mock_request_results()), 600); - } - - #[test] - fn test_calculate_size_per_request() { - assert_eq!( - calculate_size_per_request(&build_mock_request_results()).unwrap(), - 200 - ); - } - - #[test] - fn test_calculate_size_per_sec() { - assert_eq!( - (calculate_size_per_sec(&build_mock_request_results(), Duration::from_secs(1))), - 600.0 - ); - } - - #[test] - fn test_calculate_connection_times_base() { - assert_eq!( - calculate_connection_times_base(&build_mock_request_results()).len(), - 3 - ); - } - - #[test] - fn test_calculate_connection_times_dns_dialup_average() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_dialup_average(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.1 - ); - } - - #[test] - fn test_calculate_connection_times_dns_dialup_fastest() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_dialup_fastest(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.05 - ); - } - - #[test] - fn test_calculate_connection_times_dns_dialup_slowest() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_dialup_slowest(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.15 - ); - } - - #[test] - fn test_calculate_connection_times_dns_lookup_average() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_lookup_average(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.25 - ); - } - - #[test] - fn test_calculate_connection_times_dns_lookup_fastest() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_lookup_fastest(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.2 - ); - } - - #[test] - fn test_calculate_connection_times_dns_lookup_slowest() { - assert_eq!( - // Round the calculation to 4 decimal places to remove imprecision - fp_round( - calculate_connection_times_dns_lookup_slowest(&calculate_connection_times_base( - &build_mock_request_results() - )), - 4.0 - ), - 0.3 - ); - } - - #[test] - fn test_get_durations_all() { - let durations = get_durations_all(&build_mock_request_results()); - // Round the calculations to 4 decimal places to remove imprecision - assert_eq!(fp_round(durations[0], 4.0), 1.0); - assert_eq!(fp_round(durations[1], 4.0), 100.0); - assert_eq!(fp_round(durations[2], 4.0), 1000.0); - } - - #[test] - fn test_get_durations_successful() { - let durations = get_durations_successful(&build_mock_request_results()); - // Round the calculations to 4 decimal places to remove imprecision - assert_eq!(fp_round(durations[0], 4.0), 1.0); - assert_eq!(durations.get(1), None); - } - - #[test] - fn test_get_durations_not_successful() { - let durations = get_durations_not_successful(&build_mock_request_results()); - // Round the calculations to 4 decimal places to remove imprecision - assert_eq!(fp_round(durations[0], 4.0), 100.0); - assert_eq!(fp_round(durations[1], 4.0), 1000.0); - assert_eq!(durations.get(2), None); + assert_approx_eq!(&[f64], &[result[0].0, result[0].1], &[10.0, 5_f64]); + assert_approx_eq!(&[f64], &[result[1].0, result[1].1], &[25.0, 11_f64]); + assert_approx_eq!(&[f64], &[result[2].0, result[2].1], &[50.0, 12_f64]); + assert_approx_eq!(&[f64], &[result[3].0, result[3].1], &[75.0, 15_f64]); + assert_approx_eq!(&[f64], &[result[4].0, result[4].1], &[90.0, 20_f64]); + assert_approx_eq!(&[f64], &[result[5].0, result[5].1], &[95.0, 25_f64]); + assert_approx_eq!(&[f64], &[result[6].0, result[6].1], &[99.0, 30_f64]); + assert_approx_eq!(&[f64], &[result[7].0, result[7].1], &[99.9, 30_f64]); + assert_approx_eq!(&[f64], &[result[8].0, result[8].1], &[99.99, 30_f64]); } } - -*/