Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a stats-success-breakdown flag for more detailed status code specific response statistics #296

Merged
merged 3 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

- Add style and colors to the summary view #64
- Added a stats-success-breakdown flag for more detailed status code specific response statistics #212

# 0.6.2 (2023-08-12)

Expand Down
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ Note: If qps is specified, burst will be ignored",
long = "unix-socket"
)]
unix_socket: Option<std::path::PathBuf>,
#[clap(
help = "Include a response status code successful or not successful breakdown for the time histogram and distribution statistics",
long = "stats-success-breakdown"
)]
stats_success_breakdown: bool,
}

/// An entry specified by `connect-to` to override DNS resolution and default
Expand Down Expand Up @@ -367,7 +372,7 @@ async fn main() -> anyhow::Result<()> {
}
_ = ctrl_c_rx.recv_async() => {
// User pressed ctrl-c.
let _ = printer::print_result(&mut std::io::stdout(),print_mode,start, &all, start.elapsed(), opts.disable_color);
let _ = printer::print_result(&mut std::io::stdout(),print_mode,start, &all, start.elapsed(), opts.disable_color, opts.stats_success_breakdown);
std::process::exit(libc::EXIT_SUCCESS);
}
}
Expand All @@ -390,6 +395,7 @@ async fn main() -> anyhow::Result<()> {
start,
fps: opts.fps,
disable_color: opts.disable_color,
stats_success_breakdown: opts.stats_success_breakdown,
}
.monitor(),
)
Expand Down Expand Up @@ -565,6 +571,7 @@ async fn main() -> anyhow::Result<()> {
&res,
duration,
opts.disable_color,
opts.stats_success_breakdown,
)?;

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct Monitor {
// Frame per scond of TUI
pub fps: usize,
pub disable_color: bool,
pub stats_success_breakdown: bool,
}

impl Monitor {
Expand Down Expand Up @@ -452,6 +453,7 @@ impl Monitor {
&all,
now - self.start,
self.disable_color,
self.stats_success_breakdown,
);
std::process::exit(libc::EXIT_SUCCESS);
}
Expand Down
152 changes: 139 additions & 13 deletions src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,17 @@ pub fn print_result<W: Write, E: std::fmt::Display>(
res: &[Result<RequestResult, E>],
total_duration: Duration,
disable_color: bool,
stats_success_breakdown: bool,
) -> anyhow::Result<()> {
match mode {
PrintMode::Text => print_summary(w, res, total_duration, disable_color)?,
PrintMode::Json => print_json(w, start, res, total_duration)?,
PrintMode::Text => print_summary(
w,
res,
total_duration,
disable_color,
stats_success_breakdown,
)?,
PrintMode::Json => print_json(w, start, res, total_duration, stats_success_breakdown)?,
}
Ok(())
}
Expand All @@ -115,6 +122,7 @@ fn print_json<W: Write, E: std::fmt::Display>(
start: Instant,
res: &[Result<RequestResult, E>],
total_duration: Duration,
stats_success_breakdown: bool,
) -> serde_json::Result<()> {
use serde::Serialize;
#[derive(Serialize)]
Expand Down Expand Up @@ -165,6 +173,26 @@ fn print_json<W: Write, E: std::fmt::Display>(
response_time_histogram: BTreeMap<String, usize>,
#[serde(rename = "latencyPercentiles")]
latency_percentiles: BTreeMap<String, f64>,
#[serde(
rename = "responseTimeHistogramSuccessful",
skip_serializing_if = "Option::is_none"
)]
response_time_histogram_successful: Option<BTreeMap<String, usize>>,
#[serde(
rename = "latencyPercentilesSuccessful",
skip_serializing_if = "Option::is_none"
)]
latency_percentiles_successful: Option<BTreeMap<String, f64>>,
#[serde(
rename = "responseTimeHistogramNotSuccessful",
skip_serializing_if = "Option::is_none"
)]
response_time_histogram_not_successful: Option<BTreeMap<String, usize>>,
#[serde(
rename = "latencyPercentilesNotSuccessful",
skip_serializing_if = "Option::is_none"
)]
latency_percentiles_not_successful: Option<BTreeMap<String, f64>>,
#[serde(rename = "rps")]
rps: Rps,
details: Details,
Expand Down Expand Up @@ -220,10 +248,10 @@ fn print_json<W: Write, E: std::fmt::Display>(
.sum::<u128>() as f64
/ total_duration.as_secs_f64()),
};
let durations_base = res.iter().filter_map(|r| r.as_ref().ok());

let mut durations = res
.iter()
.filter_map(|r| r.as_ref().ok())
let mut durations = durations_base
.clone()
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();
float_ord::sort(&mut durations);
Expand All @@ -233,7 +261,52 @@ fn print_json<W: Write, E: std::fmt::Display>(
.map(|(k, v)| (k.to_string(), v))
.collect();

let latency_percentiles = percentiles(&durations, &[10, 25, 50, 75, 90, 95, 99]);
let latency_percentile_buckets = &[10, 25, 50, 75, 90, 95, 99];
let latency_percentiles = percentiles(&durations, latency_percentile_buckets);

let mut response_time_histogram_successful: Option<BTreeMap<String, usize>> = None;
let mut latency_percentiles_successful: Option<BTreeMap<String, f64>> = None;
let mut response_time_histogram_not_successful: Option<BTreeMap<String, usize>> = None;
let mut latency_percentiles_not_successful: Option<BTreeMap<String, f64>> = None;

if stats_success_breakdown {
let mut durations_successful = durations_base
.clone()
.filter(|r| r.status.is_success())
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();
float_ord::sort(&mut durations_successful);

response_time_histogram_successful = Some(
histogram(&durations_successful, 11)
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
);

latency_percentiles_successful = Some(percentiles(
&durations_successful,
latency_percentile_buckets,
));

let mut durations_not_successful = durations_base
.filter(|r| r.status.is_client_error() || r.status.is_server_error())
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();
float_ord::sort(&mut durations_not_successful);

response_time_histogram_not_successful = Some(
histogram(&durations_not_successful, 11)
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
);

latency_percentiles_not_successful = Some(percentiles(
&durations_not_successful,
latency_percentile_buckets,
));
}

let mut ends = res
.iter()
Expand Down Expand Up @@ -339,6 +412,10 @@ fn print_json<W: Write, E: std::fmt::Display>(
summary,
response_time_histogram,
latency_percentiles,
response_time_histogram_successful,
latency_percentiles_successful,
response_time_histogram_not_successful,
latency_percentiles_not_successful,
rps,
details,
status_code_distribution: status_code_distribution
Expand All @@ -356,6 +433,7 @@ fn print_summary<W: Write, E: std::fmt::Display>(
res: &[Result<RequestResult, E>],
total_duration: Duration,
disable_color: bool,
stats_success_breakdown: bool,
) -> std::io::Result<()> {
let style = StyleScheme {
color_enabled: !disable_color,
Expand Down Expand Up @@ -448,21 +526,69 @@ fn print_summary<W: Write, E: std::fmt::Display>(
.get_appropriate_unit(true)
)?;
writeln!(w)?;
let durations = res
.iter()
.filter_map(|r| r.as_ref().ok())

let durations_base = res.iter().filter_map(|r| r.as_ref().ok());
let durations = durations_base
.clone()
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();

writeln!(w, "{}", style.heading("Response time histogram:"))?;

print_histogram(w, &durations, style)?;
writeln!(w)?;
writeln!(w, "{}", style.heading("Response time distribution:"))?;

writeln!(w, "{}", style.heading("Response time distribution:"))?;
print_distribution(w, &durations, style)?;
writeln!(w)?;

if stats_success_breakdown {
let mut durations_successful = durations_base
.clone()
.filter(|r| r.status.is_success())
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();
float_ord::sort(&mut durations_successful);

writeln!(
w,
"{}",
style.heading("Response time histogram (2xx only):")
)?;
print_histogram(w, &durations_successful, style)?;
writeln!(w)?;

writeln!(
w,
"{}",
style.heading("Response time distribution (2xx only):")
)?;
print_distribution(w, &durations_successful, style)?;
writeln!(w)?;

let mut durations_not_successful = durations_base
.filter(|r| r.status.is_client_error() || r.status.is_server_error())
.map(|r| r.duration().as_secs_f64())
.collect::<Vec<_>>();
float_ord::sort(&mut durations_not_successful);

writeln!(
w,
"{}",
style.heading("Response time histogram (4xx + 5xx only):")
)?;
print_histogram(w, &durations_not_successful, style)?;
writeln!(w)?;

writeln!(
w,
"{}",
style.heading("Response time distribution (4xx + 5xx only):")
)?;
print_distribution(w, &durations_not_successful, style)?;
writeln!(w)?;
}
writeln!(w)?;

let connection_times: Vec<(std::time::Instant, ConnectionTime)> = res
.iter()
.filter_map(|r| r.as_ref().ok())
Expand Down Expand Up @@ -635,8 +761,8 @@ fn print_distribution<W: Write>(
Ok(())
}

fn percentiles(values: &[f64], pecents: &[i32]) -> BTreeMap<String, f64> {
pecents
fn percentiles(values: &[f64], percents: &[i32]) -> BTreeMap<String, f64> {
percents
.iter()
.map(|&p| {
let i = (f64::from(p) / 100.0 * values.len() as f64) as usize;
Expand Down
Loading