diff --git a/src/api_client.rs b/src/api_client.rs index 9e33b874..4fa926a7 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -1,5 +1,6 @@ use std::fmt::Display; +use crate::executor::ExecutorName; use crate::prelude::*; use crate::run_environment::RepositoryProvider; use crate::{app::Cli, config::CodSpeedConfig}; @@ -138,13 +139,14 @@ pub struct FetchLocalRunReportHeadReport { #[derive(Debug, Deserialize, Serialize)] pub struct FetchLocalRunBenchmarkResult { - pub time: f64, + pub value: f64, pub benchmark: FetchLocalRunBenchmark, } #[derive(Debug, Deserialize, Serialize)] pub struct FetchLocalRunBenchmark { pub name: String, + pub executor: ExecutorName, } nest! { diff --git a/src/exec/poll_results.rs b/src/exec/poll_results.rs index cbf02ee8..1f865f81 100644 --- a/src/exec/poll_results.rs +++ b/src/exec/poll_results.rs @@ -6,7 +6,7 @@ use crate::api_client::{ }; use crate::prelude::*; use crate::run::helpers::poll_results::{ - POLLING_INTERVAL, RUN_PROCESSING_MAX_DURATION, build_benchmark_table, + POLLING_INTERVAL, RUN_PROCESSING_MAX_DURATION, build_benchmark_table, build_detailed_summary, }; use crate::run::uploader::UploadResult; @@ -59,8 +59,13 @@ pub async fn poll_results( if !response.run.results.is_empty() { start_group!("Benchmark results"); - let table = build_benchmark_table(&response.run.results); - info!("\n{table}"); + if response.run.results.len() == 1 { + let summary = build_detailed_summary(&response.run.results[0]); + info!("\n{summary}"); + } else { + let table = build_benchmark_table(&response.run.results); + info!("\n{table}"); + } end_group!(); } diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql index 73e950c0..65fe5330 100644 --- a/src/queries/FetchLocalRunReport.gql +++ b/src/queries/FetchLocalRunReport.gql @@ -4,7 +4,7 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { allowedRegression } run(id: $runId) { - id + id status url headReports { @@ -13,9 +13,10 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { conclusion } results { - time + value benchmark { name + executor } } } diff --git a/src/run/helpers/format_memory.rs b/src/run/helpers/format_memory.rs new file mode 100644 index 00000000..eac88a50 --- /dev/null +++ b/src/run/helpers/format_memory.rs @@ -0,0 +1,84 @@ +const BASE: f64 = 1024.0; +const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"]; + +fn get_unit_index(bytes: f64) -> usize { + if bytes == 0.0 { + return 0; + } + let index = (bytes.ln() / BASE.ln()).floor() as usize; + index.min(UNITS.len() - 1) +} + +fn format_shifted_value(value: f64, fraction_digits: usize) -> String { + let formatted_value = format!("{value:.fraction_digits$}"); + + if fraction_digits > 0 { + formatted_value + .trim_end_matches('0') + .trim_end_matches('.') + .to_owned() + } else { + formatted_value + } +} + +pub(crate) fn format_memory(bytes: f64, fraction_digits: Option) -> String { + let fraction_digits = fraction_digits.unwrap_or(1); // Default to 1 decimal place + + if bytes == 0.0 { + return "0 B".to_string(); + } + + let unit_index = get_unit_index(bytes); + let unit = UNITS[unit_index]; + let shifted_value = bytes / BASE.powi(unit_index as i32); + + format!( + "{} {}", + format_shifted_value(shifted_value, fraction_digits), + unit + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_memory_bytes() { + assert_eq!(format_memory(100.0, None), "100 B"); + assert_eq!(format_memory(100.0, Some(0)), "100 B"); + assert_eq!(format_memory(123.45, Some(2)), "123.45 B"); + } + + #[test] + fn test_format_memory_kilobytes() { + assert_eq!(format_memory(1024.0, None), "1 KB"); + assert_eq!(format_memory(1024.0, Some(0)), "1 KB"); + assert_eq!(format_memory(1536.0, Some(1)), "1.5 KB"); + assert_eq!(format_memory(1263.0, Some(2)), "1.23 KB"); + } + + #[test] + fn test_format_memory_megabytes() { + assert_eq!(format_memory(1048576.0, None), "1 MB"); // 1024^2 + assert_eq!(format_memory(1258291.0, Some(2)), "1.2 MB"); + } + + #[test] + fn test_format_memory_gigabytes() { + assert_eq!(format_memory(1073741824.0, None), "1 GB"); // 1024^3 + assert_eq!(format_memory(1288490188.8, Some(2)), "1.2 GB"); + } + + #[test] + fn test_format_memory_zero() { + assert_eq!(format_memory(0.0, None), "0 B"); + } + + #[test] + fn test_format_memory_no_trailing_zeros() { + assert_eq!(format_memory(2048.0, None), "2 KB"); // Should be "2 KB" not "2.0 KB" + assert_eq!(format_memory(3072.0, None), "3 KB"); // Should be "3 KB" not "3.0 KB" + } +} diff --git a/src/run/helpers/mod.rs b/src/run/helpers/mod.rs index 4ef10b7d..faa1ac4e 100644 --- a/src/run/helpers/mod.rs +++ b/src/run/helpers/mod.rs @@ -1,6 +1,7 @@ mod download_file; mod find_repository_root; mod format_duration; +mod format_memory; mod get_env_var; mod parse_git_remote; pub(crate) mod poll_results; @@ -8,5 +9,6 @@ pub(crate) mod poll_results; pub(crate) use download_file::download_file; pub(crate) use find_repository_root::find_repository_root; pub(crate) use format_duration::format_duration; +pub(crate) use format_memory::format_memory; pub(crate) use get_env_var::get_env_variable; pub(crate) use parse_git_remote::*; diff --git a/src/run/helpers/poll_results.rs b/src/run/helpers/poll_results.rs index a3a4fc26..9f81d6e0 100644 --- a/src/run/helpers/poll_results.rs +++ b/src/run/helpers/poll_results.rs @@ -1,3 +1,5 @@ +use crate::api_client::FetchLocalRunBenchmarkResult; +use crate::executor::ExecutorName; use crate::run::helpers; use std::time::Duration; use tabled::settings::Style; @@ -6,32 +8,45 @@ use tabled::{Table, Tabled}; pub const RUN_PROCESSING_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 minutes pub const POLLING_INTERVAL: Duration = Duration::from_secs(1); +fn format_measurement(value: f64, executor: &ExecutorName) -> String { + match executor { + ExecutorName::Memory => helpers::format_memory(value, Some(1)), + ExecutorName::Valgrind | ExecutorName::WallTime => helpers::format_duration(value, Some(2)), + } +} + #[derive(Tabled)] struct BenchmarkRow { #[tabled(rename = "Benchmark")] name: String, - #[tabled(rename = "Time")] - time: String, + #[tabled(rename = "Measurement")] + measurement: String, } -pub fn build_benchmark_table( - results: &[crate::api_client::FetchLocalRunBenchmarkResult], -) -> String { +pub fn build_benchmark_table(results: &[FetchLocalRunBenchmarkResult]) -> String { let table_rows: Vec = results .iter() .map(|result| BenchmarkRow { name: result.benchmark.name.clone(), - time: helpers::format_duration(result.time, Some(2)), + measurement: format_measurement(result.value, &result.benchmark.executor), }) .collect(); Table::new(&table_rows).with(Style::modern()).to_string() } +pub fn build_detailed_summary(result: &FetchLocalRunBenchmarkResult) -> String { + format!( + "{}: {}", + result.benchmark.name, + format_measurement(result.value, &result.benchmark.executor) + ) +} + #[cfg(test)] mod tests { use super::*; - use crate::api_client::{FetchLocalRunBenchmark, FetchLocalRunBenchmarkResult}; + use crate::{api_client::FetchLocalRunBenchmark, executor::ExecutorName}; #[test] fn test_benchmark_table_formatting() { @@ -39,35 +54,53 @@ mod tests { FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_fast".to_string(), + executor: ExecutorName::Valgrind, }, - time: 0.001234, // 1.23 ms + value: 0.001234, // 1.23 ms }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_slow".to_string(), + executor: ExecutorName::WallTime, }, - time: 1.5678, // 1.57 s + value: 1.5678, // 1.57 s }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { - name: "benchmark_medium".to_string(), + name: "benchmark_memory".to_string(), + executor: ExecutorName::Memory, }, - time: 0.000567, // 567 µs + value: 2097152.0, // 2 MB (2 * 1024^2) }, ]; let table = build_benchmark_table(&results); insta::assert_snapshot!(table, @r###" - ┌──────────────────┬───────────┐ - │ Benchmark │ Time │ - ├──────────────────┼───────────┤ - │ benchmark_fast │ 1.23 ms │ - ├──────────────────┼───────────┤ - │ benchmark_slow │ 1.57 s │ - ├──────────────────┼───────────┤ - │ benchmark_medium │ 567.00 µs │ - └──────────────────┴───────────┘ + ┌──────────────────┬─────────────┐ + │ Benchmark │ Measurement │ + ├──────────────────┼─────────────┤ + │ benchmark_fast │ 1.23 ms │ + ├──────────────────┼─────────────┤ + │ benchmark_slow │ 1.57 s │ + ├──────────────────┼─────────────┤ + │ benchmark_memory │ 2 MB │ + └──────────────────┴─────────────┘ "###); } + + #[test] + fn test_detailed_summary_formatting() { + let result = FetchLocalRunBenchmarkResult { + benchmark: FetchLocalRunBenchmark { + name: "benchmark_fast".to_string(), + executor: ExecutorName::Valgrind, + }, + value: 0.001234, // 1.23 ms + }; + + let summary = build_detailed_summary(&result); + + insta::assert_snapshot!(summary, @"benchmark_fast: 1.23 ms"); + } } diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index 942f0b89..82032608 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -101,7 +101,7 @@ pub async fn poll_results( for result in response.run.results { log_json!(format!( "{{\"event\": \"benchmark_ran\", \"name\": \"{}\", \"time\": \"{}\"}}", - result.benchmark.name, result.time, + result.benchmark.name, result.value )); } }