use itertools::Itertools;
use num_format::{Locale, ToFormattedString};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::{f32, fmt};
use crate::goose::{GooseRawRequest, GooseRequest, GooseTaskSet};
use crate::util;
use crate::GooseConfiguration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GooseMetric {
Request(GooseRawRequest),
Task(GooseRawTask),
}
pub type GooseRequestMetrics = HashMap<String, GooseRequest>;
pub type GooseTaskMetrics = Vec<Vec<GooseTaskMetric>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GooseRawTask {
pub elapsed: u64,
pub taskset_index: usize,
pub task_index: usize,
pub name: String,
pub run_time: u64,
pub success: bool,
pub user: usize,
}
impl GooseRawTask {
pub fn new(
elapsed: u128,
taskset_index: usize,
task_index: usize,
name: String,
user: usize,
) -> Self {
GooseRawTask {
elapsed: elapsed as u64,
taskset_index,
task_index,
name,
run_time: 0,
success: true,
user,
}
}
pub fn set_time(&mut self, time: u128, success: bool) {
self.run_time = time as u64;
self.success = success;
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct GooseTaskMetric {
pub taskset_index: usize,
pub taskset_name: String,
pub task_index: usize,
pub task_name: String,
pub times: BTreeMap<usize, usize>,
pub min_time: usize,
pub max_time: usize,
pub total_time: usize,
pub counter: usize,
pub success_count: usize,
pub fail_count: usize,
}
impl GooseTaskMetric {
pub fn new(
taskset_index: usize,
taskset_name: &str,
task_index: usize,
task_name: &str,
) -> Self {
GooseTaskMetric {
taskset_index,
taskset_name: taskset_name.to_string(),
task_index,
task_name: task_name.to_string(),
times: BTreeMap::new(),
min_time: 0,
max_time: 0,
total_time: 0,
counter: 0,
success_count: 0,
fail_count: 0,
}
}
pub fn set_time(&mut self, time: u64, success: bool) {
let time_usize = time as usize;
if self.min_time == 0 || time_usize < self.min_time {
self.min_time = time_usize;
}
if time_usize > self.max_time {
self.max_time = time_usize;
}
self.total_time += time_usize;
self.counter += 1;
if success {
self.success_count += 1;
} else {
self.fail_count += 1;
}
let rounded_time = match time {
0..=100 => time_usize,
101..=500 => ((time as f64 / 10.0).round() * 10.0) as usize,
501..=1000 => ((time as f64 / 100.0).round() * 10.0) as usize,
_ => ((time as f64 / 1000.0).round() * 10.0) as usize,
};
let counter = match self.times.get(&rounded_time) {
Some(c) => *c + 1,
None => 1,
};
self.times.insert(rounded_time, counter);
debug!("incremented {} counter: {}", rounded_time, counter);
}
}
#[derive(Clone, Debug, Default)]
pub struct GooseMetrics {
pub hash: u64,
pub duration: usize,
pub users: usize,
pub requests: GooseRequestMetrics,
pub tasks: GooseTaskMetrics,
pub display_percentile: bool,
pub display_status_codes: bool,
pub display_metrics: bool,
}
impl GooseMetrics {
pub fn initialize_task_metrics(
&mut self,
task_sets: &[GooseTaskSet],
config: &GooseConfiguration,
) {
self.tasks = Vec::new();
if !config.no_metrics && !config.no_task_metrics {
for task_set in task_sets {
let mut task_vector = Vec::new();
for task in &task_set.tasks {
task_vector.push(GooseTaskMetric::new(
task_set.task_sets_index,
&task_set.name,
task.tasks_index,
&task.name,
));
}
self.tasks.push(task_vector);
}
}
}
pub fn print(&self) {
if self.display_metrics {
info!("printing metrics after {} seconds...", self.duration);
print!("{}", self);
}
}
pub fn print_running(&self) {
if self.display_metrics {
info!(
"printing running metrics after {} seconds...",
self.duration
);
println!("{}", self);
}
}
pub fn fmt_requests(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.requests.is_empty() {
return Ok(());
}
writeln!(
fmt,
"\n === PER REQUEST METRICS ===\n ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8} | {:>7}",
"Name", "# reqs", "# fails", "req/s", "fail/s"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
let mut aggregate_fail_count = 0;
let mut aggregate_total_count = 0;
for (request_key, request) in self.requests.iter().sorted() {
let total_count = request.success_count + request.fail_count;
let fail_percent = if request.fail_count > 0 {
request.fail_count as f32 / total_count as f32 * 100.0
} else {
0.0
};
let (reqs, fails) =
per_second_calculations(self.duration, total_count, request.fail_count);
let reqs_precision = determine_precision(reqs);
let fails_precision = determine_precision(fails);
if fail_percent as usize == 100 || fail_percent as usize == 0 {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.reqs_p$} | {:>7.fails_p$}",
util::truncate_string(&request_key, 24),
total_count.to_formatted_string(&Locale::en),
format!(
"{} ({}%)",
request.fail_count.to_formatted_string(&Locale::en),
fail_percent as usize
),
reqs,
fails,
reqs_p = reqs_precision,
fails_p = fails_precision,
)?;
} else {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.reqs_p$} | {:>7.fails_p$}",
util::truncate_string(&request_key, 24),
total_count.to_formatted_string(&Locale::en),
format!(
"{} ({:.1}%)",
request.fail_count.to_formatted_string(&Locale::en),
fail_percent
),
reqs,
fails,
reqs_p = reqs_precision,
fails_p = fails_precision,
)?;
}
aggregate_total_count += total_count;
aggregate_fail_count += request.fail_count;
}
if self.requests.len() > 1 {
let aggregate_fail_percent = if aggregate_fail_count > 0 {
aggregate_fail_count as f32 / aggregate_total_count as f32 * 100.0
} else {
0.0
};
writeln!(
fmt,
" -------------------------+---------------+----------------+----------+--------"
)?;
let (reqs, fails) =
per_second_calculations(self.duration, aggregate_total_count, aggregate_fail_count);
let reqs_precision = determine_precision(reqs);
let fails_precision = determine_precision(fails);
if aggregate_fail_percent as usize == 100 || aggregate_fail_percent as usize == 0 {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.reqs_p$} | {:>7.fails_p$}",
"Aggregated",
aggregate_total_count.to_formatted_string(&Locale::en),
format!(
"{} ({}%)",
aggregate_fail_count.to_formatted_string(&Locale::en),
aggregate_fail_percent as usize
),
reqs,
fails,
reqs_p = reqs_precision,
fails_p = fails_precision,
)?;
} else {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.reqs_p$} | {:>7.fails_p$}",
"Aggregated",
aggregate_total_count.to_formatted_string(&Locale::en),
format!(
"{} ({:.1}%)",
aggregate_fail_count.to_formatted_string(&Locale::en),
aggregate_fail_percent
),
reqs,
fails,
reqs_p = reqs_precision,
fails_p = fails_precision,
)?;
}
}
Ok(())
}
pub fn fmt_tasks(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.tasks.is_empty() || !self.display_metrics {
return Ok(());
}
writeln!(
fmt,
"\n === PER TASK METRICS ===\n ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8} | {:>7}",
"Name", "# times run", "# fails", "task/s", "fail/s"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
let mut aggregate_fail_count = 0;
let mut aggregate_total_count = 0;
let mut task_count = 0;
for task_set in &self.tasks {
let mut displayed_task_set = false;
for task in task_set {
task_count += 1;
let total_count = task.success_count + task.fail_count;
let fail_percent = if task.fail_count > 0 {
task.fail_count as f32 / total_count as f32 * 100.0
} else {
0.0
};
let (runs, fails) =
per_second_calculations(self.duration, total_count, task.fail_count);
let runs_precision = determine_precision(runs);
let fails_precision = determine_precision(fails);
if !displayed_task_set {
writeln!(
fmt,
" {:24 } |",
util::truncate_string(
&format!("{}: {}", task.taskset_index + 1, &task.taskset_name),
60
),
)?;
displayed_task_set = true;
}
if fail_percent as usize == 100 || fail_percent as usize == 0 {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.runs_p$} | {:>7.fails_p$}",
util::truncate_string(
&format!(" {}: {}", task.task_index + 1, task.task_name),
24
),
total_count.to_formatted_string(&Locale::en),
format!(
"{} ({}%)",
task.fail_count.to_formatted_string(&Locale::en),
fail_percent as usize
),
runs,
fails,
runs_p = runs_precision,
fails_p = fails_precision,
)?;
} else {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.runs_p$} | {:>7.fails_p$}",
util::truncate_string(
&format!(" {}: {}", task.task_index + 1, task.task_name),
24
),
total_count.to_formatted_string(&Locale::en),
format!(
"{} ({:.1}%)",
task.fail_count.to_formatted_string(&Locale::en),
fail_percent
),
runs,
fails,
runs_p = runs_precision,
fails_p = fails_precision,
)?;
}
aggregate_total_count += total_count;
aggregate_fail_count += task.fail_count;
}
}
if task_count > 1 {
let aggregate_fail_percent = if aggregate_fail_count > 0 {
aggregate_fail_count as f32 / aggregate_total_count as f32 * 100.0
} else {
0.0
};
writeln!(
fmt,
" -------------------------+---------------+----------------+----------+--------"
)?;
let (runs, fails) =
per_second_calculations(self.duration, aggregate_total_count, aggregate_fail_count);
let runs_precision = determine_precision(runs);
let fails_precision = determine_precision(fails);
if aggregate_fail_percent as usize == 100 || aggregate_fail_percent as usize == 0 {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.runs_p$} | {:>7.fails_p$}",
"Aggregated",
aggregate_total_count.to_formatted_string(&Locale::en),
format!(
"{} ({}%)",
aggregate_fail_count.to_formatted_string(&Locale::en),
aggregate_fail_percent as usize
),
runs,
fails,
runs_p = runs_precision,
fails_p = fails_precision,
)?;
} else {
writeln!(
fmt,
" {:<24} | {:>13} | {:>14} | {:>8.runs_p$} | {:>7.fails_p$}",
"Aggregated",
aggregate_total_count.to_formatted_string(&Locale::en),
format!(
"{} ({:.1}%)",
aggregate_fail_count.to_formatted_string(&Locale::en),
aggregate_fail_percent
),
runs,
fails,
runs_p = runs_precision,
fails_p = fails_precision,
)?;
}
}
Ok(())
}
pub fn fmt_task_times(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.tasks.is_empty() || !self.display_metrics {
return Ok(());
}
let mut aggregate_task_times: BTreeMap<usize, usize> = BTreeMap::new();
let mut aggregate_total_task_time: usize = 0;
let mut aggregate_task_time_counter: usize = 0;
let mut aggregate_min_task_time: usize = 0;
let mut aggregate_max_task_time: usize = 0;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" {:<24} | {:>11} | {:>10} | {:>11} | {:>10}",
"Name", "Avg (ms)", "Min", "Max", "Median"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
let mut task_count = 0;
for task_set in &self.tasks {
let mut displayed_task_set = false;
for task in task_set {
task_count += 1;
if !displayed_task_set {
writeln!(
fmt,
" {:24 } |",
util::truncate_string(
&format!("{}: {}", task.taskset_index + 1, &task.taskset_name),
60
),
)?;
displayed_task_set = true;
}
aggregate_task_times = merge_times(aggregate_task_times, task.times.clone());
aggregate_total_task_time += &task.total_time;
aggregate_task_time_counter += &task.counter;
aggregate_min_task_time = update_min_time(aggregate_min_task_time, task.min_time);
aggregate_max_task_time = update_max_time(aggregate_max_task_time, task.max_time);
let average = match task.counter {
0 => 0.00,
_ => task.total_time as f32 / task.counter as f32,
};
let average_precision = determine_precision(average);
writeln!(
fmt,
" {:<24} | {:>11.avg_precision$} | {:>10} | {:>11} | {:>10}",
util::truncate_string(
&format!(" {}: {}", task.task_index + 1, task.task_name),
24
),
average,
format_number(task.min_time),
format_number(task.max_time),
format_number(util::median(
&task.times,
task.counter,
task.min_time,
task.max_time
)),
avg_precision = average_precision,
)?;
}
}
if task_count > 1 {
let average = match aggregate_task_time_counter {
0 => 0.00,
_ => aggregate_total_task_time as f32 / aggregate_task_time_counter as f32,
};
let average_precision = determine_precision(average);
writeln!(
fmt,
" -------------------------+-------------+------------+-------------+-----------"
)?;
writeln!(
fmt,
" {:<24} | {:>11.avg_precision$} | {:>10} | {:>11} | {:>10}",
"Aggregated",
average,
format_number(aggregate_min_task_time),
format_number(aggregate_max_task_time),
format_number(util::median(
&aggregate_task_times,
aggregate_task_time_counter,
aggregate_min_task_time,
aggregate_max_task_time
)),
avg_precision = average_precision,
)?;
}
Ok(())
}
pub fn fmt_response_times(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.requests.is_empty() {
return Ok(());
}
let mut aggregate_response_times: BTreeMap<usize, usize> = BTreeMap::new();
let mut aggregate_total_response_time: usize = 0;
let mut aggregate_response_time_counter: usize = 0;
let mut aggregate_min_response_time: usize = 0;
let mut aggregate_max_response_time: usize = 0;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" {:<24} | {:>11} | {:>10} | {:>10} | {:>11}",
"Name", "Avg (ms)", "Min", "Max", "Median"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
for (request_key, request) in self.requests.iter().sorted() {
let average = match request.response_time_counter {
0 => 0.0,
_ => request.total_response_time as f32 / request.response_time_counter as f32,
};
let average_precision = determine_precision(average);
aggregate_response_times =
merge_times(aggregate_response_times, request.response_times.clone());
aggregate_total_response_time += &request.total_response_time;
aggregate_response_time_counter += &request.response_time_counter;
aggregate_min_response_time =
update_min_time(aggregate_min_response_time, request.min_response_time);
aggregate_max_response_time =
update_max_time(aggregate_max_response_time, request.max_response_time);
writeln!(
fmt,
" {:<24} | {:>11.avg_precision$} | {:>10} | {:>11} | {:>10}",
util::truncate_string(&request_key, 24),
average,
format_number(request.min_response_time),
format_number(request.max_response_time),
format_number(util::median(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time
)),
avg_precision = average_precision,
)?;
}
if self.requests.len() > 1 {
let average = match aggregate_response_time_counter {
0 => 0.0,
_ => aggregate_total_response_time as f32 / aggregate_response_time_counter as f32,
};
let average_precision = determine_precision(average);
writeln!(
fmt,
" -------------------------+-------------+------------+-------------+-----------"
)?;
writeln!(
fmt,
" {:<24} | {:>11.avg_precision$} | {:>10} | {:>11} | {:>10}",
"Aggregated",
average,
format_number(aggregate_min_response_time),
format_number(aggregate_max_response_time),
format_number(util::median(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time
)),
avg_precision = average_precision,
)?;
}
Ok(())
}
pub fn fmt_percentiles(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.display_percentile {
return Ok(());
}
let mut aggregate_response_times: BTreeMap<usize, usize> = BTreeMap::new();
let mut aggregate_total_response_time: usize = 0;
let mut aggregate_response_time_counter: usize = 0;
let mut aggregate_min_response_time: usize = 0;
let mut aggregate_max_response_time: usize = 0;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" Slowest page load within specified percentile of requests (in ms):"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
writeln!(
fmt,
" {:<24} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6}",
"Name", "50%", "75%", "98%", "99%", "99.9%", "99.99%"
)?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
for (request_key, request) in self.requests.iter().sorted() {
aggregate_response_times =
merge_times(aggregate_response_times, request.response_times.clone());
aggregate_total_response_time += &request.total_response_time;
aggregate_response_time_counter += &request.response_time_counter;
aggregate_min_response_time =
update_min_time(aggregate_min_response_time, request.min_response_time);
aggregate_max_response_time =
update_max_time(aggregate_max_response_time, request.max_response_time);
writeln!(
fmt,
" {:<24} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6}",
util::truncate_string(&request_key, 24),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.5
),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.75
),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.98
),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.99
),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.999
),
calculate_response_time_percentile(
&request.response_times,
request.response_time_counter,
request.min_response_time,
request.max_response_time,
0.999
),
)?;
}
if self.requests.len() > 1 {
writeln!(
fmt,
" -------------------------+--------+--------+--------+--------+--------+-------"
)?;
writeln!(
fmt,
" {:<24} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6} | {:>6}",
"Aggregated",
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.5
),
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.75
),
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.98
),
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.99
),
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.999
),
calculate_response_time_percentile(
&aggregate_response_times,
aggregate_response_time_counter,
aggregate_min_response_time,
aggregate_max_response_time,
0.9999
),
)?;
}
Ok(())
}
pub fn fmt_status_codes(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.display_status_codes {
return Ok(());
}
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
writeln!(fmt, " {:<24} | {:>51} ", "Name", "Status codes")?;
writeln!(
fmt,
" ------------------------------------------------------------------------------"
)?;
let mut aggregated_status_code_counts: HashMap<u16, usize> = HashMap::new();
for (request_key, request) in self.requests.iter().sorted() {
let mut codes: String = "".to_string();
for (status_code, count) in &request.status_code_counts {
if codes.is_empty() {
codes = format!(
"{} [{}]",
count.to_formatted_string(&Locale::en),
status_code
);
} else {
codes = format!(
"{}, {} [{}]",
codes.clone(),
count.to_formatted_string(&Locale::en),
status_code
);
}
let new_count;
if let Some(existing_status_code_count) =
aggregated_status_code_counts.get(&status_code)
{
new_count = *existing_status_code_count + *count;
} else {
new_count = *count;
}
aggregated_status_code_counts.insert(*status_code, new_count);
}
writeln!(
fmt,
" {:<24} | {:>51}",
util::truncate_string(&request_key, 24),
codes,
)?;
}
writeln!(
fmt,
" -------------------------+----------------------------------------------------"
)?;
let mut codes: String = "".to_string();
for (status_code, count) in &aggregated_status_code_counts {
if codes.is_empty() {
codes = format!(
"{} [{}]",
count.to_formatted_string(&Locale::en),
status_code
);
} else {
codes = format!(
"{}, {} [{}]",
codes.clone(),
count.to_formatted_string(&Locale::en),
status_code
);
}
}
writeln!(fmt, " {:<24} | {:>51} ", "Aggregated", codes)?;
Ok(())
}
}
impl fmt::Display for GooseMetrics {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.fmt_tasks(fmt)?;
self.fmt_task_times(fmt)?;
self.fmt_requests(fmt)?;
self.fmt_response_times(fmt)?;
self.fmt_percentiles(fmt)?;
self.fmt_status_codes(fmt)
}
}
fn per_second_calculations(duration: usize, total: usize, fail: usize) -> (f32, f32) {
let requests_per_second;
let fails_per_second;
if duration == 0 {
requests_per_second = 0.0;
fails_per_second = 0.0;
} else {
requests_per_second = total as f32 / duration as f32;
fails_per_second = fail as f32 / duration as f32;
}
(requests_per_second, fails_per_second)
}
fn determine_precision(value: f32) -> usize {
if value < 1000.0 {
2
} else {
0
}
}
fn format_number(number: usize) -> String {
(number).to_formatted_string(&Locale::en)
}
pub fn merge_times(
mut global_response_times: BTreeMap<usize, usize>,
local_response_times: BTreeMap<usize, usize>,
) -> BTreeMap<usize, usize> {
for (response_time, count) in &local_response_times {
let counter = match global_response_times.get(&response_time) {
Some(c) => *c + count,
None => *count,
};
global_response_times.insert(*response_time, counter);
}
global_response_times
}
pub fn update_min_time(mut global_min: usize, min: usize) -> usize {
if global_min == 0 || (min > 0 && min < global_min) {
global_min = min;
}
global_min
}
pub fn update_max_time(mut global_max: usize, max: usize) -> usize {
if global_max < max {
global_max = max;
}
global_max
}
fn calculate_response_time_percentile(
response_times: &BTreeMap<usize, usize>,
total_requests: usize,
min: usize,
max: usize,
percent: f32,
) -> String {
let percentile_request = (total_requests as f32 * percent).round() as usize;
debug!(
"percentile: {}, request {} of total {}",
percent, percentile_request, total_requests
);
let mut total_count: usize = 0;
for (value, counter) in response_times {
total_count += counter;
if total_count >= percentile_request {
if *value < min {
return format_number(min);
} else if *value > max {
return format_number(max);
} else {
return format_number(*value);
}
}
}
format_number(0)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn max_response_time() {
let mut max_response_time = 99;
max_response_time = update_max_time(max_response_time, 101);
assert_eq!(max_response_time, 101);
max_response_time = update_max_time(max_response_time, 1);
assert_eq!(max_response_time, 101);
}
#[test]
fn min_response_time() {
let mut min_response_time = 11;
min_response_time = update_min_time(min_response_time, 9);
assert_eq!(min_response_time, 9);
min_response_time = update_min_time(min_response_time, 22);
assert_eq!(min_response_time, 9);
min_response_time = update_min_time(min_response_time, 0);
assert_eq!(min_response_time, 9);
}
#[test]
fn response_time_merge() {
let mut global_response_times: BTreeMap<usize, usize> = BTreeMap::new();
let local_response_times: BTreeMap<usize, usize> = BTreeMap::new();
global_response_times = merge_times(global_response_times, local_response_times.clone());
assert_eq!(&global_response_times, &local_response_times);
}
#[test]
fn max_response_time_percentile() {
let mut response_times: BTreeMap<usize, usize> = BTreeMap::new();
response_times.insert(1, 1);
response_times.insert(2, 1);
response_times.insert(3, 1);
assert!(calculate_response_time_percentile(&response_times, 3, 1, 3, 0.5) == "2");
response_times.insert(3, 2);
assert!(calculate_response_time_percentile(&response_times, 4, 1, 3, 0.5) == "2");
assert!(calculate_response_time_percentile(&response_times, 4, 1, 3, 0.25) == "1");
assert!(calculate_response_time_percentile(&response_times, 4, 1, 3, 0.75) == "3");
assert!(calculate_response_time_percentile(&response_times, 4, 1, 3, 1.0) == "3");
assert!(calculate_response_time_percentile(&response_times, 4, 2, 3, 0.25) == "2");
assert!(calculate_response_time_percentile(&response_times, 4, 1, 2, 0.75) == "2");
response_times.insert(10, 25);
response_times.insert(20, 25);
response_times.insert(30, 25);
response_times.insert(50, 25);
response_times.insert(100, 10);
response_times.insert(200, 1);
assert!(calculate_response_time_percentile(&response_times, 115, 1, 200, 0.9) == "50");
assert!(calculate_response_time_percentile(&response_times, 115, 1, 200, 0.99) == "100");
assert!(calculate_response_time_percentile(&response_times, 115, 1, 200, 0.999) == "200");
}
#[test]
fn calculate_per_second() {
let mut duration = 0;
let mut total = 10;
let fail = 10;
let (requests_per_second, fails_per_second) =
per_second_calculations(duration, total, fail);
assert!(requests_per_second == 0.0);
assert!(fails_per_second == 0.0);
total = 100;
let (requests_per_second, fails_per_second) =
per_second_calculations(duration, total, fail);
assert!(requests_per_second == 0.0);
assert!(fails_per_second == 0.0);
duration = 10;
let (requests_per_second, fails_per_second) =
per_second_calculations(duration, total, fail);
assert!((requests_per_second - 10.0).abs() < f32::EPSILON);
assert!((fails_per_second - 1.0).abs() < f32::EPSILON);
}
}