diff --git a/Cargo.lock b/Cargo.lock index 63e434e6..7f797444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,7 @@ dependencies = [ "sysinfo", "temp-env", "tempfile", + "test-with", "tokio", "tokio-tar", "url", @@ -1540,6 +1541,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -2154,6 +2177,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-with" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb2a43a245ab793321d71ff9f2be21785999c9aa5b2ea93dc507e97572be843" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 92ff82a5..9aac9fd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "codspeed-runner" version = "3.6.1" -edition = "2021" +edition = "2024" repository = "https://github.com/CodSpeedHQ/runner" publish = false @@ -61,7 +61,7 @@ procfs = "0.17.0" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } insta = { version = "1.29.0", features = ["json", "redactions"] } - +test-with = { version = "0.15", default-features = false, features = [] } [workspace.metadata.release] sign-tag = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b475f2f9..7855e6d5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.85.0" +channel = "1.88.0" components = ["rustfmt", "clippy"] diff --git a/src/app.rs b/src/app.rs index c92db85d..96ab23f2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,13 +2,13 @@ use crate::{ api_client::CodSpeedAPIClient, auth, config::CodSpeedConfig, - local_logger::{init_local_logger, CODSPEED_U8_COLOR_CODE}, + local_logger::{CODSPEED_U8_COLOR_CODE, init_local_logger}, prelude::*, run, setup, }; use clap::{ - builder::{styling, Styles}, Parser, Subcommand, + builder::{Styles, styling}, }; fn create_styles() -> Styles { diff --git a/src/auth.rs b/src/auth.rs index 582a6628..67fdd26c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*}; use clap::{Args, Subcommand}; use console::style; -use tokio::time::{sleep, Instant}; +use tokio::time::{Instant, sleep}; #[derive(Debug, Args)] pub struct AuthArgs { diff --git a/src/local_logger.rs b/src/local_logger.rs index d5acaa3a..ddfbb6ab 100644 --- a/src/local_logger.rs +++ b/src/local_logger.rs @@ -5,14 +5,14 @@ use std::{ }; use crate::prelude::*; -use console::{style, Style}; +use console::{Style, style}; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; use log::Log; use simplelog::{CombinedLogger, SharedLogger}; use std::io::Write; -use crate::logger::{get_group_event, get_json_event, GroupEvent, JsonEvent}; +use crate::logger::{GroupEvent, JsonEvent, get_group_event, get_json_event}; pub const CODSPEED_U8_COLOR_CODE: u8 = 208; // #FF8700 @@ -68,7 +68,7 @@ impl Log for LocalLogger { GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { eprintln!( "\n{}", - style(format!("►►► {} ", name)) + style(format!("►►► {name} ")) .bold() .color256(CODSPEED_U8_COLOR_CODE) ); @@ -78,18 +78,17 @@ impl Log for LocalLogger { spinner.set_style( ProgressStyle::with_template( format!( - " {{spinner:>.{}}} {{wide_msg:.{}.bold}}", - CODSPEED_U8_COLOR_CODE, CODSPEED_U8_COLOR_CODE + " {{spinner:>.{CODSPEED_U8_COLOR_CODE}}} {{wide_msg:.{CODSPEED_U8_COLOR_CODE}.bold}}" ) .as_str(), ) .unwrap(), ); - spinner.set_message(format!("{}...", name)); + spinner.set_message(format!("{name}...")); spinner.enable_steady_tick(Duration::from_millis(100)); SPINNER.lock().unwrap().replace(spinner); } else { - eprintln!("{}...", name); + eprintln!("{name}..."); } } GroupEvent::End => { diff --git a/src/main.rs b/src/main.rs index ddc57bef..27099496 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,12 +35,12 @@ async fn main() { if log_enabled!(log::Level::Error) { error!("{} {}", style("Error:").bold().red(), style(cause).red()); } else { - eprintln!("Error: {}", cause); + eprintln!("Error: {cause}"); } } if log_enabled!(log::Level::Debug) { for e in err.chain().skip(1) { - debug!("Caused by: {}", e); + debug!("Caused by: {e}"); } } clean_logger(); diff --git a/src/prelude.rs b/src/prelude.rs index 272f962a..7f8254c9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,6 @@ pub use crate::{end_group, log_json, start_group, start_opened_group}; #[allow(unused_imports)] -pub use anyhow::{anyhow, bail, ensure, Context, Error, Result}; +pub use anyhow::{Context, Error, Result, anyhow, bail, ensure}; pub use itertools::Itertools; #[allow(unused_imports)] pub use log::{debug, error, info, trace, warn}; diff --git a/src/request_client.rs b/src/request_client.rs index 07a2b6dc..f8dde6ea 100644 --- a/src/request_client.rs +++ b/src/request_client.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use reqwest::ClientBuilder; use reqwest_middleware::{ClientBuilder as ClientWithMiddlewareBuilder, ClientWithMiddleware}; -use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; +use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; const UPLOAD_RETRY_COUNT: u32 = 3; diff --git a/src/run/check_system.rs b/src/run/check_system.rs index f4f73710..efe7a29e 100644 --- a/src/run/check_system.rs +++ b/src/run/check_system.rs @@ -123,7 +123,7 @@ lazy_static! { /// - Debian 11 x86_64 /// - Debian 12 x86_64 pub fn check_system(system_info: &SystemInfo) -> Result<()> { - debug!("System info: {:#?}", system_info); + debug!("System info: {system_info:#?}"); let system_tuple = ( system_info.os.as_str(), diff --git a/src/run/config.rs b/src/run/config.rs index a7c1763b..ffa7e003 100644 --- a/src/run/config.rs +++ b/src/run/config.rs @@ -3,8 +3,8 @@ use crate::run::instruments::Instruments; use std::path::PathBuf; use url::Url; -use crate::run::run_environment::RepositoryProvider; use crate::run::RunArgs; +use crate::run::run_environment::RepositoryProvider; use super::{RunnerMode, UnwindingMode}; @@ -110,8 +110,8 @@ fn extract_owner_and_repository_from_arg(owner_and_repository: &str) -> Result<( #[cfg(test)] mod tests { - use crate::run::instruments::MongoDBConfig; use crate::run::PerfRunArgs; + use crate::run::instruments::MongoDBConfig; use super::*; diff --git a/src/run/helpers/download_file.rs b/src/run/helpers/download_file.rs index 10e2f0a2..e07c2b19 100644 --- a/src/run/helpers/download_file.rs +++ b/src/run/helpers/download_file.rs @@ -4,7 +4,7 @@ use std::path::Path; use url::Url; pub async fn download_file(url: &Url, path: &Path) -> Result<()> { - debug!("Downloading file: {}", url); + debug!("Downloading file: {url}"); let response = REQUEST_CLIENT .get(url.clone()) .send() diff --git a/src/run/helpers/format_duration.rs b/src/run/helpers/format_duration.rs index bcca12a2..010acf4b 100644 --- a/src/run/helpers/format_duration.rs +++ b/src/run/helpers/format_duration.rs @@ -10,9 +10,9 @@ fn get_nearest_exponent(val: f64) -> i32 { fn format_shifted_value(value: f64, fraction_digits: usize) -> String { if fraction_digits == 0 && value.fract() == 0.0 { - format!("{:.0}", value) + format!("{value:.0}") } else { - format!("{:.1$}", value, fraction_digits) + format!("{value:.fraction_digits$}") } } @@ -35,7 +35,7 @@ fn format_duration_to_exponent(val: f64, exponent: i32, fraction_digits: usize) "{} ns", format_shifted_value(val * 10f64.powi(9), fraction_digits) ), - _ => format!("{} s", val), + _ => format!("{val} s"), } } diff --git a/src/run/helpers/parse_git_remote.rs b/src/run/helpers/parse_git_remote.rs index 42cb19e0..5447ec34 100644 --- a/src/run/helpers/parse_git_remote.rs +++ b/src/run/helpers/parse_git_remote.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use lazy_static::lazy_static; lazy_static! { diff --git a/src/run/instruments/mod.rs b/src/run/instruments/mod.rs index 752a41e7..48b60d37 100644 --- a/src/run/instruments/mod.rs +++ b/src/run/instruments/mod.rs @@ -56,7 +56,9 @@ impl TryFrom<&RunArgs> for Instruments { uri_env_name: args.mongo_uri_env_name.clone(), }) } else if args.mongo_uri_env_name.is_some() { - warn!("The MongoDB instrument is disabled but a MongoDB URI environment variable name was provided, ignoring it"); + warn!( + "The MongoDB instrument is disabled but a MongoDB URI environment variable name was provided, ignoring it" + ); None } else { None diff --git a/src/run/instruments/mongo_tracer.rs b/src/run/instruments/mongo_tracer.rs index 7078ac6c..450db62f 100644 --- a/src/run/instruments/mongo_tracer.rs +++ b/src/run/instruments/mongo_tracer.rs @@ -11,8 +11,8 @@ use reqwest::Client; use tokio::fs; use url::Url; +use crate::{MONGODB_TRACER_VERSION, run::helpers::get_env_variable}; use crate::{prelude::*, run::helpers::download_file}; -use crate::{run::helpers::get_env_variable, MONGODB_TRACER_VERSION}; use super::MongoDBConfig; @@ -53,10 +53,7 @@ impl MongoTracer { pub fn try_from(profile_folder: &Path, mongodb_config: &MongoDBConfig) -> Result { let user_input = match &mongodb_config.uri_env_name { Some(uri_env_name) => { - debug!( - "Retrieving the value of {} to patch the MongoDB URL", - uri_env_name - ); + debug!("Retrieving the value of {uri_env_name} to patch the MongoDB URL"); Some(UserInput { mongo_uri: get_env_variable(uri_env_name.as_str())?, uri_env_name: uri_env_name.to_string(), @@ -98,9 +95,9 @@ impl MongoTracer { Some(destination_uri) => { let parsing_error_fn = || { anyhow!( - "Failed to parse the Mongo URI: {}. Be sure to follow the MongoDB URI format described here: https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats", - destination_uri.as_str() - ) + "Failed to parse the Mongo URI: {}. Be sure to follow the MongoDB URI format described here: https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats", + destination_uri.as_str() + ) }; Some(format!( @@ -141,14 +138,13 @@ impl MongoTracer { } command.envs(envs); - debug!("Start the MongoDB tracer: {:?}", command); + debug!("Start the MongoDB tracer: {command:?}"); if let Some(destination_host_port) = destination_host_port { - debug!( - "Proxy MongoDB from {} to {}", - proxy_host_port, destination_host_port - ); + debug!("Proxy MongoDB from {proxy_host_port} to {destination_host_port}"); } else { - info!("No MongoDB URI provided, user will have to provide it dynamically through the CodSpeed integration"); + info!( + "No MongoDB URI provided, user will have to provide it dynamically through the CodSpeed integration" + ); } let mut process = command .stdout(Stdio::piped()) @@ -232,7 +228,9 @@ impl MongoTracer { pub async fn install_mongodb_tracer() -> Result<()> { debug!("Installing mongodb-tracer"); // TODO: release the tracer and update this url - let installer_url = format!("https://codspeed-public-assets.s3.eu-west-1.amazonaws.com/mongo-tracer/{MONGODB_TRACER_VERSION}/cs-mongo-tracer-installer.sh"); + let installer_url = format!( + "https://codspeed-public-assets.s3.eu-west-1.amazonaws.com/mongo-tracer/{MONGODB_TRACER_VERSION}/cs-mongo-tracer-installer.sh" + ); let installer_path = env::temp_dir().join("cs-mongo-tracer-installer.sh"); download_file( &Url::parse(installer_url.as_str()).unwrap(), diff --git a/src/run/mod.rs b/src/run/mod.rs index edf19bae..ccdce8c5 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -1,11 +1,11 @@ +use crate::VERSION; use crate::api_client::CodSpeedAPIClient; use crate::config::CodSpeedConfig; use crate::prelude::*; use crate::run::{config::Config, logger::Logger}; -use crate::VERSION; use check_system::SystemInfo; use clap::{Args, ValueEnum}; -use instruments::mongo_tracer::{install_mongodb_tracer, MongoTracer}; +use instruments::mongo_tracer::{MongoTracer, install_mongodb_tracer}; use run_environment::interfaces::{RepositoryProvider, RunEnvironment}; use runner::get_run_data; use serde::Serialize; @@ -30,12 +30,11 @@ fn show_banner() { / / / __ \ / __ / \__ \ / __ \ / _ \ / _ \ / __ / / /___ / /_/ // /_/ / ___/ // /_/ // __// __// /_/ / \____/ \____/ \__,_/ /____// .___/ \___/ \___/ \__,_/ - https://codspeed.io /_/ runner v{} -"#, - VERSION + https://codspeed.io /_/ runner v{VERSION} +"# ); - println!("{}", banner); - debug!("codspeed v{}", VERSION); + println!("{banner}"); + debug!("codspeed v{VERSION}"); } #[derive(Debug, Copy, Clone, PartialEq, ValueEnum, Default)] @@ -187,7 +186,7 @@ pub async fn run( if provider.get_run_environment() != RunEnvironment::Local { show_banner(); } - debug!("config: {:#?}", config); + debug!("config: {config:#?}"); if provider.get_run_environment() == RunEnvironment::Local { if codspeed_config.auth.token.is_none() { diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index c1e4ede5..255c5019 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -1,7 +1,7 @@ use std::time::Duration; use console::style; -use tokio::time::{sleep, Instant}; +use tokio::time::{Instant, sleep}; use crate::api_client::{ CodSpeedAPIClient, FetchLocalRunReportResponse, FetchLocalRunReportVars, RunStatus, @@ -68,9 +68,9 @@ pub async fn poll_results( 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() + style(format!("+{rounded_impact}%")).green().bold() } else { - style(format!("{}%", rounded_impact)).red().bold() + style(format!("{rounded_impact}%")).red().bold() }; info!( diff --git a/src/run/run_environment/buildkite/logger.rs b/src/run/run_environment/buildkite/logger.rs index 6950dfdf..64ec8957 100644 --- a/src/run/run_environment/buildkite/logger.rs +++ b/src/run/run_environment/buildkite/logger.rs @@ -1,5 +1,5 @@ use crate::{ - logger::{get_group_event, get_json_event, GroupEvent}, + logger::{GroupEvent, get_group_event, get_json_event}, run::run_environment::logger::should_provider_logger_handle_record, }; use log::*; diff --git a/src/run/run_environment/buildkite/provider.rs b/src/run/run_environment/buildkite/provider.rs index 40c048c7..b4105993 100644 --- a/src/run/run_environment/buildkite/provider.rs +++ b/src/run/run_environment/buildkite/provider.rs @@ -3,7 +3,7 @@ use std::env; use simplelog::SharedLogger; use crate::prelude::*; -use crate::run::helpers::{parse_git_remote, GitRemote}; +use crate::run::helpers::{GitRemote, parse_git_remote}; use crate::run::run_environment::{RunEnvironment, RunPart}; use crate::run::{ config::Config, @@ -45,7 +45,7 @@ pub fn get_ref() -> Result { let pr_number = get_pr_number()?; if let Some(pr_number) = pr_number { - Ok(format!("refs/pull/{}/merge", pr_number)) + Ok(format!("refs/pull/{pr_number}/merge")) } else { Ok(format!( "refs/heads/{}", diff --git a/src/run/run_environment/github_actions/logger.rs b/src/run/run_environment/github_actions/logger.rs index 63c84895..444f217f 100644 --- a/src/run/run_environment/github_actions/logger.rs +++ b/src/run/run_environment/github_actions/logger.rs @@ -1,5 +1,5 @@ use crate::{ - logger::{get_group_event, get_json_event, GroupEvent}, + logger::{GroupEvent, get_group_event, get_json_event}, run::run_environment::logger::should_provider_logger_handle_record, }; use log::*; @@ -27,7 +27,7 @@ impl Log for GithubActionLogger { if let Some(group_event) = get_group_event(record) { match group_event { GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { - println!("::group::{}", name); + println!("::group::{name}"); } GroupEvent::End => { println!("::endgroup::"); @@ -50,7 +50,7 @@ impl Log for GithubActionLogger { let message_string = message.to_string(); let lines = message_string.lines(); // ensure that all the lines of the message have the prefix, otherwise GitHub Actions will not recognize the command for the whole string - lines.for_each(|line| println!("{}{}", prefix, line)); + lines.for_each(|line| println!("{prefix}{line}")); } fn flush(&self) { diff --git a/src/run/run_environment/github_actions/provider.rs b/src/run/run_environment/github_actions/provider.rs index 0698076a..9d813a95 100644 --- a/src/run/run_environment/github_actions/provider.rs +++ b/src/run/run_environment/github_actions/provider.rs @@ -81,16 +81,16 @@ impl TryFrom<&Config> for GitHubActionsProvider { }; let github_event_name = get_env_variable("GITHUB_EVENT_NAME")?; - let event = serde_json::from_str(&format!("\"{}\"", github_event_name)).context( - format!("Event {} is not supported by CodSpeed", github_event_name), - )?; + let event = serde_json::from_str(&format!("\"{github_event_name}\"")).context(format!( + "Event {github_event_name} is not supported by CodSpeed" + ))?; let repository_root_path = match find_repository_root(&std::env::current_dir()?) { Some(mut path) => { // Add a trailing slash to the path path.push(""); path.to_string_lossy().to_string() } - None => format!("/home/runner/work/{}/{}/", repository, repository), + None => format!("/home/runner/work/{repository}/{repository}/"), }; Ok(Self { @@ -563,7 +563,10 @@ mod tests { assert_eq!(run_part.run_id, "123789"); assert_eq!(run_part.job_name, "my_job"); - assert_eq!(run_part.run_part_id, "my_job-{\"runner-version\":\"3.2.1\",\"numeric-value\":123456789}-{\"job-total\":2,\"job-index\":1}"); + assert_eq!( + run_part.run_part_id, + "my_job-{\"runner-version\":\"3.2.1\",\"numeric-value\":123456789}-{\"job-total\":2,\"job-index\":1}" + ); assert_json_snapshot!(run_part.metadata, @r#" { "job-index": 1, @@ -609,7 +612,10 @@ mod tests { assert_eq!(run_part.run_id, "123789"); assert_eq!(run_part.job_name, "my_job"); - assert_eq!(run_part.run_part_id, "my_job-{\"runner-version\":\"3.2.1\",\"numeric-value\":123456789}-{\"job-total\":2,\"job-index\":1}"); + assert_eq!( + run_part.run_part_id, + "my_job-{\"runner-version\":\"3.2.1\",\"numeric-value\":123456789}-{\"job-total\":2,\"job-index\":1}" + ); assert_json_snapshot!(run_part.metadata, @r#" { "job-index": 1, diff --git a/src/run/run_environment/gitlab_ci/logger.rs b/src/run/run_environment/gitlab_ci/logger.rs index b519bb95..03d55c89 100644 --- a/src/run/run_environment/gitlab_ci/logger.rs +++ b/src/run/run_environment/gitlab_ci/logger.rs @@ -11,7 +11,7 @@ use std::{ }; use crate::{ - logger::{get_group_event, get_json_event, GroupEvent}, + logger::{GroupEvent, get_group_event, get_json_event}, run::run_environment::logger::should_provider_logger_handle_record, }; @@ -93,14 +93,18 @@ impl Log for GitLabCILogger { *section_id = Some(new_section_id.to_string()); // https://docs.gitlab.com/ee/ci/yaml/script.html#custom-collapsible-sections - println!("{ERASE_CURSOR}section_start:{timestamp}:{new_section_id}{U_CR}{ERASE_CURSOR}{U_ESC}[36;1m{name}{COLOR_RESET}"); + println!( + "{ERASE_CURSOR}section_start:{timestamp}:{new_section_id}{U_CR}{ERASE_CURSOR}{U_ESC}[36;1m{name}{COLOR_RESET}" + ); } GroupEvent::End => { // do not fail if there is no current section let current_section_id = section_id.clone().unwrap_or("".to_string()); // https://docs.gitlab.com/ee/ci/yaml/script.html#custom-collapsible-sections - println!("{ERASE_CURSOR}section_end:{timestamp}:{current_section_id}{U_CR}{ERASE_CURSOR}"); + println!( + "{ERASE_CURSOR}section_end:{timestamp}:{current_section_id}{U_CR}{ERASE_CURSOR}" + ); *section_id = None; } diff --git a/src/run/run_environment/local/provider.rs b/src/run/run_environment/local/provider.rs index 6f38fbe9..da9ce256 100644 --- a/src/run/run_environment/local/provider.rs +++ b/src/run/run_environment/local/provider.rs @@ -5,7 +5,7 @@ use crate::local_logger::get_local_logger; use crate::prelude::*; use crate::run::check_system::SystemInfo; use crate::run::config::RepositoryOverride; -use crate::run::helpers::{parse_git_remote, GitRemote}; +use crate::run::helpers::{GitRemote, parse_git_remote}; use crate::run::run_environment::{RunEnvironment, RunPart}; use crate::run::runner::ExecutorName; use crate::run::uploader::{Runner, UploadMetadata}; @@ -67,8 +67,7 @@ impl TryFrom<&Config> for LocalProvider { }; let git_repository = Repository::open(repository_root_path.clone()).context(format!( - "Failed to open repository at path: {}", - repository_root_path + "Failed to open repository at path: {repository_root_path}" ))?; let remote = git_repository.find_remote("origin")?; diff --git a/src/run/run_environment/provider.rs b/src/run/run_environment/provider.rs index 70d98548..c236db93 100644 --- a/src/run/run_environment/provider.rs +++ b/src/run/run_environment/provider.rs @@ -16,8 +16,7 @@ pub trait RunEnvironmentDetector { fn get_commit_hash(repository_root_path: &str) -> Result { let repo = Repository::open(repository_root_path).context(format!( - "Failed to open repository at path: {}", - repository_root_path + "Failed to open repository at path: {repository_root_path}" ))?; let commit_hash = repo diff --git a/src/run/runner/mod.rs b/src/run/runner/mod.rs index 2314ced7..9296f27d 100644 --- a/src/run/runner/mod.rs +++ b/src/run/runner/mod.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use crate::prelude::*; -use super::{config::Config, RunnerMode}; +use super::{RunnerMode, config::Config}; mod executor; mod helpers; diff --git a/src/run/runner/valgrind/executor.rs b/src/run/runner/valgrind/executor.rs index b24c6d94..14321d3d 100644 --- a/src/run/runner/valgrind/executor.rs +++ b/src/run/runner/valgrind/executor.rs @@ -21,7 +21,7 @@ impl Executor for ValgrindExecutor { install_valgrind(system_info).await?; if let Err(error) = venv_compat::symlink_libpython(None) { - error!("Failed to symlink libpython: {}", error); + error!("Failed to symlink libpython: {error}"); } else { info!("Successfully added symlink for libpython in the venv"); } diff --git a/src/run/runner/valgrind/helpers/ignored_objects_path.rs b/src/run/runner/valgrind/helpers/ignored_objects_path.rs index f1f88b48..df2d4471 100644 --- a/src/run/runner/valgrind/helpers/ignored_objects_path.rs +++ b/src/run/runner/valgrind/helpers/ignored_objects_path.rs @@ -9,7 +9,7 @@ fn get_python_objects() -> Vec { if output.is_err() { let err = output.err().unwrap().to_string(); - debug!("Failed to get python shared objects: {}", err); + debug!("Failed to get python shared objects: {err}"); return vec![]; } let output = output.unwrap(); @@ -62,14 +62,8 @@ pub fn get_objects_path_to_ignore() -> Vec { let mut objects_path_to_ignore = vec![]; objects_path_to_ignore.extend(get_python_objects()); objects_path_to_ignore.extend(get_node_objects()); - debug!( - "objects_path_to_ignore before normalization: {:?}", - objects_path_to_ignore - ); + debug!("objects_path_to_ignore before normalization: {objects_path_to_ignore:?}"); normalize_object_paths(&mut objects_path_to_ignore); - debug!( - "objects_path_to_ignore after normalization: {:?}", - objects_path_to_ignore - ); + debug!("objects_path_to_ignore after normalization: {objects_path_to_ignore:?}"); objects_path_to_ignore } diff --git a/src/run/runner/valgrind/helpers/perf_maps.rs b/src/run/runner/valgrind/helpers/perf_maps.rs index 82839da5..796ab4c1 100644 --- a/src/run/runner/valgrind/helpers/perf_maps.rs +++ b/src/run/runner/valgrind/helpers/perf_maps.rs @@ -24,7 +24,7 @@ pub async fn harvest_perf_maps(profile_folder: &Path) -> Result<()> { pub async fn harvest_perf_maps_for_pids(profile_folder: &Path, pids: &HashSet) -> Result<()> { let perf_maps = pids .iter() - .map(|pid| format!("perf-{}.map", pid)) + .map(|pid| format!("perf-{pid}.map")) .map(|file_name| { ( PathBuf::from("/tmp").join(&file_name), diff --git a/src/run/runner/valgrind/helpers/venv_compat.rs b/src/run/runner/valgrind/helpers/venv_compat.rs index c5d696ee..c0f9ba8d 100644 --- a/src/run/runner/valgrind/helpers/venv_compat.rs +++ b/src/run/runner/valgrind/helpers/venv_compat.rs @@ -51,6 +51,8 @@ pub fn symlink_libpython(cwd: Option<&String>) -> anyhow::Result<()> { mod tests { use super::*; + // Only run in Github Actions, to ensure python is dynamically linked. + #[test_with::env(GITHUB_ACTIONS)] #[test] fn test_venv_compat_no_crash() { assert!(symlink_libpython(None).is_ok()); diff --git a/src/run/runner/valgrind/measure.rs b/src/run/runner/valgrind/measure.rs index 8a7b85a4..a0510e1f 100644 --- a/src/run/runner/valgrind/measure.rs +++ b/src/run/runner/valgrind/measure.rs @@ -1,10 +1,10 @@ use crate::prelude::*; +use crate::run::runner::RunnerMode; use crate::run::runner::helpers::env::get_base_injected_env; use crate::run::runner::helpers::get_bench_command::get_bench_command; use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe; use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore; use crate::run::runner::valgrind::helpers::introspected_nodejs::setup_introspected_nodejs; -use crate::run::runner::RunnerMode; use crate::run::{config::Config, instruments::mongo_tracer::MongoTracer}; use lazy_static::lazy_static; use std::env; @@ -111,7 +111,7 @@ pub async fn measure( .args( get_objects_path_to_ignore() .iter() - .map(|x| format!("--obj-skip={}", x)), + .map(|x| format!("--obj-skip={x}")), ) .arg(format!("--callgrind-out-file={}", profile_path.to_str().unwrap()).as_str()) .arg(format!("--log-file={}", log_path.to_str().unwrap()).as_str()); @@ -130,7 +130,7 @@ pub async fn measure( mongo_tracer.apply_run_command_transformations(&mut cmd)?; } - debug!("cmd: {:?}", cmd); + debug!("cmd: {cmd:?}"); let status = run_command_with_log_pipe(cmd) .await .map_err(|e| anyhow!("failed to execute the benchmark process. {}", e))?; @@ -144,7 +144,7 @@ pub async fn measure( if !status.success() { let valgrind_log = profile_folder.join("valgrind.log"); let valgrind_log = std::fs::read_to_string(&valgrind_log).unwrap_or_default(); - debug!("valgrind.log: {}", valgrind_log); + debug!("valgrind.log: {valgrind_log}"); bail!("failed to execute valgrind"); } @@ -156,7 +156,7 @@ pub async fn measure( .parse::() .map_err(|e| anyhow!("unable to retrieve the program exit code. {}", e))? }; - debug!("Program exit code = {}", cmd_status); + debug!("Program exit code = {cmd_status}"); if cmd_status != 0 { bail!( "failed to execute the benchmark process, exit code: {}", diff --git a/src/run/runner/valgrind/setup.rs b/src/run/runner/valgrind/setup.rs index 9b2f23c3..0179f22f 100644 --- a/src/run/runner/valgrind/setup.rs +++ b/src/run/runner/valgrind/setup.rs @@ -1,6 +1,6 @@ use crate::run::runner::helpers::setup::run_with_sudo; -use crate::{prelude::*, run::helpers::download_file, VALGRIND_CODSPEED_VERSION}; -use crate::{run::check_system::SystemInfo, VALGRIND_CODSPEED_DEB_VERSION}; +use crate::{VALGRIND_CODSPEED_DEB_VERSION, run::check_system::SystemInfo}; +use crate::{VALGRIND_CODSPEED_VERSION, prelude::*, run::helpers::download_file}; use std::{env, process::Command}; use url::Url; diff --git a/src/run/runner/wall_time/executor.rs b/src/run/runner/wall_time/executor.rs index eb607f44..5c58c18c 100644 --- a/src/run/runner/wall_time/executor.rs +++ b/src/run/runner/wall_time/executor.rs @@ -1,11 +1,12 @@ use super::perf::PerfRunner; use crate::prelude::*; +use crate::run::RunnerMode; use crate::run::instruments::mongo_tracer::MongoTracer; use crate::run::runner::executor::Executor; use crate::run::runner::helpers::env::get_base_injected_env; use crate::run::runner::helpers::get_bench_command::get_bench_command; use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe; -use crate::run::runner::{ExecutorName, RunData, RunnerMode}; +use crate::run::runner::{ExecutorName, RunData}; use crate::run::{check_system::SystemInfo, config::Config}; use async_trait::async_trait; use std::fs::canonicalize; @@ -21,6 +22,20 @@ impl WallTimeExecutor { perf: cfg!(target_os = "linux").then(PerfRunner::new), } } + + fn walltime_bench_cmd(config: &Config, run_data: &RunData) -> Result { + let bench_cmd = get_bench_command(config)?; + + let setenv = get_base_injected_env(RunnerMode::Walltime, &run_data.profile_folder) + .into_iter() + .map(|(env, value)| format!("--setenv={env}={value}")) + .join(" "); + let uid = nix::unistd::Uid::current().as_raw(); + let gid = nix::unistd::Gid::current().as_raw(); + Ok(format!( + "systemd-run --scope --slice=codspeed.slice --same-dir --uid={uid} --gid={gid} {setenv} -- {bench_cmd}" + )) + } } #[async_trait(?Send)] @@ -44,29 +59,24 @@ impl Executor for WallTimeExecutor { run_data: &RunData, _mongo_tracer: &Option, ) -> Result<()> { - // IMPORTANT: Don't use `sh` here! We will use this pid to send signals to the - // spawned child process which won't work if we use a different shell. - let mut cmd = Command::new("bash"); - - cmd.envs(get_base_injected_env( - RunnerMode::Walltime, - &run_data.profile_folder, - )); + let mut cmd = Command::new("sudo"); if let Some(cwd) = &config.working_directory { let abs_cwd = canonicalize(cwd)?; cmd.current_dir(abs_cwd); } - let bench_cmd = get_bench_command(config)?; - let status = match (config.enable_perf, &self.perf) { - (true, Some(perf)) => perf.run(cmd, &bench_cmd, config).await, - _ => { - cmd.args(["-c", &bench_cmd]); - debug!("cmd: {:?}", cmd); - - run_command_with_log_pipe(cmd).await - } + let bench_cmd = Self::walltime_bench_cmd(config, run_data)?; + + let status = if let Some(perf) = &self.perf + && config.enable_perf + { + perf.run(cmd, &bench_cmd, config).await + } else { + cmd.args(["sh", "-c", &bench_cmd]); + debug!("cmd: {cmd:?}"); + + run_command_with_log_pipe(cmd).await }; let status = @@ -82,13 +92,15 @@ impl Executor for WallTimeExecutor { async fn teardown( &self, - _config: &Config, + config: &Config, _system_info: &SystemInfo, run_data: &RunData, ) -> Result<()> { debug!("Copying files to the profile folder"); - if let Some(perf) = &self.perf { + if let Some(perf) = &self.perf + && config.enable_perf + { perf.save_files_to(&run_data.profile_folder).await?; } diff --git a/src/run/runner/wall_time/perf/helpers.rs b/src/run/runner/wall_time/perf/helpers.rs index 3dc58832..419bdf4c 100644 --- a/src/run/runner/wall_time/perf/helpers.rs +++ b/src/run/runner/wall_time/perf/helpers.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use anyhow::{anyhow, bail}; -use linux_perf_data::{linux_perf_event_reader::EventRecord, PerfFileReader, PerfFileRecord}; +use linux_perf_data::{PerfFileReader, PerfFileRecord, linux_perf_event_reader::EventRecord}; /// Tries to find the pid of the sampled process within a perf.data file. pub fn find_pid>(perf_file: P) -> anyhow::Result { @@ -56,7 +56,7 @@ pub fn find_pid>(perf_file: P) -> anyhow::Result .iter() .max_by_key(|&(_, count)| count) .ok_or_else(|| anyhow!("Couldn't find pid in perf.data"))?; - log::debug!("Pid frequency: {:?}", pid_freq); + log::debug!("Pid frequency: {pid_freq:?}"); let pid_percentage = (*pid_count as f64 / total_count as f64) * 100.0; if pid_percentage < 75.0 { diff --git a/src/run/runner/wall_time/perf/mod.rs b/src/run/runner/wall_time/perf/mod.rs index 5253fdba..455721b2 100644 --- a/src/run/runner/wall_time/perf/mod.rs +++ b/src/run/runner/wall_time/perf/mod.rs @@ -1,12 +1,12 @@ #![cfg_attr(not(unix), allow(dead_code, unused_mut))] use crate::prelude::*; +use crate::run::UnwindingMode; use crate::run::config::Config; use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback; use crate::run::runner::helpers::setup::run_with_sudo; use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore; use crate::run::runner::valgrind::helpers::perf_maps::harvest_perf_maps_for_pids; -use crate::run::UnwindingMode; use anyhow::Context; use fifo::{PerfFifo, RunnerFifo}; use futures::stream::FuturesUnordered; @@ -14,7 +14,7 @@ use metadata::PerfMetadata; use perf_map::ProcessSymbols; use shared::Command as FifoCommand; use std::collections::HashSet; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Duration; use std::{cell::OnceCell, collections::HashMap, process::ExitStatus}; @@ -33,6 +33,55 @@ pub mod unwind_data; const PERF_DATA_PREFIX: &str = "perf.data."; +struct EnvGuard { + post_bench_script: PathBuf, +} + +impl EnvGuard { + fn execute_script_from_path>(path: P) -> anyhow::Result<()> { + let path = path.as_ref(); + if !path.exists() || !path.is_file() { + warn!("Script not found or not a file: {}", path.display()); + return Ok(()); + } + + let output = Command::new("bash").args([&path]).output()?; + if !output.status.success() { + info!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + error!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + bail!("Failed to execute script: {}", path.display()); + } + + Ok(()) + } + + pub fn setup_with_scripts>(pre_bench_script: P, post_bench_script: P) -> Self { + if let Err(e) = Self::execute_script_from_path(pre_bench_script.as_ref()) { + warn!("Failed to execute pre-bench script: {e}"); + println!("asdf: {e}"); + } + + Self { + post_bench_script: post_bench_script.as_ref().to_path_buf(), + } + } + + pub fn setup() -> Self { + Self::setup_with_scripts( + "/usr/local/bin/codspeed-pre-bench", + "/usr/local/bin/codspeed-post-bench", + ) + } +} + +impl Drop for EnvGuard { + fn drop(&mut self) { + if let Err(e) = Self::execute_script_from_path(&self.post_bench_script) { + warn!("Failed to execute post-bench script: {e}"); + } + } +} + pub struct PerfRunner { perf_dir: TempDir, benchmark_data: OnceCell, @@ -106,7 +155,7 @@ impl PerfRunner { UnwindingMode::FramePointer => "fp", UnwindingMode::Dwarf => "dwarf", }; - debug!("Using call graph mode: {:?}", cg_mode); + debug!("Using call graph mode: {cg_mode:?}"); let quiet_flag = { let log_level = std::env::var("CODSPEED_LOG") @@ -120,32 +169,47 @@ impl PerfRunner { "" } }; - let user = nix::unistd::User::from_uid(nix::unistd::Uid::current())?.unwrap(); + cmd.args([ + "sh", "-c", &format!( - "perf record {quiet_flag} --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph={cg_mode} --output={} -- \ - sudo systemd-run --scope --slice=codspeed.slice --same-dir -- runuser -u {} -- {bench_cmd}", + "perf record {quiet_flag} --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph={cg_mode} --output={} -- {bench_cmd}", perf_fifo.ctl_fifo_path.to_string_lossy(), perf_fifo.ack_fifo_path.to_string_lossy(), perf_file.path().to_string_lossy(), - user.name ), ]); - debug!("cmd: {:?}", cmd); + debug!("cmd: {cmd:?}"); - let on_process_started = async |pid: u32| -> anyhow::Result<()> { - let data = Self::handle_fifo(pid, runner_fifo, perf_fifo).await?; + let on_process_started = async |_| -> anyhow::Result<()> { + let data = Self::handle_fifo(runner_fifo, perf_fifo).await?; let _ = self.benchmark_data.set(data); Ok(()) }; - run_command_with_log_pipe_and_callback(cmd, on_process_started).await + + { + let _guard = EnvGuard::setup(); + run_command_with_log_pipe_and_callback(cmd, on_process_started).await + } } pub async fn save_files_to(&self, profile_folder: &PathBuf) -> anyhow::Result<()> { let start = std::time::Instant::now(); + // We ran perf with sudo, so we have to change the ownership of the perf data files + run_with_sudo(&[ + "chown", + "-R", + &format!( + "{}:{}", + nix::unistd::Uid::current(), + nix::unistd::Gid::current() + ), + self.perf_dir.path().to_str().unwrap(), + ])?; + // Copy the perf data files to the profile folder let copy_tasks = std::fs::read_dir(&self.perf_dir)? .filter_map(|entry| entry.ok()) @@ -201,12 +265,11 @@ impl PerfRunner { bench_data.save_to(profile_folder).unwrap(); let elapsed = start.elapsed(); - debug!("Perf teardown took: {:?}", elapsed); + debug!("Perf teardown took: {elapsed:?}"); Ok(()) } async fn handle_fifo( - perf_pid: u32, mut runner_fifo: RunnerFifo, mut perf_fifo: PerfFifo, ) -> anyhow::Result { @@ -215,6 +278,7 @@ impl PerfRunner { let mut unwind_data_by_pid = HashMap::>::new(); let mut integration = None; + let perf_pid = OnceCell::new(); loop { let perf_ping = tokio::time::timeout(Duration::from_secs(1), perf_fifo.ping()).await; if let Ok(Err(_)) | Err(_) = perf_ping { @@ -225,7 +289,7 @@ impl PerfRunner { let Ok(Ok(cmd)) = result else { continue; }; - debug!("Received command: {:?}", cmd); + debug!("Received command: {cmd:?}"); match cmd { FifoCommand::CurrentBenchmark { pid, uri } => { @@ -253,7 +317,7 @@ impl PerfRunner { .entry(pid) .or_insert(ProcessSymbols::new(pid)) .add_mapping(pid, path, base_addr, end_addr); - debug!("Added mapping for module {:?}", path); + debug!("Added mapping for module {path:?}"); if map.perms.contains(MMPermissions::EXECUTE) { match UnwindData::new( @@ -268,7 +332,9 @@ impl PerfRunner { .entry(pid) .or_default() .push(unwind_data); - debug!("Added unwind data for {path:?} ({base_addr:x} - {end_addr:x})"); + debug!( + "Added unwind data for {path:?} ({base_addr:x} - {end_addr:x})" + ); } Err(error) => { debug!( @@ -280,7 +346,9 @@ impl PerfRunner { } } } else if map.perms.contains(MMPermissions::EXECUTE) { - debug!("Found executable mapping without path: {base_addr:x} - {end_addr:x}"); + debug!( + "Found executable mapping without path: {base_addr:x} - {end_addr:x}" + ); } } } @@ -288,7 +356,22 @@ impl PerfRunner { runner_fifo.send_cmd(FifoCommand::Ack).await?; } FifoCommand::StartBenchmark => { - unsafe { libc::kill(perf_pid as i32, libc::SIGUSR2) }; + let perf_pid = perf_pid.get_or_init(|| { + let output = std::process::Command::new("sh") + .arg("-c") + .arg("pidof -s perf") + .output() + .expect("Failed to run pidof command"); + + String::from_utf8_lossy(&output.stdout) + .trim() + .parse::() + .expect("Failed to parse perf pid") + }); + + // Split the perf.data file + run_with_sudo(&["kill", "-USR2", &perf_pid.to_string()])?; + perf_fifo.start_events().await?; runner_fifo.send_cmd(FifoCommand::Ack).await?; } @@ -376,3 +459,49 @@ impl BenchmarkData { self.bench_order_by_pid.values().map(|v| v.len()).sum() } } +#[cfg(test)] +mod tests { + use tempfile::NamedTempFile; + + use super::*; + use std::{ + io::{Read, Write}, + os::unix::fs::PermissionsExt, + }; + + #[test] + fn test_env_guard_no_crash() { + fn create_run_script(content: &str) -> anyhow::Result { + let rwx = std::fs::Permissions::from_mode(0o777); + let mut script_file = tempfile::Builder::new() + .suffix(".sh") + .permissions(rwx) + .keep(true) + .tempfile()?; + script_file.write_all(content.as_bytes())?; + + Ok(script_file) + } + + let mut tmp_dst = tempfile::NamedTempFile::new().unwrap(); + + let pre_script = create_run_script(&format!( + "#!/usr/bin/env bash\necho \"pre\" >> {}", + tmp_dst.path().display() + )) + .unwrap(); + let post_script = create_run_script(&format!( + "#!/usr/bin/env bash\necho \"post\" >> {}", + tmp_dst.path().display() + )) + .unwrap(); + + { + let _guard = EnvGuard::setup_with_scripts(pre_script.path(), post_script.path()); + } + + let mut result = String::new(); + tmp_dst.read_to_string(&mut result).unwrap(); + assert_eq!(result, "pre\npost\n"); + } +} diff --git a/src/run/runner/wall_time/perf/setup.rs b/src/run/runner/wall_time/perf/setup.rs index 59e87d55..6a2a4ba0 100644 --- a/src/run/runner/wall_time/perf/setup.rs +++ b/src/run/runner/wall_time/perf/setup.rs @@ -18,7 +18,7 @@ fn cmd_version(cmd: &str) -> anyhow::Result { fn is_perf_installed() -> bool { let version_str = cmd_version("perf"); - debug!("Perf version: {:?}", version_str); + debug!("Perf version: {version_str:?}"); version_str.is_ok() } diff --git a/src/run/runner/wall_time/perf/unwind_data.rs b/src/run/runner/wall_time/perf/unwind_data.rs index d79d27ba..2ec8884c 100644 --- a/src/run/runner/wall_time/perf/unwind_data.rs +++ b/src/run/runner/wall_time/perf/unwind_data.rs @@ -1,6 +1,6 @@ //! WARNING: This file has to be in sync with perf-parser! -use anyhow::{bail, Context}; +use anyhow::{Context, bail}; use debugid::CodeId; use serde::{Deserialize, Serialize}; use std::ops::Range; diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index 0a568f76..eb34aec8 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -8,7 +8,7 @@ use crate::run::{ }; use crate::{prelude::*, request_client::REQUEST_CLIENT}; use async_compression::tokio::write::GzipEncoder; -use base64::{engine::general_purpose, Engine as _}; +use base64::{Engine as _, engine::general_purpose}; use console::style; use reqwest::StatusCode; use tokio::io::AsyncWriteExt; @@ -60,8 +60,15 @@ async fn retrieve_upload_data( } else { "Check that CODSPEED_TOKEN is set and has the correct value" }; - error_message.push_str(&format!("\n\n{}", additional_message)); + error_message.push_str(&format!("\n\n{additional_message}")); } + + debug!( + "Check that owner and repository are correct (case-sensitive!): {}/{}", + upload_metadata.run_environment_metadata.owner, + upload_metadata.run_environment_metadata.repository + ); + bail!( "Failed to retrieve upload data: {}\n -> {} {}", status, @@ -126,7 +133,7 @@ pub async fn upload( let upload_metadata = provider.get_upload_metadata(config, system_info, &archive_hash, executor_name)?; - debug!("Upload metadata: {:#?}", upload_metadata); + debug!("Upload metadata: {upload_metadata:#?}"); info!( "Linked repository: {}\n", style(format!( @@ -138,7 +145,7 @@ pub async fn upload( ); if upload_metadata.tokenless { let hash = upload_metadata.get_hash(); - info!("CodSpeed Run Hash: \"{}\"", hash); + info!("CodSpeed Run Hash: \"{hash}\""); } info!("Preparing upload...");