From b4296d17585e5b0382b1ee51738014deb8053db8 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Thu, 5 Mar 2026 12:00:44 +0100 Subject: [PATCH 1/4] feat: use repositoryOverview resolver rather than repository This avoids triggering an error if the repository is not enabled --- src/api_client.rs | 4 ++-- src/queries/GetRepository.gql | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index 0dc9015e..f740a583 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -230,7 +230,7 @@ nest! { #[derive(Debug, Deserialize, Serialize)]* #[serde(rename_all = "camelCase")]* struct GetRepositoryData { - repository: Option } @@ -327,7 +327,7 @@ impl CodSpeedAPIClient { ) .await; match response { - Ok(response) => Ok(response.repository), + Ok(response) => Ok(response.repository_overview), Err(err) if err.contains_error_code("REPOSITORY_NOT_FOUND") => Ok(None), Err(err) if err.contains_error_code("UNAUTHENTICATED") => { bail!("Your session has expired, please login again using `codspeed auth login`") diff --git a/src/queries/GetRepository.gql b/src/queries/GetRepository.gql index b4391fdb..c6d547cf 100644 --- a/src/queries/GetRepository.gql +++ b/src/queries/GetRepository.gql @@ -3,7 +3,12 @@ query Repository( $name: String! $provider: RepositoryProvider ) { - repository(owner: $owner, name: $name, provider: $provider) { - id + repositoryOverview(owner: $owner, name: $name, provider: $provider) { + ... on Repository { + id + } + ... on AvailableRepository { + id + } } } From a35637a59487b6372b95772f0102d14da4ec67f8 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Thu, 5 Mar 2026 12:31:20 +0100 Subject: [PATCH 2/4] refactor: consolidate poll_results into upload module Merge the two separate `poll_results` implementations from `cli/exec` and `cli/run` into a single `upload::poll_results` function controlled by `PollResultsOptions`. Co-Authored-By: Claude Sonnet 4.6 --- src/cli/exec/mod.rs | 5 +- src/cli/exec/poll_results.rs | 34 ------------ src/cli/run/mod.rs | 5 +- src/cli/run/poll_results.rs | 69 ----------------------- src/upload/mod.rs | 1 + src/upload/poll_results.rs | 104 +++++++++++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 107 deletions(-) delete mode 100644 src/cli/exec/poll_results.rs delete mode 100644 src/cli/run/poll_results.rs create mode 100644 src/upload/poll_results.rs diff --git a/src/cli/exec/mod.rs b/src/cli/exec/mod.rs index ac6164bd..91159188 100644 --- a/src/cli/exec/mod.rs +++ b/src/cli/exec/mod.rs @@ -7,11 +7,11 @@ use crate::prelude::*; use crate::project_config::ProjectConfig; use crate::project_config::merger::ConfigMerger; use crate::upload::UploadResult; +use crate::upload::poll_results::{PollResultsOptions, poll_results}; use clap::Args; use std::path::Path; pub mod multi_targets; -mod poll_results; /// We temporarily force this name for all exec runs pub const DEFAULT_REPOSITORY_NAME: &str = "local-runs"; @@ -120,8 +120,9 @@ pub async fn execute_with_harness( ) .await?; + let poll_opts = PollResultsOptions::for_exec(); let poll_results_fn = async |upload_result: &UploadResult| { - poll_results::poll_results(api_client, upload_result).await + poll_results(api_client, upload_result, &poll_opts).await }; executor::execute_benchmarks( diff --git a/src/cli/exec/poll_results.rs b/src/cli/exec/poll_results.rs deleted file mode 100644 index 1812bf73..00000000 --- a/src/cli/exec/poll_results.rs +++ /dev/null @@ -1,34 +0,0 @@ -use console::style; - -use crate::api_client::CodSpeedAPIClient; -use crate::cli::run::helpers::benchmark_display::{build_benchmark_table, build_detailed_summary}; -use crate::prelude::*; -use crate::upload::{UploadResult, poll_run_report}; - -#[allow(clippy::borrowed_box)] -pub async fn poll_results( - api_client: &CodSpeedAPIClient, - upload_result: &UploadResult, -) -> Result<()> { - let response = poll_run_report(api_client, upload_result).await?; - - if !response.run.results.is_empty() { - end_group!(); - start_group!("Benchmark results"); - - 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}"); - } - - info!( - "\nTo see the full report, visit: {}", - style(response.run.url).blue().bold().underlined() - ); - } - - Ok(()) -} diff --git a/src/cli/run/mod.rs b/src/cli/run/mod.rs index e4dc2125..55d4afe0 100644 --- a/src/cli/run/mod.rs +++ b/src/cli/run/mod.rs @@ -8,12 +8,12 @@ use crate::project_config::ProjectConfig; use crate::project_config::merger::ConfigMerger; use crate::run_environment::interfaces::RepositoryProvider; use crate::upload::UploadResult; +use crate::upload::poll_results::{PollResultsOptions, poll_results}; use clap::{Args, ValueEnum}; use std::path::Path; pub mod helpers; pub mod logger; -mod poll_results; #[derive(Args, Debug)] pub struct RunArgs { @@ -155,8 +155,9 @@ pub async fn run( // Execute benchmarks let executor = executor::get_executor_from_mode(&execution_context.config.mode); + let poll_opts = PollResultsOptions::for_run(output_json); let poll_results_fn = async |upload_result: &UploadResult| { - poll_results::poll_results(api_client, upload_result, output_json).await + poll_results(api_client, upload_result, &poll_opts).await }; executor::execute_benchmarks( executor.as_ref(), diff --git a/src/cli/run/poll_results.rs b/src/cli/run/poll_results.rs deleted file mode 100644 index beb8220b..00000000 --- a/src/cli/run/poll_results.rs +++ /dev/null @@ -1,69 +0,0 @@ -use console::style; - -use crate::api_client::CodSpeedAPIClient; -use crate::cli::run::helpers::benchmark_display::build_benchmark_table; -use crate::prelude::*; -use crate::upload::{UploadResult, poll_run_report}; - -#[allow(clippy::borrowed_box)] -pub async fn poll_results( - api_client: &CodSpeedAPIClient, - upload_result: &UploadResult, - output_json: bool, -) -> Result<()> { - let response = poll_run_report(api_client, upload_result).await?; - - let report = response.run.head_reports.into_iter().next(); - - if let Some(report) = report { - if let Some(impact) = report.impact { - let rounded_impact = (impact * 100.0).round(); - let impact_text = if impact > 0.0 { - style(format!("+{rounded_impact}%")).green().bold() - } else { - style(format!("{rounded_impact}%")).red().bold() - }; - - info!( - "Impact: {} (allowed regression: -{}%)", - impact_text, - (response.allowed_regression * 100.0).round() - ); - } else { - info!("No impact detected, reason: {}", report.conclusion); - } - } - - if output_json { - // TODO: Refactor `log_json` to avoid having to format the json manually - // We could make use of structured logging for this https://docs.rs/log/latest/log/#structured-logging - log_json!(format!( - "{{\"event\": \"run_finished\", \"run_id\": \"{}\"}}", - upload_result.run_id - )); - } - - if !response.run.results.is_empty() { - end_group!(); - start_group!("Benchmark results"); - - let table = build_benchmark_table(&response.run.results); - info!("\n{table}"); - - if output_json { - for result in response.run.results { - log_json!(format!( - "{{\"event\": \"benchmark_ran\", \"name\": \"{}\", \"time\": \"{}\"}}", - result.benchmark.name, result.value - )); - } - } - - info!( - "\nTo see the full report, visit: {}", - style(response.run.url).blue().bold().underlined() - ); - } - - Ok(()) -} diff --git a/src/upload/mod.rs b/src/upload/mod.rs index 9208ba35..9ba5d14d 100644 --- a/src/upload/mod.rs +++ b/src/upload/mod.rs @@ -1,4 +1,5 @@ mod interfaces; +pub mod poll_results; mod polling; mod profile_archive; mod run_index_state; diff --git a/src/upload/poll_results.rs b/src/upload/poll_results.rs new file mode 100644 index 00000000..e1ed7d39 --- /dev/null +++ b/src/upload/poll_results.rs @@ -0,0 +1,104 @@ +use console::style; + +use crate::api_client::CodSpeedAPIClient; +use crate::cli::run::helpers::benchmark_display::{build_benchmark_table, build_detailed_summary}; +use crate::prelude::*; + +use super::{UploadResult, poll_run_report}; + +/// Options controlling poll_results display behavior. +pub struct PollResultsOptions { + /// If true, show impact percentage (used by `codspeed run`) + pub show_impact: bool, + /// If true, output JSON events (used by `codspeed run --message-format json`) + pub output_json: bool, + /// If true, show detailed summary for single benchmark result (used by `codspeed exec`) + pub detailed_single: bool, +} + +impl PollResultsOptions { + /// Options for `codspeed run` + pub fn for_run(output_json: bool) -> Self { + Self { + show_impact: true, + output_json, + detailed_single: false, + } + } + + /// Options for `codspeed exec` + pub fn for_exec() -> Self { + Self { + show_impact: false, + output_json: false, + detailed_single: true, + } + } +} + +pub async fn poll_results( + api_client: &CodSpeedAPIClient, + upload_result: &UploadResult, + options: &PollResultsOptions, +) -> Result<()> { + let response = poll_run_report(api_client, upload_result).await?; + + if options.show_impact { + let report = response.run.head_reports.into_iter().next(); + if let Some(report) = report { + if let Some(impact) = report.impact { + let rounded_impact = (impact * 100.0).round(); + let impact_text = if impact > 0.0 { + style(format!("+{rounded_impact}%")).green().bold() + } else { + style(format!("{rounded_impact}%")).red().bold() + }; + + info!( + "Impact: {} (allowed regression: -{}%)", + impact_text, + (response.allowed_regression * 100.0).round() + ); + } else { + info!("No impact detected, reason: {}", report.conclusion); + } + } + } + + if options.output_json { + log_json!(format!( + "{{\"event\": \"run_finished\", \"run_id\": \"{}\"}}", + upload_result.run_id + )); + } + + if !response.run.results.is_empty() { + end_group!(); + start_group!("Benchmark results"); + + if options.detailed_single && 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}"); + } + + if options.output_json { + for result in &response.run.results { + log_json!(format!( + "{{\"event\": \"benchmark_ran\", \"name\": \"{}\", \"time\": \"{}\"}}", + result.benchmark.name, result.value + )); + } + } + + info!( + "\nTo see the full report, visit: {}", + style(response.run.url).blue().bold().underlined() + ); + end_group!(); + } + + Ok(()) +} From 4449281ece4fe41d0b5a35589250e159c84e0c95 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Thu, 5 Mar 2026 12:34:03 +0100 Subject: [PATCH 3/4] feat: add local_data to upload metadata Add a `local_data` field to `RunEnvironmentMetadata` to carry local-run specific information (expected run parts count). Also populate `run_part` for local runs with a stable run_id and per-executor run_part_id. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 196 +++++++++++++++++- Cargo.toml | 1 + src/run_environment/buildkite/provider.rs | 1 + ...pull_request_run_environment_metadata.snap | 1 + .../github_actions/provider.rs | 1 + ...pull_request_run_environment_metadata.snap | 1 + ...__matrix_job_run_environment_metadata.snap | 1 + ...pull_request_run_environment_metadata.snap | 1 + src/run_environment/gitlab_ci/provider.rs | 1 + ...erge_request_run_environment_metadata.snap | 1 + ...erge_request_run_environment_metadata.snap | 1 + ...s__push_main_run_environment_metadata.snap | 1 + src/run_environment/interfaces.rs | 7 + src/run_environment/local/provider.rs | 26 ++- src/upload/interfaces.rs | 2 +- ...ata__tests__get_local_metadata_hash-2.snap | 49 +++++ ..._metadata__tests__get_metadata_hash-2.snap | 3 +- src/upload/upload_metadata.rs | 69 +++++- 18 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap diff --git a/Cargo.lock b/Cargo.lock index 29174528..76b54c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -615,6 +615,7 @@ dependencies = [ "tokio", "tokio-util", "url", + "uuid", ] [[package]] @@ -963,6 +964,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1119,8 +1126,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -1213,6 +1233,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1532,6 +1561,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1561,6 +1596,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1745,6 +1782,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libbpf-cargo" version = "0.25.0" @@ -2614,6 +2657,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -4143,6 +4192,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4187,11 +4242,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -4270,7 +4325,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -4331,6 +4395,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -4359,6 +4445,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.83" @@ -4959,6 +5057,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.111", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.111", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index e5453f71..4b312705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ open = "5.3.2" tabled = { version = "0.20.0", features = ["ansi"] } shell-words = "1.1.0" rmp-serde = "1.3.0" +uuid = { version = "1.21.0", features = ["v4"] } [target.'cfg(target_os = "linux")'.dependencies] procfs = "0.17.0" diff --git a/src/run_environment/buildkite/provider.rs b/src/run_environment/buildkite/provider.rs index bb4ad17c..b9f32e01 100644 --- a/src/run_environment/buildkite/provider.rs +++ b/src/run_environment/buildkite/provider.rs @@ -142,6 +142,7 @@ impl RunEnvironmentProvider for BuildkiteProvider { repository_root_path: self.repository_root_path.clone(), gh_data: None, gl_data: None, + local_data: None, sender: None, }) } diff --git a/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap index 8ddaec7d..95c04c63 100644 --- a/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap +++ b/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap @@ -12,5 +12,6 @@ expression: run_environment_metadata "sender": null, "ghData": null, "glData": null, + "localData": null, "repositoryRootPath": "/buildkite/builds/7b10eca7600b-1/my-org/buildkite-test/" } diff --git a/src/run_environment/github_actions/provider.rs b/src/run_environment/github_actions/provider.rs index d53a6493..6db406ce 100644 --- a/src/run_environment/github_actions/provider.rs +++ b/src/run_environment/github_actions/provider.rs @@ -179,6 +179,7 @@ impl RunEnvironmentProvider for GitHubActionsProvider { event: self.event.clone(), gh_data: Some(self.gh_data.clone()), gl_data: None, + local_data: None, sender: self.sender.clone(), owner: self.owner.clone(), repository: self.repository.clone(), diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap index 777a9791..c721764f 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "job": "log-env" }, "glData": null, + "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap index eb4a24de..b6a8868b 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "job": "log-env" }, "glData": null, + "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap index eb4a24de..b6a8868b 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "job": "log-env" }, "glData": null, + "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/gitlab_ci/provider.rs b/src/run_environment/gitlab_ci/provider.rs index ba741a3c..a78de026 100644 --- a/src/run_environment/gitlab_ci/provider.rs +++ b/src/run_environment/gitlab_ci/provider.rs @@ -161,6 +161,7 @@ impl RunEnvironmentProvider for GitLabCIProvider { event: self.event.clone(), gh_data: None, gl_data: Some(self.gl_data.clone()), + local_data: None, sender: Some(self.sender.clone()), owner: self.owner.clone(), repository: self.repository.clone(), diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap index 0256509b..371fe9f2 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "runId": "6957110437", "job": "build-job" }, + "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap index 2b136ecc..4a277013 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "runId": "6957110437", "job": "build-job" }, + "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap index 61616c98..b2a3c4d7 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap @@ -18,5 +18,6 @@ expression: run_environment_metadata "runId": "1234567890", "job": "job" }, + "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/interfaces.rs b/src/run_environment/interfaces.rs index 46e94db6..0f05704e 100644 --- a/src/run_environment/interfaces.rs +++ b/src/run_environment/interfaces.rs @@ -33,6 +33,7 @@ pub struct RunEnvironmentMetadata { pub sender: Option, pub gh_data: Option, pub gl_data: Option, + pub local_data: Option, pub repository_root_path: String, } @@ -60,6 +61,12 @@ pub struct GlData { pub job: String, } +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct LocalData { + pub expected_run_parts_count: u32, +} + /// Each execution of the CLI maps to a `RunPart`. /// /// Several `RunParts` can be aggregated in a single `Run` thanks to this data. diff --git a/src/run_environment/local/provider.rs b/src/run_environment/local/provider.rs index 28c4903a..8ed0b366 100644 --- a/src/run_environment/local/provider.rs +++ b/src/run_environment/local/provider.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use git2::Repository; use simplelog::SharedLogger; +use uuid::Uuid; use crate::api_client::{CodSpeedAPIClient, GetOrCreateProjectRepositoryVars, GetRepositoryVars}; use crate::cli::run::helpers::{GitRemote, find_repository_root, parse_git_remote}; @@ -8,7 +9,9 @@ use crate::executor::config::RepositoryOverride; use crate::executor::{Config, ExecutorName}; use crate::local_logger::get_local_logger; use crate::prelude::*; -use crate::run_environment::interfaces::{RepositoryProvider, RunEnvironmentMetadata, RunEvent}; +use crate::run_environment::interfaces::{ + LocalData, RepositoryProvider, RunEnvironmentMetadata, RunEvent, +}; use crate::run_environment::provider::{RunEnvironmentDetector, RunEnvironmentProvider}; use crate::run_environment::{RunEnvironment, RunPart}; use crate::system::SystemInfo; @@ -25,6 +28,8 @@ pub struct LocalProvider { head_ref: Option, pub event: RunEvent, pub repository_root_path: String, + run_id: String, + expected_run_parts_count: u32, } /// Information about the git repository root path @@ -62,6 +67,8 @@ impl LocalProvider { head_ref: resolved.head_ref, repository_root_path, event: RunEvent::Local, + run_id: Uuid::new_v4().to_string(), + expected_run_parts_count: 1, }) } @@ -237,6 +244,7 @@ impl RunEnvironmentProvider for LocalProvider { event: self.event.clone(), gh_data: None, gl_data: None, + local_data: None, sender: None, owner: self.owner.clone(), repository: self.repository.clone(), @@ -252,7 +260,18 @@ impl RunEnvironmentProvider for LocalProvider { profile_archive: &ProfileArchive, executor_name: ExecutorName, ) -> Result { - let run_environment_metadata = self.get_run_environment_metadata()?; + let mut run_environment_metadata = self.get_run_environment_metadata()?; + + run_environment_metadata.local_data = Some(LocalData { + expected_run_parts_count: self.expected_run_parts_count, + }); + + let run_part = Some(RunPart { + run_id: self.run_id.clone(), + run_part_id: executor_name.to_string(), + job_name: "local-job".into(), + metadata: Default::default(), + }); Ok(UploadMetadata { version: Some(LATEST_UPLOAD_METADATA_VERSION), @@ -271,11 +290,10 @@ impl RunEnvironmentProvider for LocalProvider { system_info: system_info.clone(), }, run_environment: self.get_run_environment(), - run_part: self.get_run_provider_run_part(), + run_part, }) } - /// For local runs have, we cannot really send anything here fn get_run_provider_run_part(&self) -> Option { None } diff --git a/src/upload/interfaces.rs b/src/upload/interfaces.rs index 10cc4ab5..2be3ce98 100644 --- a/src/upload/interfaces.rs +++ b/src/upload/interfaces.rs @@ -5,7 +5,7 @@ use crate::instruments::InstrumentName; use crate::run_environment::{RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunPart}; use crate::system::SystemInfo; -pub const LATEST_UPLOAD_METADATA_VERSION: u32 = 8; +pub const LATEST_UPLOAD_METADATA_VERSION: u32 = 9; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap new file mode 100644 index 00000000..a7002d4f --- /dev/null +++ b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap @@ -0,0 +1,49 @@ +--- +source: src/upload/upload_metadata.rs +expression: upload_metadata +--- +{ + "repositoryProvider": "PROJECT", + "version": 9, + "tokenless": false, + "profileMd5": "tfC4VxYiYdJcTWpHpv4Ouw==", + "profileEncoding": "gzip", + "runner": { + "name": "codspeed-runner", + "version": "4.11.1", + "instruments": [], + "executor": "valgrind", + "os": "nixos", + "osVersion": "25.11", + "arch": "x86_64", + "host": "badlands", + "user": "guillaume", + "cpuBrand": "11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz", + "cpuName": "cpu0", + "cpuVendorId": "GenuineIntel", + "cpuCores": 6, + "totalMemoryGb": 16 + }, + "runEnvironment": "LOCAL", + "runPart": { + "runId": "e0878123-c467-4191-994b-8560d8a7424e", + "runPartId": "valgrind", + "jobName": "local-job", + "metadata": {} + }, + "commitHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "allowEmpty": false, + "ref": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "headRef": null, + "baseRef": null, + "owner": "GuillaumeLagrange", + "repository": "local-runs", + "event": "local", + "sender": null, + "ghData": null, + "glData": null, + "localData": { + "expectedRunPartsCount": 1 + }, + "repositoryRootPath": "/home/guillaume/codspeed/runner/" +} diff --git a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap index 8ba2b114..3cee1ad0 100644 --- a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap +++ b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap @@ -4,7 +4,7 @@ expression: upload_metadata --- { "repositoryProvider": "GITHUB", - "version": 8, + "version": 9, "tokenless": true, "profileMd5": "jp/k05RKuqP3ERQuIIvx4Q==", "profileEncoding": "gzip", @@ -53,5 +53,6 @@ expression: upload_metadata "job": "codspeed" }, "glData": null, + "localData": null, "repositoryRootPath": "/home/runner/work/codspeed-node/codspeed-node/" } diff --git a/src/upload/upload_metadata.rs b/src/upload/upload_metadata.rs index b9561247..418ffbc3 100644 --- a/src/upload/upload_metadata.rs +++ b/src/upload/upload_metadata.rs @@ -18,8 +18,8 @@ mod tests { use crate::executor::ExecutorName; use crate::instruments::InstrumentName; use crate::run_environment::{ - GhData, RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, RunPart, - Sender, + GhData, LocalData, RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, + RunPart, Sender, }; use crate::system::SystemInfo; use crate::upload::{LATEST_UPLOAD_METADATA_VERSION, Runner, UploadMetadata}; @@ -58,6 +58,7 @@ mod tests { login: "adriencaccia".into(), }), gl_data: None, + local_data: None, repository_root_path: "/home/runner/work/codspeed-node/codspeed-node/".into(), }, run_part: Some(RunPart { @@ -76,7 +77,69 @@ mod tests { hash, // Caution: when changing this value, we need to ensure that // the related backend snapshot remains the same - @"11f363bd959389e57c79f6fc7d5c965d168c7b2f3cb2b566b647588b23322013" + @"b2c6175fa81d4c4c5eb215e2e77667891f33abca9f8614b45899e3ee070bdca6" + ); + assert_json_snapshot!(upload_metadata); + } + + #[test] + fn test_get_local_metadata_hash() { + let upload_metadata = UploadMetadata { + repository_provider: RepositoryProvider::Project, + version: Some(LATEST_UPLOAD_METADATA_VERSION), + tokenless: false, + profile_md5: "tfC4VxYiYdJcTWpHpv4Ouw==".into(), + profile_encoding: Some("gzip".into()), + runner: Runner { + name: "codspeed-runner".into(), + version: "4.11.1".into(), + instruments: vec![], + executor: ExecutorName::Valgrind, + system_info: SystemInfo { + os: "nixos".to_string(), + os_version: "25.11".to_string(), + arch: "x86_64".to_string(), + host: "badlands".to_string(), + user: "guillaume".to_string(), + cpu_brand: "11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz".to_string(), + cpu_name: "cpu0".to_string(), + cpu_vendor_id: "GenuineIntel".to_string(), + cpu_cores: 6, + total_memory_gb: 16, + }, + }, + run_environment: RunEnvironment::Local, + commit_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(), + allow_empty: false, + run_environment_metadata: RunEnvironmentMetadata { + ref_: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(), + head_ref: None, + base_ref: None, + owner: "GuillaumeLagrange".into(), + repository: "local-runs".into(), + event: RunEvent::Local, + gh_data: None, + sender: None, + gl_data: None, + local_data: Some(LocalData { + expected_run_parts_count: 1, + }), + repository_root_path: "/home/guillaume/codspeed/runner/".into(), + }, + run_part: Some(RunPart { + run_id: "e0878123-c467-4191-994b-8560d8a7424e".into(), + run_part_id: "valgrind".into(), + job_name: "local-job".into(), + metadata: BTreeMap::new(), + }), + }; + + let hash = upload_metadata.get_hash(); + assert_snapshot!( + hash, + // Caution: when changing this value, we need to ensure that + // the related backend snapshot remains the same + @"47b6317da2747edae177d8a99143efc6f7516beb3222b9d45331ba48d4e1c369" ); assert_json_snapshot!(upload_metadata); } From 79ac61073f9cfaa271a93b17188bf05e5c8729b9 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Thu, 5 Mar 2026 12:36:43 +0100 Subject: [PATCH 4/4] refactor: introduce Orchestrator to support multi-mode execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an Orchestrator struct that owns the shared run-time state (provider, system_info, logger) and drives the full execution lifecycle: setup → run all modes → upload all results → poll. This replaces the monolithic ExecutionContext with a leaner per-mode context, and wires up the mode/shell-session APIs to accept Vec so that multiple modes can be requested in a single CLI invocation. Co-Authored-By: Claude Sonnet 4.6 --- src/cli/exec/mod.rs | 21 ++-- src/cli/run/logger.rs | 9 +- src/cli/run/mod.rs | 26 ++--- src/cli/shared.rs | 31 +++--- src/cli/show.rs | 13 ++- src/cli/use_mode.rs | 8 +- src/executor/config.rs | 29 +++-- src/executor/execution_context.rs | 53 +-------- src/executor/mod.rs | 49 ++------- src/executor/orchestrator.rs | 148 ++++++++++++++++++++++++++ src/executor/tests.rs | 17 +-- src/instruments/mod.rs | 4 +- src/project_config/merger.rs | 10 +- src/run_environment/local/provider.rs | 6 +- src/runner_mode/shell_session.rs | 10 +- src/upload/uploader.rs | 59 ++++------ 16 files changed, 275 insertions(+), 218 deletions(-) create mode 100644 src/executor/orchestrator.rs diff --git a/src/cli/exec/mod.rs b/src/cli/exec/mod.rs index 91159188..3f7f0d01 100644 --- a/src/cli/exec/mod.rs +++ b/src/cli/exec/mod.rs @@ -91,20 +91,19 @@ pub async fn run( /// result polling. It is used by both `codspeed exec` directly and by `codspeed run` when /// executing targets defined in codspeed.yaml. pub async fn execute_with_harness( - config: crate::executor::Config, + mut config: crate::executor::Config, api_client: &CodSpeedAPIClient, codspeed_config: &CodSpeedConfig, setup_cache_dir: Option<&Path>, ) -> Result<()> { - let mut execution_context = - executor::ExecutionContext::new(config, codspeed_config, api_client).await?; + let orchestrator = + executor::Orchestrator::new(&mut config, codspeed_config, api_client).await?; - if !execution_context.is_local() { + if !orchestrator.is_local() { super::show_banner(); } - debug!("config: {:#?}", execution_context.config); - let executor = executor::get_executor_from_mode(&execution_context.config.mode); + debug!("config: {config:#?}"); let get_exec_harness_installer_url = || { format!( @@ -125,13 +124,9 @@ pub async fn execute_with_harness( poll_results(api_client, upload_result, &poll_opts).await }; - executor::execute_benchmarks( - executor.as_ref(), - &mut execution_context, - setup_cache_dir, - poll_results_fn, - ) - .await?; + orchestrator + .execute(&mut config, setup_cache_dir, poll_results_fn) + .await?; Ok(()) } diff --git a/src/cli/run/logger.rs b/src/cli/run/logger.rs index ba8786fc..15be0da0 100644 --- a/src/cli/run/logger.rs +++ b/src/cli/run/logger.rs @@ -1,11 +1,10 @@ -use crate::executor::ExecutionContext; use crate::logger::{GROUP_TARGET, OPENED_GROUP_TARGET}; use crate::prelude::*; use crate::run_environment::RunEnvironmentProvider; use log::LevelFilter; use simplelog::{CombinedLogger, WriteLogger}; use std::fs::copy; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use tempfile::NamedTempFile; pub struct Logger { @@ -34,11 +33,7 @@ impl Logger { Ok(Self { log_file_path }) } - pub fn persist_log_to_profile_folder( - &self, - execution_context: &ExecutionContext, - ) -> Result<()> { - let profile_folder = execution_context.profile_folder.clone(); + pub fn persist_log_to_profile_folder(&self, profile_folder: &Path) -> Result<()> { let dest_log_file_path = profile_folder.join("runner.log"); debug!("Persisting log file to {}", dest_log_file_path.display()); log::logger().flush(); diff --git a/src/cli/run/mod.rs b/src/cli/run/mod.rs index 55d4afe0..358d7d80 100644 --- a/src/cli/run/mod.rs +++ b/src/cli/run/mod.rs @@ -70,7 +70,7 @@ impl RunArgs { repository: None, provider: None, working_directory: None, - mode: Some(RunnerMode::Simulation), + mode: vec![RunnerMode::Simulation], simulation_tool: None, profile_folder: None, skip_upload: false, @@ -141,31 +141,23 @@ pub async fn run( match run_target { RunTarget::SingleCommand(args) => { - let config = Config::try_from(args)?; + let mut config = Config::try_from(args)?; - // Create execution context - let mut execution_context = - executor::ExecutionContext::new(config, codspeed_config, api_client).await?; + let orchestrator = + executor::Orchestrator::new(&mut config, codspeed_config, api_client).await?; - if !execution_context.is_local() { + if !orchestrator.is_local() { super::show_banner(); } - debug!("config: {:#?}", execution_context.config); - - // Execute benchmarks - let executor = executor::get_executor_from_mode(&execution_context.config.mode); + debug!("config: {config:#?}"); let poll_opts = PollResultsOptions::for_run(output_json); let poll_results_fn = async |upload_result: &UploadResult| { poll_results(api_client, upload_result, &poll_opts).await }; - executor::execute_benchmarks( - executor.as_ref(), - &mut execution_context, - setup_cache_dir, - poll_results_fn, - ) - .await?; + orchestrator + .execute(&mut config, setup_cache_dir, poll_results_fn) + .await?; } RunTarget::ConfigTargets { diff --git a/src/cli/shared.rs b/src/cli/shared.rs index 4b266edf..bc45579e 100644 --- a/src/cli/shared.rs +++ b/src/cli/shared.rs @@ -55,8 +55,14 @@ pub struct ExecAndRunSharedArgs { /// The mode to run the benchmarks in. /// If not provided, the mode will be loaded from the shell session (set via `codspeed use `). - #[arg(short, long, value_enum, env = "CODSPEED_RUNNER_MODE")] - pub mode: Option, + #[arg( + short, + long, + value_enum, + env = "CODSPEED_RUNNER_MODE", + value_delimiter = ',' + )] + pub mode: Vec, /// The Valgrind simulation tool to use (callgrind or tracegrind). #[arg(long, value_enum, env = "CODSPEED_SIMULATION_TOOL", hide = true)] @@ -98,25 +104,26 @@ pub struct ExecAndRunSharedArgs { } impl ExecAndRunSharedArgs { - /// Resolves the runner mode from CLI argument, shell session, or returns an error. + /// Resolves the runner modes from CLI argument, shell session, or returns an error. /// /// Priority: /// 1. CLI argument (--mode or -m) /// 2. Shell session mode (set via `codspeed use `) /// 3. Error if neither is available - pub fn resolve_mode(&self) -> Result { - if let Some(mode) = &self.mode { - return Ok(mode.clone()); + pub fn resolve_modes(&self) -> Result> { + if !self.mode.is_empty() { + return Ok(self.mode.clone()); } - if let Some(mode) = load_shell_session_mode()? { - debug!("Loaded mode from shell session: {mode:?}"); - return Ok(mode); + let modes = load_shell_session_mode()?; + + if modes.is_empty() { + return Err(anyhow!( + "No runner mode specified. Use --mode or set the mode for this shell session with `codspeed use `." + )); } - Err(anyhow!( - "No runner mode specified. Use --mode or set the mode for this shell session with `codspeed use `." - )) + Ok(modes) } } diff --git a/src/cli/show.rs b/src/cli/show.rs index 41aa8173..37a9e02f 100644 --- a/src/cli/show.rs +++ b/src/cli/show.rs @@ -1,12 +1,17 @@ use crate::prelude::*; pub fn run() -> Result<()> { - let shell_session_mode = crate::runner_mode::load_shell_session_mode()?; + let modes = crate::runner_mode::load_shell_session_mode()?; - if let Some(mode) = shell_session_mode { - info!("{mode:?}"); - } else { + if modes.is_empty() { info!("No mode set for this shell session"); + } else { + let modes_str = modes + .iter() + .map(|m| format!("{m:?}")) + .collect::>() + .join(", "); + info!("{modes_str}"); } Ok(()) diff --git a/src/cli/use_mode.rs b/src/cli/use_mode.rs index a08d79af..d2e5be4a 100644 --- a/src/cli/use_mode.rs +++ b/src/cli/use_mode.rs @@ -6,9 +6,11 @@ use clap::Args; #[derive(Debug, Args)] pub struct UseArgs { - /// Set the CodSpeed runner mode for this shell session. If not provided, the current mode will - /// be displayed. - pub mode: RunnerMode, + /// Set the CodSpeed runner mode(s) for this shell session. + /// Multiple modes can be provided as separate arguments (e.g. `simulation walltime`) + /// or comma-separated (e.g. `simulation,walltime`). + #[arg(value_delimiter = ',', required = true)] + pub mode: Vec, } pub fn run(args: UseArgs) -> Result<()> { diff --git a/src/executor/config.rs b/src/executor/config.rs index ad21f2b5..daaba52f 100644 --- a/src/executor/config.rs +++ b/src/executor/config.rs @@ -27,7 +27,7 @@ pub enum SimulationTool { /// for controlling the execution flow. It is typically constructed from command-line /// arguments via [`RunArgs`] and combined with [`CodSpeedConfig`] to create an /// [`ExecutionContext`]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Config { pub upload_url: Url, pub token: Option, @@ -35,7 +35,7 @@ pub struct Config { pub working_directory: Option, pub command: String, - pub mode: RunnerMode, + pub modes: Vec, pub instruments: Instruments, pub enable_perf: bool, /// Stack unwinding mode for perf (if enabled) @@ -53,7 +53,7 @@ pub struct Config { pub go_runner_version: Option, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct RepositoryOverride { pub owner: String, pub repository: String, @@ -81,6 +81,13 @@ impl Config { pub fn set_token(&mut self, token: Option) { self.token = token; } + + /// Create a clone of this config pinned to a single mode. + pub fn for_mode(&self, mode: &RunnerMode) -> Config { + let mut c = self.clone(); + c.modes = vec![mode.clone()]; + c + } } #[cfg(test)] @@ -93,7 +100,7 @@ impl Config { repository_override: None, working_directory: None, command: "".into(), - mode: RunnerMode::Simulation, + modes: vec![RunnerMode::Simulation], instruments: Instruments::test(), perf_unwinding_mode: None, enable_perf: false, @@ -114,7 +121,7 @@ impl TryFrom for Config { type Error = Error; fn try_from(args: RunArgs) -> Result { let instruments = Instruments::try_from(&args)?; - let mode = args.shared.resolve_mode()?; + let modes = args.shared.resolve_modes()?; let raw_upload_url = args .shared .upload_url @@ -131,7 +138,7 @@ impl TryFrom for Config { .map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider)) .transpose()?, working_directory: args.shared.working_directory, - mode, + modes, instruments, perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode, enable_perf: args.shared.perf_run_args.enable_perf, @@ -153,7 +160,7 @@ impl Config { args: crate::cli::exec::ExecArgs, command: String, ) -> Result { - let mode = args.shared.resolve_mode()?; + let modes = args.shared.resolve_modes()?; let raw_upload_url = args .shared .upload_url @@ -170,7 +177,7 @@ impl Config { .map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider)) .transpose()?, working_directory: args.shared.working_directory, - mode, + modes, instruments: Instruments { mongodb: None }, // exec doesn't support MongoDB perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode, enable_perf: args.shared.perf_run_args.enable_perf, @@ -210,7 +217,7 @@ mod tests { repository: None, provider: None, working_directory: None, - mode: Some(RunnerMode::Simulation), + mode: vec![RunnerMode::Simulation], simulation_tool: None, profile_folder: None, skip_upload: false, @@ -250,7 +257,7 @@ mod tests { repository: Some("owner/repo".into()), provider: Some(RepositoryProvider::GitLab), working_directory: Some("/tmp".into()), - mode: Some(RunnerMode::Simulation), + mode: vec![RunnerMode::Simulation], simulation_tool: None, profile_folder: Some("./codspeed.out".into()), skip_upload: true, @@ -334,7 +341,7 @@ mod tests { repository: None, provider: None, working_directory: None, - mode: Some(RunnerMode::Simulation), + mode: vec![RunnerMode::Simulation], simulation_tool: None, profile_folder: None, skip_upload: false, diff --git a/src/executor/execution_context.rs b/src/executor/execution_context.rs index 9d945a88..ca71a3fc 100644 --- a/src/executor/execution_context.rs +++ b/src/executor/execution_context.rs @@ -1,74 +1,29 @@ use super::Config; -use crate::api_client::CodSpeedAPIClient; -use crate::cli::run::logger::Logger; -use crate::config::CodSpeedConfig; -use crate::prelude::*; -use crate::run_environment::{self, RunEnvironment}; -use crate::runner_mode::RunnerMode; -use crate::system::{self, SystemInfo}; use std::path::PathBuf; use super::create_profile_folder; -/// Runtime context for benchmark execution. +/// Per-mode execution context. /// -/// This struct contains all the necessary information and dependencies needed to execute -/// benchmarks, including the execution configuration, system information, environment provider, -/// and logging facilities. It is constructed from a [`Config`] and [`CodSpeedConfig`] and -/// serves as the primary context passed to executors during the benchmark run lifecycle. +/// Contains only the mode-specific configuration and the profile folder path. +/// Shared state (provider, system_info, logger) lives in [`Orchestrator`]. pub struct ExecutionContext { pub config: Config, /// Directory path where profiling data and results are stored pub profile_folder: PathBuf, - pub system_info: SystemInfo, - /// The run environment provider (GitHub Actions, GitLab CI, local, etc.) - pub provider: Box, - pub logger: Logger, } impl ExecutionContext { - pub fn is_local(&self) -> bool { - self.provider.get_run_environment() == RunEnvironment::Local - } - - pub async fn new( - mut config: Config, - codspeed_config: &CodSpeedConfig, - api_client: &CodSpeedAPIClient, - ) -> Result { - let provider = run_environment::get_provider(&config, api_client).await?; - let system_info = SystemInfo::new()?; - system::check_system(&system_info)?; - let logger = Logger::new(provider.as_ref())?; - + pub fn new(config: Config) -> anyhow::Result { let profile_folder = if let Some(profile_folder) = &config.profile_folder { profile_folder.clone() } else { create_profile_folder()? }; - if provider.get_run_environment() == RunEnvironment::Local { - if codspeed_config.auth.token.is_none() { - bail!("You have to authenticate the CLI first. Run `codspeed auth login`."); - } - debug!("Using the token from the CodSpeed configuration file"); - config.set_token(codspeed_config.auth.token.clone()); - } - - #[allow(deprecated)] - if config.mode == RunnerMode::Instrumentation { - warn!( - "The 'instrumentation' runner mode is deprecated and will be removed in a future version. \ - Please use 'simulation' instead." - ); - } - Ok(ExecutionContext { config, profile_folder, - system_info, - provider, - logger, }) } } diff --git a/src/executor/mod.rs b/src/executor/mod.rs index 24bd54d2..a674dc97 100644 --- a/src/executor/mod.rs +++ b/src/executor/mod.rs @@ -5,6 +5,7 @@ mod execution_context; mod helpers; mod interfaces; mod memory; +pub mod orchestrator; mod shared; #[cfg(test)] mod tests; @@ -15,12 +16,12 @@ use crate::instruments::mongo_tracer::{MongoTracer, install_mongodb_tracer}; use crate::prelude::*; use crate::runner_mode::RunnerMode; use crate::system::SystemInfo; -use crate::upload::UploadResult; use async_trait::async_trait; pub use config::Config; pub use execution_context::ExecutionContext; pub use helpers::profile_folder::create_profile_folder; pub use interfaces::ExecutorName; +pub use orchestrator::Orchestrator; use memory::executor::MemoryExecutor; use std::path::Path; @@ -81,20 +82,17 @@ pub trait Executor { async fn teardown(&self, execution_context: &ExecutionContext) -> Result<()>; } -/// Execute benchmarks with the given configuration -/// This is the core execution logic shared between `run` and `exec` commands -pub async fn execute_benchmarks( +/// Run a single executor: setup → run → teardown → persist logs. +/// Does NOT upload. +pub async fn run_executor( executor: &dyn Executor, - execution_context: &mut ExecutionContext, + orchestrator: &Orchestrator, + execution_context: &ExecutionContext, setup_cache_dir: Option<&Path>, - poll_results: F, -) -> Result<()> -where - F: AsyncFn(&UploadResult) -> Result<()>, -{ +) -> Result<()> { if !execution_context.config.skip_setup { executor - .setup(&execution_context.system_info, setup_cache_dir) + .setup(&orchestrator.system_info, setup_cache_dir) .await?; // TODO: refactor and move directly in the Instruments struct as a `setup` method @@ -106,8 +104,6 @@ where } if !execution_context.config.skip_run { - start_opened_group!("Running the benchmarks"); - // TODO: refactor and move directly in the Instruments struct as a `start` method let mongo_tracer = if let Some(mongodb_config) = &execution_context.config.instruments.mongodb { @@ -125,38 +121,15 @@ where if let Some(mut mongo_tracer) = mongo_tracer { mongo_tracer.stop().await?; } - end_group!(); debug!("Tearing down the executor"); executor.teardown(execution_context).await?; - execution_context + orchestrator .logger - .persist_log_to_profile_folder(execution_context)?; + .persist_log_to_profile_folder(&execution_context.profile_folder)?; } else { debug!("Skipping the run of the benchmarks"); }; - // Handle upload and polling - if !execution_context.config.skip_upload { - if !execution_context.is_local() { - // If relevant, set the OIDC token for authentication - // Note: OIDC tokens can expire quickly, so we set it just before the upload - execution_context - .provider - .set_oidc_token(&mut execution_context.config) - .await?; - } - - start_group!("Uploading results"); - let upload_result = crate::upload::upload(execution_context, executor.name()).await?; - - if execution_context.is_local() { - poll_results(&upload_result).await?; - } - end_group!(); - } else { - debug!("Skipping upload of performance data"); - } - Ok(()) } diff --git a/src/executor/orchestrator.rs b/src/executor/orchestrator.rs new file mode 100644 index 00000000..d01a6d4d --- /dev/null +++ b/src/executor/orchestrator.rs @@ -0,0 +1,148 @@ +use super::{Config, ExecutionContext, ExecutorName, get_executor_from_mode, run_executor}; +use crate::api_client::CodSpeedAPIClient; +use crate::cli::run::logger::Logger; +use crate::config::CodSpeedConfig; +use crate::prelude::*; +use crate::run_environment::{self, RunEnvironment, RunEnvironmentProvider}; +use crate::runner_mode::RunnerMode; +use crate::system::{self, SystemInfo}; +use crate::upload::{UploadResult, upload}; +use std::path::Path; + +/// Shared orchestration state created once per CLI invocation. +/// +/// Contains the run environment provider, system info, and logger — things +/// that are the same regardless of which executor mode is running. +pub struct Orchestrator { + pub system_info: SystemInfo, + pub provider: Box, + pub logger: Logger, +} + +impl Orchestrator { + pub fn is_local(&self) -> bool { + self.provider.get_run_environment() == RunEnvironment::Local + } + + pub async fn new( + config: &mut Config, + codspeed_config: &CodSpeedConfig, + api_client: &CodSpeedAPIClient, + ) -> Result { + let provider = run_environment::get_provider(config, api_client).await?; + let system_info = SystemInfo::new()?; + system::check_system(&system_info)?; + let logger = Logger::new(provider.as_ref())?; + + if provider.get_run_environment() == RunEnvironment::Local { + if codspeed_config.auth.token.is_none() { + bail!("You have to authenticate the CLI first. Run `codspeed auth login`."); + } + debug!("Using the token from the CodSpeed configuration file"); + config.set_token(codspeed_config.auth.token.clone()); + } + + #[allow(deprecated)] + if config.modes.contains(&RunnerMode::Instrumentation) { + warn!( + "The 'instrumentation' runner mode is deprecated and will be removed in a future version. \ + Please use 'simulation' instead." + ); + } + + Ok(Orchestrator { + system_info, + provider, + logger, + }) + } + + /// Execute benchmarks for all configured modes, then upload results. + pub async fn execute( + &self, + config: &mut Config, + setup_cache_dir: Option<&Path>, + poll_results: F, + ) -> Result<()> + where + F: AsyncFn(&UploadResult) -> Result<()>, + { + // Phase 1: Run all executors + let modes = config.modes.clone(); + let is_multi_mode = modes.len() > 1; + let mut completed_runs: Vec<(ExecutionContext, ExecutorName)> = vec![]; + if !config.skip_run { + start_opened_group!("Running the benchmarks"); + } + for mode in &modes { + if modes.len() > 1 { + info!("Running benchmarks for {mode} mode"); + } + let mut per_mode_config = config.for_mode(mode); + // For multi-mode runs, always create a fresh profile folder per mode + // even if the user specified one (to avoid modes overwriting each other). + if is_multi_mode { + per_mode_config.profile_folder = None; + } + let ctx = ExecutionContext::new(per_mode_config)?; + let executor = get_executor_from_mode(mode); + + run_executor(executor.as_ref(), self, &ctx, setup_cache_dir).await?; + completed_runs.push((ctx, executor.name())); + } + if !config.skip_run { + end_group!(); + } + + // Phase 2: Upload all results + if !config.skip_upload { + start_group!("Uploading results"); + let last_upload_result = self.upload_all(&mut completed_runs).await?; + end_group!(); + + // Phase 3: Poll results after all uploads are complete. + // All uploads share the same run_id/owner/repository, so polling once is sufficient. + if self.is_local() { + poll_results(&last_upload_result).await?; + } + } else { + debug!("Skipping upload of performance data"); + } + + Ok(()) + } + + pub async fn upload_all( + &self, + completed_runs: &mut [(ExecutionContext, ExecutorName)], + ) -> Result { + let mut last_upload_result: Option = None; + + let total_runs = completed_runs.len(); + for (ctx, executor_name) in completed_runs.iter_mut() { + if !self.is_local() { + // OIDC tokens can expire quickly, so refresh just before each upload + self.provider.set_oidc_token(&mut ctx.config).await?; + } + + if total_runs > 1 { + info!("Uploading results for {executor_name:?} executor"); + } + let upload_result = upload(self, ctx, executor_name.clone()).await?; + last_upload_result = Some(upload_result); + } + info!("Performance data uploaded"); + if let Some(upload_result) = &last_upload_result { + info!( + "Linked repository: {}", + console::style(format!( + "{}/{}", + upload_result.owner, upload_result.repository + )) + .bold() + ); + } + + last_upload_result.ok_or_else(|| anyhow::anyhow!("No completed runs to upload")) + } +} diff --git a/src/executor/tests.rs b/src/executor/tests.rs index c640f2d0..930a3f06 100644 --- a/src/executor/tests.rs +++ b/src/executor/tests.rs @@ -119,15 +119,11 @@ fn test_cases(#[case] cmd: &str) {} fn env_test_cases(#[case] env_case: (&str, &str)) {} async fn create_test_setup(config: Config) -> (ExecutionContext, TempDir) { - use crate::api_client::CodSpeedAPIClient; - use crate::config::CodSpeedConfig; use crate::executor::config::RepositoryOverride; use crate::run_environment::interfaces::RepositoryProvider; let temp_dir = TempDir::new().unwrap(); - let codspeed_config = CodSpeedConfig::default(); - let api_client = CodSpeedAPIClient::create_test_client(); let mut config_with_folder = config; config_with_folder.profile_folder = Some(temp_dir.path().to_path_buf()); @@ -141,15 +137,12 @@ async fn create_test_setup(config: Config) -> (ExecutionContext, TempDir) { } // Provide a test token so authentication doesn't fail - let mut codspeed_config_with_token = codspeed_config; if config_with_folder.token.is_none() { - codspeed_config_with_token.auth.token = Some("test-token".to_string()); + config_with_folder.token = Some("test-token".to_string()); } let execution_context = - ExecutionContext::new(config_with_folder, &codspeed_config_with_token, &api_client) - .await - .expect("Failed to create ExecutionContext for test"); + ExecutionContext::new(config_with_folder).expect("Failed to create ExecutionContext"); (execution_context, temp_dir) } @@ -189,7 +182,7 @@ mod valgrind { fn valgrind_config(command: &str) -> Config { Config { - mode: RunnerMode::Simulation, + modes: vec![RunnerMode::Simulation], command: command.to_string(), ..Config::test() } @@ -257,7 +250,7 @@ mod walltime { fn walltime_config(command: &str, enable_perf: bool) -> Config { Config { - mode: RunnerMode::Walltime, + modes: vec![RunnerMode::Walltime], command: command.to_string(), enable_perf, ..Config::test() @@ -425,7 +418,7 @@ mod memory { fn memory_config(command: &str) -> Config { Config { - mode: RunnerMode::Memory, + modes: vec![RunnerMode::Memory], command: command.to_string(), ..Config::test() } diff --git a/src/instruments/mod.rs b/src/instruments/mod.rs index 68ef036f..c16a00de 100644 --- a/src/instruments/mod.rs +++ b/src/instruments/mod.rs @@ -8,12 +8,12 @@ use crate::prelude::*; pub mod mongo_tracer; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MongoDBConfig { pub uri_env_name: Option, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Instruments { pub mongodb: Option, } diff --git a/src/project_config/merger.rs b/src/project_config/merger.rs index 773bc696..a063fd6a 100644 --- a/src/project_config/merger.rs +++ b/src/project_config/merger.rs @@ -152,7 +152,7 @@ mod tests { repository: None, provider: None, working_directory: Some("./cli-dir".to_string()), - mode: Some(RunnerMode::Walltime), + mode: vec![RunnerMode::Walltime], simulation_tool: None, profile_folder: None, skip_upload: false, @@ -185,7 +185,7 @@ mod tests { repository: None, provider: None, working_directory: None, - mode: Some(RunnerMode::Walltime), + mode: vec![RunnerMode::Walltime], simulation_tool: None, profile_folder: None, skip_upload: false, @@ -209,7 +209,7 @@ mod tests { // Config working_directory should be used assert_eq!(merged.working_directory, Some("./config-dir".to_string())); // Mode stays as CLI value - assert_eq!(merged.mode, Some(RunnerMode::Walltime)); + assert_eq!(merged.mode, vec![RunnerMode::Walltime]); } #[test] @@ -220,7 +220,7 @@ mod tests { repository: None, provider: None, working_directory: Some("./dir".to_string()), - mode: Some(RunnerMode::Simulation), + mode: vec![RunnerMode::Simulation], simulation_tool: None, profile_folder: None, skip_upload: false, @@ -238,7 +238,7 @@ mod tests { // Should be identical to CLI assert_eq!(merged.working_directory, Some("./dir".to_string())); - assert_eq!(merged.mode, Some(RunnerMode::Simulation)); + assert_eq!(merged.mode, vec![RunnerMode::Simulation]); } #[test] diff --git a/src/run_environment/local/provider.rs b/src/run_environment/local/provider.rs index 8ed0b366..2c245b3b 100644 --- a/src/run_environment/local/provider.rs +++ b/src/run_environment/local/provider.rs @@ -59,6 +59,8 @@ impl LocalProvider { let resolved = Self::resolve_repository(config, api_client, git_context.as_ref()).await?; + let expected_run_parts_count = config.modes.len() as u32; + Ok(Self { repository_provider: resolved.provider, owner: resolved.owner, @@ -68,7 +70,7 @@ impl LocalProvider { repository_root_path, event: RunEvent::Local, run_id: Uuid::new_v4().to_string(), - expected_run_parts_count: 1, + expected_run_parts_count, }) } @@ -294,6 +296,8 @@ impl RunEnvironmentProvider for LocalProvider { }) } + /// Not used here because we need the executor_name to generate the run_part_id + /// TODO(COD-2009) Change this interface as all providers will add the executor to the run part metadata fn get_run_provider_run_part(&self) -> Option { None } diff --git a/src/runner_mode/shell_session.rs b/src/runner_mode/shell_session.rs index de6822d3..46586563 100644 --- a/src/runner_mode/shell_session.rs +++ b/src/runner_mode/shell_session.rs @@ -42,7 +42,7 @@ fn get_mode_file_path(base_dir: &Path, pid: pid_t) -> PathBuf { base_dir.join(pid.to_string()) } -pub(crate) fn register_shell_session_mode(mode: &RunnerMode) -> Result<()> { +pub(crate) fn register_shell_session_mode(mode: &[RunnerMode]) -> Result<()> { let use_mode_dir = get_use_mode_root_dir(); std::fs::create_dir_all(&use_mode_dir)?; @@ -56,7 +56,7 @@ pub(crate) fn register_shell_session_mode(mode: &RunnerMode) -> Result<()> { Ok(()) } -pub(crate) fn load_shell_session_mode() -> Result> { +pub(crate) fn load_shell_session_mode() -> Result> { // Go up the process tree until we find a registered mode let mut current_pid = std::process::id() as pid_t; @@ -66,12 +66,12 @@ pub(crate) fn load_shell_session_mode() -> Result> { if mode_file_path.exists() { let mode_str = std::fs::read_to_string(mode_file_path)?; - let mode: RunnerMode = serde_json::from_str(&mode_str)?; - return Ok(Some(mode)); + let mode: Vec = serde_json::from_str(&mode_str)?; + return Ok(mode); } current_pid = parent_pid; } - Ok(None) + Ok(Vec::new()) } diff --git a/src/upload/uploader.rs b/src/upload/uploader.rs index 9565c85c..76fd9862 100644 --- a/src/upload/uploader.rs +++ b/src/upload/uploader.rs @@ -1,6 +1,7 @@ use crate::executor::Config; use crate::executor::ExecutionContext; use crate::executor::ExecutorName; +use crate::executor::Orchestrator; use crate::run_environment::RunEnvironment; use crate::upload::{UploadError, profile_archive::ProfileArchiveContent}; use crate::{ @@ -51,7 +52,7 @@ async fn calculate_folder_size(path: &std::path::Path) -> Result { /// For WallTime, we check the folder size and create either a compressed or uncompressed tar archive /// based on the MAX_UNCOMPRESSED_PROFILE_SIZE_BYTES threshold. async fn create_profile_archive( - execution_context: &ExecutionContext, + profile_folder: &std::path::Path, executor_name: ExecutorName, ) -> Result { let time_start = std::time::Instant::now(); @@ -60,8 +61,7 @@ async fn create_profile_archive( debug!("Creating compressed tar archive for Valgrind"); let enc = GzipEncoder::new(Vec::new()); let mut tar = Builder::new(enc); - tar.append_dir_all(".", execution_context.profile_folder.clone()) - .await?; + tar.append_dir_all(".", profile_folder).await?; let mut gzip_encoder = tar.into_inner().await?; gzip_encoder.shutdown().await?; let data = gzip_encoder.into_inner(); @@ -69,8 +69,7 @@ async fn create_profile_archive( } ExecutorName::Memory | ExecutorName::WallTime => { // Check folder size to decide on compression - let folder_size_bytes = - calculate_folder_size(&execution_context.profile_folder).await?; + let folder_size_bytes = calculate_folder_size(profile_folder).await?; let should_compress = folder_size_bytes >= MAX_UNCOMPRESSED_PROFILE_SIZE_BYTES; let temp_file = tempfile::NamedTempFile::new()?; @@ -90,8 +89,7 @@ async fn create_profile_archive( ); let enc = GzipEncoder::new(file); let mut tar = Builder::new(enc); - tar.append_dir_all(".", execution_context.profile_folder.clone()) - .await?; + tar.append_dir_all(".", profile_folder).await?; let mut gzip_encoder = tar.into_inner().await?; gzip_encoder.shutdown().await?; gzip_encoder.into_inner().sync_all().await?; @@ -104,8 +102,7 @@ async fn create_profile_archive( bytes_to_mib(MAX_UNCOMPRESSED_PROFILE_SIZE_BYTES) ); let mut tar = Builder::new(file); - tar.append_dir_all(".", execution_context.profile_folder.clone()) - .await?; + tar.append_dir_all(".", profile_folder).await?; tar.into_inner().await?.sync_all().await?; ProfileArchive::new_uncompressed_on_disk(persistent_path)? @@ -249,44 +246,28 @@ pub struct UploadResult { } pub async fn upload( - execution_context: &mut ExecutionContext, + orchestrator: &Orchestrator, + execution_context: &ExecutionContext, executor_name: ExecutorName, ) -> Result { - let profile_archive = create_profile_archive(execution_context, executor_name.clone()).await?; + let profile_archive = + create_profile_archive(&execution_context.profile_folder, executor_name.clone()).await?; debug!( "Run Environment provider detected: {:?}", - execution_context.provider.get_run_environment() + orchestrator.provider.get_run_environment() ); - if !execution_context.is_local() { - // If relevant, set the OIDC token for authentication - // Note: OIDC tokens can expire quickly, so we set it just before the upload - execution_context - .provider - .set_oidc_token(&mut execution_context.config) - .await?; - } - - let upload_metadata = execution_context + let upload_metadata = orchestrator .provider .get_upload_metadata( &execution_context.config, - &execution_context.system_info, + &orchestrator.system_info, &profile_archive, executor_name, ) .await?; debug!("Upload metadata: {upload_metadata:#?}"); - info!( - "Linked repository: {}", - style(format!( - "{}/{}", - upload_metadata.run_environment_metadata.owner, - upload_metadata.run_environment_metadata.repository - )) - .bold(), - ); if upload_metadata.tokenless { let hash = upload_metadata.get_hash(); info!("CodSpeed Run Hash: \"{hash}\""); @@ -301,7 +282,6 @@ pub async fn upload( profile_archive.content.size().await? ); upload_profile_archive(&upload_data, profile_archive).await?; - info!("Performance data uploaded"); Ok(UploadResult { run_id: upload_data.run_id, @@ -324,7 +304,7 @@ mod tests { #[ignore] #[tokio::test] async fn test_upload() { - let config = Config { + let mut config = Config { command: "pytest tests/ --codspeed".into(), upload_url: Url::parse("change me").unwrap(), token: Some("change me".into()), @@ -365,11 +345,12 @@ mod tests { async { let codspeed_config = CodSpeedConfig::default(); let api_client = CodSpeedAPIClient::create_test_client(); - let mut execution_context = - ExecutionContext::new(config, &codspeed_config, &api_client) - .await - .expect("Failed to create ExecutionContext for test"); - upload(&mut execution_context, ExecutorName::Valgrind) + let orchestrator = Orchestrator::new(&mut config, &codspeed_config, &api_client) + .await + .expect("Failed to create Orchestrator for test"); + let execution_context = + ExecutionContext::new(config).expect("Failed to create ExecutionContext"); + upload(&orchestrator, &execution_context, ExecutorName::Valgrind) .await .unwrap(); },