Skip to content
Open
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ strum_macros = "0.28.0"
syn = { version = "2", features = ["full", "extra-traits"] }
sysinfo = "0.38.4"
tempfile = "3.27.0"
terminal_size = { version = "0.4.4" }
test-case = "3.3.1"
testcontainers-modules = { version = "0.15.0", features = ["postgres", "http_wait"] }
thiserror = "2.0.18"
Expand Down
1 change: 1 addition & 0 deletions core/bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ bytes = { workspace = true }
charming = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
comfy-table = { workspace = true }
figlet-rs = { workspace = true }
futures-util = { workspace = true }
governor = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions core/bench/report/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ license = "Apache-2.0"
[dependencies]
charming = { workspace = true }
colored = { workspace = true }
comfy-table = { workspace = true }
derive-new = { workspace = true }
derive_more = { workspace = true }
human-repr = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sysinfo = { workspace = true }
terminal_size = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }
203 changes: 188 additions & 15 deletions core/bench/report/src/prints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@
* under the License.
*/

use colored::{Color, ColoredString, Colorize};
use colored::{Color, Colorize};
use comfy_table::{Cell, Color as TableColor, ContentArrangement, Table, presets::UTF8_FULL};
use human_repr::HumanCount;
use tracing::info;

use crate::{
actor_kind::ActorKind, benchmark_kind::BenchmarkKind, group_metrics::BenchmarkGroupMetrics,
group_metrics_kind::GroupMetricsKind, report::BenchmarkReport,
actor_kind::ActorKind,
benchmark_kind::BenchmarkKind,
group_metrics::BenchmarkGroupMetrics,
group_metrics_kind::GroupMetricsKind,
report::BenchmarkReport,
utils::{WIDE_LAYOUT_THRESHOLD, get_terminal_width},
};

impl BenchmarkReport {
pub fn print_summary(&self) {
pub fn print_summary(&self, pretty: bool) {
let kind = self.params.benchmark_kind;
let total_messages = format!("{} messages, ", self.total_messages());
let total_size = format!(
Expand Down Expand Up @@ -71,7 +76,7 @@ impl BenchmarkReport {

self.group_metrics
.iter()
.for_each(|s| info!("{}\n", s.formatted_string()));
.for_each(|s| println!("\n{}", s.formatted_string(pretty)));
}

pub fn total_messages(&self) -> u64 {
Expand Down Expand Up @@ -136,23 +141,33 @@ impl BenchmarkReport {
}

impl BenchmarkGroupMetrics {
pub fn formatted_string(&self) -> ColoredString {
pub fn formatted_string(&self, pretty: bool) -> String {
if pretty {
let width = get_terminal_width();
if width >= WIDE_LAYOUT_THRESHOLD {
self.format_wide_layout()
} else {
self.format_narrow_layout()
}
} else {
self.format_original()
}
}

fn format_original(&self) -> String {
let (prefix, color) = match self.summary.kind {
GroupMetricsKind::Producers => ("Producers Results", Color::Green),
GroupMetricsKind::Consumers => ("Consumers Results", Color::Green),
GroupMetricsKind::ProducersAndConsumers => ("Aggregate Results", Color::Red),
GroupMetricsKind::ProducingConsumers => ("Producing Consumer Results", Color::Red),
};

let actor = self.summary.kind.actor();

let total_mb = format!("{:.2}", self.summary.total_throughput_megabytes_per_second);
let total_msg = format!("{:.0}", self.summary.total_throughput_messages_per_second);
let avg_mb = format!(
"{:.2}",
self.summary.average_throughput_megabytes_per_second
);

let p50 = format!("{:.2}", self.summary.average_p50_latency_ms);
let p90 = format!("{:.2}", self.summary.average_p90_latency_ms);
let p95 = format!("{:.2}", self.summary.average_p95_latency_ms);
Expand All @@ -170,11 +185,169 @@ impl BenchmarkGroupMetrics {
);

format!(
"{prefix}: Total throughput: {total_mb} MB/s, {total_msg} messages/s, average throughput per {actor}: {avg_mb} MB/s, \
p50 latency: {p50} ms, p90 latency: {p90} ms, p95 latency: {p95} ms, \
p99 latency: {p99} ms, p999 latency: {p999} ms, p9999 latency: {p9999} ms, average latency: {avg} ms, \
median latency: {median} ms, min: {min} ms, max: {max} ms, std dev: {std_dev} ms, total time: {total_test_time} s"
)
.color(color)
"{prefix}: Total throughput: {total_mb} MB/s, {total_msg} messages/s, average throughput per {actor}: {avg_mb} MB/s, \
p50 latency: {p50} ms, p90 latency: {p90} ms, p95 latency: {p95} ms, \
p99 latency: {p99} ms, p999 latency: {p999} ms, p9999 latency: {p9999} ms, average latency: {avg} ms, \
median latency: {median} ms, min: {min} ms, max: {max} ms, std dev: {std_dev} ms, total time: {total_test_time} s"
)
.color(color)
.to_string()
}

fn format_wide_layout(&self) -> String {
let (prefix, color) = match self.summary.kind {
GroupMetricsKind::Producers => ("Producers Results", TableColor::Green),
GroupMetricsKind::Consumers => ("Consumers Results", TableColor::Green),
GroupMetricsKind::ProducersAndConsumers => ("Aggregate Results", TableColor::Red),
GroupMetricsKind::ProducingConsumers => ("Producing Consumer Results", TableColor::Red),
};
let actor = self.summary.kind.actor();

let mut summary_table = Table::new();
summary_table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic);

summary_table.add_row(vec![
Cell::new(prefix).fg(color),
Cell::new(format!(
"{:.2} s",
self.avg_throughput_mb_ts.points.last().unwrap().time_s
))
.fg(color),
Cell::new(format!(
"{:.2} MB/s",
self.summary.total_throughput_megabytes_per_second
))
.fg(color),
Cell::new(format!(
"{:.0} msg/s",
self.summary.total_throughput_messages_per_second
))
.fg(color),
Cell::new(format!(
"{:.2} MB/s per {}",
self.summary.average_throughput_megabytes_per_second, actor
))
.fg(color),
]);

let mut latency_table = Table::new();
latency_table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic);

latency_table.add_row(vec![
"Latency", "p50", "p90", "p95", "p99", "p999", "p9999", "avg", "median", "min", "max",
"std dev",
]);
latency_table.add_row(vec![
"(ms)".to_string(),
format!("{:.2}", self.summary.average_p50_latency_ms),
format!("{:.2}", self.summary.average_p90_latency_ms),
format!("{:.2}", self.summary.average_p95_latency_ms),
format!("{:.2}", self.summary.average_p99_latency_ms),
format!("{:.2}", self.summary.average_p999_latency_ms),
format!("{:.2}", self.summary.average_p9999_latency_ms),
format!("{:.2}", self.summary.average_latency_ms),
format!("{:.2}", self.summary.average_median_latency_ms),
format!("{:.2}", self.summary.min_latency_ms),
format!("{:.2}", self.summary.max_latency_ms),
format!("{:.2}", self.summary.std_dev_latency_ms),
]);

format!("\n{}\n{}", summary_table, latency_table)
}

fn format_narrow_layout(&self) -> String {
let (prefix, color) = match self.summary.kind {
GroupMetricsKind::Producers => ("Producers Results", TableColor::Green),
GroupMetricsKind::Consumers => ("Consumers Results", TableColor::Green),
GroupMetricsKind::ProducersAndConsumers => ("Aggregate Results", TableColor::Red),
GroupMetricsKind::ProducingConsumers => ("Producing Consumer Results", TableColor::Red),
};
let actor = self.summary.kind.actor();

let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_width(60);

table.add_row(vec![Cell::new(prefix).fg(color), Cell::new("").fg(color)]);

table.add_row(vec!["Summary", ""]);
table.add_row(vec![
"Total Time".to_string(),
format!(
"{:.2} s",
self.avg_throughput_mb_ts.points.last().unwrap().time_s
),
]);

table.add_row(vec!["Throughput", ""]);
table.add_row(vec![
"Total (MB/s)".to_string(),
format!("{:.2}", self.summary.total_throughput_megabytes_per_second),
]);
table.add_row(vec![
"Total (msg/s)".to_string(),
format!("{:.0}", self.summary.total_throughput_messages_per_second),
]);
table.add_row(vec![
format!("Avg per {} (MB/s)", actor),
format!(
"{:.2}",
self.summary.average_throughput_megabytes_per_second
),
]);

table.add_row(vec!["Latency", ""]);
table.add_row(vec![
"p50".to_string(),
format!("{:.2} ms", self.summary.average_p50_latency_ms),
]);
table.add_row(vec![
"p90".to_string(),
format!("{:.2} ms", self.summary.average_p90_latency_ms),
]);
table.add_row(vec![
"p95".to_string(),
format!("{:.2} ms", self.summary.average_p95_latency_ms),
]);
table.add_row(vec![
"p99".to_string(),
format!("{:.2} ms", self.summary.average_p99_latency_ms),
]);
table.add_row(vec![
"p999".to_string(),
format!("{:.2} ms", self.summary.average_p999_latency_ms),
]);
table.add_row(vec![
"p9999".to_string(),
format!("{:.2} ms", self.summary.average_p9999_latency_ms),
]);
table.add_row(vec![
"avg".to_string(),
format!("{:.2} ms", self.summary.average_latency_ms),
]);
table.add_row(vec![
"median".to_string(),
format!("{:.2} ms", self.summary.average_median_latency_ms),
]);
table.add_row(vec![
"min".to_string(),
format!("{:.2} ms", self.summary.min_latency_ms),
]);
table.add_row(vec![
"max".to_string(),
format!("{:.2} ms", self.summary.max_latency_ms),
]);
table.add_row(vec![
"std dev".to_string(),
format!("{:.2} ms", self.summary.std_dev_latency_ms),
]);

format!("\n{}", table)
}
}
9 changes: 9 additions & 0 deletions core/bench/report/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@

use crate::time_series::{TimePoint, TimeSeries};
use serde::Serializer;
use terminal_size::{Width, terminal_size};

/// Terminal width threshold for switching between narrow and wide table layouts
pub const WIDE_LAYOUT_THRESHOLD: u16 = 140;

/// Returns current terminal width in columns
pub fn get_terminal_width() -> u16 {
terminal_size().map(|(Width(w), _)| w).unwrap_or(80)
}

pub(crate) fn round_float<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down
Loading
Loading