Skip to content

Commit

Permalink
chore: implement number format for delta values too
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Aug 28, 2024
1 parent 362de7d commit e089265
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 39 deletions.
10 changes: 9 additions & 1 deletion src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3360,7 +3360,15 @@ fn determine_precision(value: f32) -> usize {

/// Format large number in locale appropriate style.
pub(crate) fn format_number(number: usize) -> String {
(number).to_formatted_string(&Locale::en)
number.to_formatted_string(&Locale::en)
}

/// Format large value in locale appropriate style.
pub(crate) fn format_value<T>(value: &Value<T>) -> String
where
T: DeltaValue<Delta: ToFormattedString> + ToFormattedString,
{
value.formatted_number(&Locale::en)
}

/// A helper function that merges together times.
Expand Down
96 changes: 82 additions & 14 deletions src/metrics/delta.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use num_format::ToFormattedStr;
use num_format::{Format, ToFormattedString};
use std::fmt::{Debug, Display, Formatter, Write};

/// A value that can be used to provide a delta
///
/// As the actual value can be an unsigned type, we require an associated type which defines the
/// type of the delta.
pub trait DeltaValue: Copy + Debug + Display {
type Delta: Copy + Display;

/// Generate the delta between this and the provided value
fn delta(self, value: Self) -> Self::Delta;

/// It's positive if it's not negative or zero
Expand All @@ -23,7 +28,7 @@ impl DeltaValue for usize {
if delta > 9223372036854775808
/* the absolute value of isize::MIN as usize */
{
// ... which is too big to fix into the negative space of isize, so we limit to isize::MIN
// ... which is too big to fit into the negative space of isize, so we limit to isize::MIN
isize::MIN
} else {
// ... which fits, so we return the negative value
Expand All @@ -49,6 +54,7 @@ impl DeltaValue for f32 {
}
}

/// A value, being either a plain value of a value with delta to a baseline
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub(crate) enum Value<T: DeltaValue> {
Expand Down Expand Up @@ -81,6 +87,29 @@ impl<T: DeltaValue> Value<T> {
}
}

impl<T> Value<T>
where
T: DeltaValue<Delta: ToFormattedString> + ToFormattedString,
{
pub fn formatted_number(&self, format: &impl Format) -> String {
match self {
Self::Plain(value) => value.to_formatted_string(format),
Self::Delta { value, delta } => {
let s = if T::is_delta_positive(*delta) {
"+"
} else {
""
};
format!(
"{} ({s}{})",
value.to_formatted_string(format),
delta.to_formatted_string(format)
)
}
}
}
}

impl<T: DeltaValue> DeltaEval<T> for Value<T> {
fn eval(&mut self, other: Self) {
self.diff(other.value())
Expand Down Expand Up @@ -141,22 +170,11 @@ pub trait DeltaTo {
fn delta_to(&mut self, other: &Self);
}

pub struct Formatted<T>(pub T);

impl<T: ToFormattedStr> Display for Formatted<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use num_format::{Locale, ToFormattedString};

f.write_str(&self.0.to_formatted_string(&Locale::en))?;

Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::metrics::Value;
use num_format::Locale;

#[test]
fn eval_optional() {
Expand All @@ -181,4 +199,54 @@ mod test {
None
);
}

#[test]
fn delta_to_string() {
assert_eq!(format!("{}", 0.delta(10)), "-10");
assert_eq!(format!("{}", 10.delta(10)), "0");
assert_eq!(format!("{}", 10.delta(0)), "10");
}

#[test]
fn value_to_string() {
fn value<T: DeltaValue>(value: T, baseline: T) -> Value<T> {
let mut result = Value::from(value);
result.diff(baseline);
result
}

assert_eq!(format!("{}", value(0, 1000)), "0 (-1000)");
assert_eq!(format!("{}", value(1000, 1000)), "1000 (0)");
assert_eq!(format!("{}", value(1000, 0)), "1000 (+1000)");
}

#[test]
fn value_with_delta_to_string_num() {
fn value<T: DeltaValue>(value: T, baseline: T) -> Value<T> {
let mut result = Value::from(value);
result.diff(baseline);
result
}

assert_eq!(
format!("{}", value(0, 1000).formatted_number(&Locale::en)),
"0 (-1,000)"
);
assert_eq!(
format!("{}", value(1000, 1000).formatted_number(&Locale::en)),
"1,000 (0)"
);
assert_eq!(
format!("{}", value(1000, 0).formatted_number(&Locale::en)),
"1,000 (+1,000)"
);
}

#[test]
fn value_to_string_num() {
assert_eq!(
format!("{}", Value::from(1000).formatted_number(&Locale::en)),
"1,000"
);
}
}
44 changes: 22 additions & 22 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ mod markdown;

pub(crate) use markdown::write_markdown_report;

use crate::goose::GooseMethod;
use crate::{
metrics::{self, DeltaEval, DeltaTo, Value},
goose::GooseMethod,
metrics::{self, format_value, DeltaEval, DeltaTo, Value},
report::common::OrEmpty,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -255,14 +255,14 @@ pub(crate) fn response_metrics_row(metric: ResponseMetric) -> String {
</tr>"#,
method = metric.method,
name = metric.name,
percentile_50 = metric.percentile_50,
percentile_60 = metric.percentile_60,
percentile_70 = metric.percentile_70,
percentile_80 = metric.percentile_80,
percentile_90 = metric.percentile_90,
percentile_95 = metric.percentile_95,
percentile_99 = metric.percentile_99,
percentile_100 = metric.percentile_100,
percentile_50 = format_value(&metric.percentile_50),
percentile_60 = format_value(&metric.percentile_60),
percentile_70 = format_value(&metric.percentile_70),
percentile_80 = format_value(&metric.percentile_80),
percentile_90 = format_value(&metric.percentile_90),
percentile_95 = format_value(&metric.percentile_95),
percentile_99 = format_value(&metric.percentile_99),
percentile_100 = format_value(&metric.percentile_100),
)
}

Expand Down Expand Up @@ -358,14 +358,14 @@ pub(crate) fn coordinated_omission_response_metrics_row(metric: ResponseMetric)
</tr>"#,
method = metric.method,
name = metric.name,
percentile_50 = metric.percentile_50,
percentile_60 = metric.percentile_60,
percentile_70 = metric.percentile_70,
percentile_80 = metric.percentile_80,
percentile_90 = metric.percentile_90,
percentile_95 = metric.percentile_95,
percentile_99 = metric.percentile_99,
percentile_100 = metric.percentile_100,
percentile_50 = format_value(&metric.percentile_50),
percentile_60 = format_value(&metric.percentile_60),
percentile_70 = format_value(&metric.percentile_70),
percentile_80 = format_value(&metric.percentile_80),
percentile_90 = format_value(&metric.percentile_90),
percentile_95 = format_value(&metric.percentile_95),
percentile_99 = format_value(&metric.percentile_99),
percentile_100 = format_value(&metric.percentile_100),
)
}

Expand Down Expand Up @@ -460,8 +460,8 @@ pub(crate) fn transaction_metrics_row(metric: TransactionMetric) -> String {
</tr>"#,
transaction = metric.transaction,
name = metric.name,
number_of_requests = metric.number_of_requests,
number_of_failures = metric.number_of_failures,
number_of_requests = format_value(&metric.number_of_requests),
number_of_failures = format_value(&metric.number_of_failures),
response_time_average = OrEmpty(metric.response_time_average),
response_time_minimum = metric.response_time_minimum,
response_time_maximum = metric.response_time_maximum,
Expand Down Expand Up @@ -516,8 +516,8 @@ pub(crate) fn scenario_metrics_row(metric: ScenarioMetric) -> String {
<td>{iterations:.2}</td>
</tr>"#,
name = metric.name,
users = metric.users,
count = metric.count,
users = format_value(&metric.users),
count = format_value(&metric.count),
response_time_average = metric.response_time_average,
response_time_minimum = metric.response_time_minimum,
response_time_maximum = metric.response_time_maximum,
Expand Down
25 changes: 23 additions & 2 deletions src/report/common.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use num_format::{Locale, ToFormattedString};
use std::fmt::{Display, Formatter};

#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -12,14 +13,34 @@ impl<T: Display> Display for OrEmpty<T> {
}
}

pub struct FormattedNumber<T>(pub T)
where
T: ToFormattedString;

impl<T> Display for FormattedNumber<T>
where
T: ToFormattedString,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_formatted_string(&Locale::en))
}
}

#[cfg(test)]
mod test {
use crate::report::common::OrEmpty;
use crate::report::common::{FormattedNumber, OrEmpty};

#[test]
pub fn format() {
pub fn format_or_empty() {
assert_eq!("1.23", format!("{:.2}", OrEmpty(Some(1.23456))));
assert_eq!("1", format!("{:.0}", OrEmpty(Some(1.23456))));
assert_eq!("", format!("{:.2}", OrEmpty::<f32>(None)));
}

#[test]
pub fn format_number_format() {
assert_eq!("1", format!("{:.2}", FormattedNumber(1)));
assert_eq!("1,000", format!("{:.2}", FormattedNumber(1000)));
assert_eq!("1,000,000", format!("{:.2}", FormattedNumber(1000000)));
}
}
9 changes: 9 additions & 0 deletions src/report/markdown.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::metrics::format_value;
use crate::{
metrics::ReportData,
report::{
Expand Down Expand Up @@ -161,6 +162,14 @@ impl<'m, 'w, W: Write> Markdown<'m, 'w, W> {
writeln!(
self.w,
r#"| {method} | {name} | {percentile_50} | {percentile_60 } | {percentile_70 } | {percentile_80} | {percentile_90} | {percentile_95} | {percentile_99} | {percentile_100} |"#,
percentile_50 = format_value(percentile_50),
percentile_60 = format_value(percentile_60),
percentile_70 = format_value(percentile_70),
percentile_80 = format_value(percentile_80),
percentile_90 = format_value(percentile_90),
percentile_95 = format_value(percentile_95),
percentile_99 = format_value(percentile_99),
percentile_100 = format_value(percentile_100),
)?;
}

Expand Down

0 comments on commit e089265

Please sign in to comment.