From fd6ff0226d3fa85faa57cd02f45d219fcf5732f6 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 12 Jan 2026 15:52:45 +0100 Subject: [PATCH 1/3] feat: stop using the deprecated `time` field and use `value` instead --- src/api_client.rs | 2 +- src/queries/FetchLocalRunReport.gql | 4 ++-- src/run/helpers/poll_results.rs | 8 ++++---- src/run/poll_results.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index 9e33b874..2a21fd43 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -138,7 +138,7 @@ pub struct FetchLocalRunReportHeadReport { #[derive(Debug, Deserialize, Serialize)] pub struct FetchLocalRunBenchmarkResult { - pub time: f64, + pub value: f64, pub benchmark: FetchLocalRunBenchmark, } diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql index 73e950c0..6cb725b8 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,7 +13,7 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { conclusion } results { - time + value benchmark { name } diff --git a/src/run/helpers/poll_results.rs b/src/run/helpers/poll_results.rs index a3a4fc26..070b2d3a 100644 --- a/src/run/helpers/poll_results.rs +++ b/src/run/helpers/poll_results.rs @@ -21,7 +21,7 @@ pub fn build_benchmark_table( .iter() .map(|result| BenchmarkRow { name: result.benchmark.name.clone(), - time: helpers::format_duration(result.time, Some(2)), + time: helpers::format_duration(result.value, Some(2)), }) .collect(); @@ -40,19 +40,19 @@ mod tests { benchmark: FetchLocalRunBenchmark { name: "benchmark_fast".to_string(), }, - time: 0.001234, // 1.23 ms + value: 0.001234, // 1.23 ms }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_slow".to_string(), }, - time: 1.5678, // 1.57 s + value: 1.5678, // 1.57 s }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_medium".to_string(), }, - time: 0.000567, // 567 µs + value: 0.000567, // 567 µs }, ]; 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 )); } } From 7807aa533840c081952a6757455ddf1839ed4f03 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 12 Jan 2026 15:53:07 +0100 Subject: [PATCH 2/3] feat: do not draw a table if there is only one benchmark --- src/exec/poll_results.rs | 11 ++++++++--- src/run/helpers/poll_results.rs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) 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/run/helpers/poll_results.rs b/src/run/helpers/poll_results.rs index 070b2d3a..94eb778e 100644 --- a/src/run/helpers/poll_results.rs +++ b/src/run/helpers/poll_results.rs @@ -28,6 +28,14 @@ pub fn build_benchmark_table( Table::new(&table_rows).with(Style::modern()).to_string() } +pub fn build_detailed_summary(result: &crate::api_client::FetchLocalRunBenchmarkResult) -> String { + format!( + "{}: {}", + result.benchmark.name, + helpers::format_duration(result.value, Some(2)) + ) +} + #[cfg(test)] mod tests { use super::*; @@ -70,4 +78,18 @@ mod tests { └──────────────────┴───────────┘ "###); } + + #[test] + fn test_detailed_summary_formatting() { + let result = FetchLocalRunBenchmarkResult { + benchmark: FetchLocalRunBenchmark { + name: "benchmark_fast".to_string(), + }, + value: 0.001234, // 1.23 ms + }; + + let summary = build_detailed_summary(&result); + + insta::assert_snapshot!(summary, @"benchmark_fast: 1.23 ms"); + } } From e9d6f3d4b76cc03c7b421a60aa69fd6fe97919d9 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 12 Jan 2026 17:21:23 +0100 Subject: [PATCH 3/3] feat: handle memory executor when displaying results --- src/api_client.rs | 2 + src/queries/FetchLocalRunReport.gql | 1 + src/run/helpers/format_memory.rs | 84 +++++++++++++++++++++++++++++ src/run/helpers/mod.rs | 2 + src/run/helpers/poll_results.rs | 51 +++++++++++------- 5 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 src/run/helpers/format_memory.rs diff --git a/src/api_client.rs b/src/api_client.rs index 2a21fd43..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}; @@ -145,6 +146,7 @@ pub struct FetchLocalRunBenchmarkResult { #[derive(Debug, Deserialize, Serialize)] pub struct FetchLocalRunBenchmark { pub name: String, + pub executor: ExecutorName, } nest! { diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql index 6cb725b8..65fe5330 100644 --- a/src/queries/FetchLocalRunReport.gql +++ b/src/queries/FetchLocalRunReport.gql @@ -16,6 +16,7 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { 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 94eb778e..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,40 +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.value, 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: &crate::api_client::FetchLocalRunBenchmarkResult) -> String { +pub fn build_detailed_summary(result: &FetchLocalRunBenchmarkResult) -> String { format!( "{}: {}", result.benchmark.name, - helpers::format_duration(result.value, Some(2)) + 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() { @@ -47,35 +54,38 @@ mod tests { FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_fast".to_string(), + executor: ExecutorName::Valgrind, }, value: 0.001234, // 1.23 ms }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_slow".to_string(), + executor: ExecutorName::WallTime, }, value: 1.5678, // 1.57 s }, FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { - name: "benchmark_medium".to_string(), + name: "benchmark_memory".to_string(), + executor: ExecutorName::Memory, }, - value: 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 │ + └──────────────────┴─────────────┘ "###); } @@ -84,6 +94,7 @@ mod tests { let result = FetchLocalRunBenchmarkResult { benchmark: FetchLocalRunBenchmark { name: "benchmark_fast".to_string(), + executor: ExecutorName::Valgrind, }, value: 0.001234, // 1.23 ms };