From 0ad8ce2468e925b9064cc76e110bde06e64fcaec Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Fri, 24 May 2024 14:33:54 -0400 Subject: [PATCH 01/26] chore(runner): remove useless snapshots --- ...__tests__pull_request_upload_metadata.snap | 28 ---------------- ...ad_metadata__tests__get_metadata_hash.snap | 33 ------------------- 2 files changed, 61 deletions(-) delete mode 100644 src/ci_provider/snapshots/codspeed_runner__ci_provider__github_actions_provider__tests__pull_request_upload_metadata.snap delete mode 100644 src/uploader/snapshots/codspeed_runner__uploader__upload_metadata__tests__get_metadata_hash.snap diff --git a/src/ci_provider/snapshots/codspeed_runner__ci_provider__github_actions_provider__tests__pull_request_upload_metadata.snap b/src/ci_provider/snapshots/codspeed_runner__ci_provider__github_actions_provider__tests__pull_request_upload_metadata.snap deleted file mode 100644 index 6dc8214..0000000 --- a/src/ci_provider/snapshots/codspeed_runner__ci_provider__github_actions_provider__tests__pull_request_upload_metadata.snap +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: src/ci_provider/github_actions_provider.rs -expression: upload_metadata ---- -{ - "version": 1, - "tokenless": false, - "ref": "refs/pull/22/merge", - "headRef": "feat/codspeed-runner", - "baseRef": "main", - "owner": "my-org", - "repository": "adrien-python-test", - "commitHash": "24809d9fca9ad0808a777bcbd807ecd5ec8a9100", - "event": "pull_request", - "profileMd5": "archive_hash", - "ghData": { - "runId": 6957110437, - "job": "log-env", - "sender": { - "id": 19605940, - "login": "adriencaccia" - } - }, - "runner": { - "name": "codspeed-runner", - "version": "[version]" - } -} diff --git a/src/uploader/snapshots/codspeed_runner__uploader__upload_metadata__tests__get_metadata_hash.snap b/src/uploader/snapshots/codspeed_runner__uploader__upload_metadata__tests__get_metadata_hash.snap deleted file mode 100644 index 7d7e6f4..0000000 --- a/src/uploader/snapshots/codspeed_runner__uploader__upload_metadata__tests__get_metadata_hash.snap +++ /dev/null @@ -1,33 +0,0 @@ ---- -source: src/uploader/upload_metadata.rs -expression: upload_metadata ---- -{ - "version": 2, - "tokenless": true, - "profileMd5": "jp/k05RKuqP3ERQuIIvx4Q==", - "runner": { - "name": "codspeed-runner", - "version": "2.1.0", - "instruments": [ - "MongoDB" - ] - }, - "platform": "github-actions", - "commitHash": "5bd77cb0da72bef094893ed45fb793ff16ecfbe3", - "ref": "refs/pull/29/merge", - "headRef": "chore/native-action-runner", - "baseRef": "main", - "owner": "CodSpeedHQ", - "repository": "codspeed-node", - "event": "pull_request", - "ghData": { - "runId": 7044765741, - "job": "codspeed", - "sender": { - "id": 19605940, - "login": "adriencaccia" - } - }, - "repositoryRootPath": "/home/runner/work/codspeed-node/codspeed-node/" -} From 832acd1262079c2544600f50187fa1464d159742 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Fri, 24 May 2024 14:37:23 -0400 Subject: [PATCH 02/26] refactor(runner): move runner to run subcommand --- README.md | 8 +- src/app.rs | 98 ++-------------- src/main.rs | 8 +- src/{ => run}/ci_provider/buildkite/logger.rs | 2 +- src/{ => run}/ci_provider/buildkite/mod.rs | 0 .../ci_provider/buildkite/provider.rs | 4 +- ...ests__pull_request_provider_metadata.snap} | 2 +- .../ci_provider/github_actions/logger.rs | 2 +- .../ci_provider/github_actions/mod.rs | 0 .../ci_provider/github_actions/provider.rs | 8 +- .../github_actions/samples/fork-pr-event.json | 0 .../github_actions/samples/pr-event.json | 0 ..._fork_pull_request_provider_metadata.snap} | 2 +- ...ests__pull_request_provider_metadata.snap} | 2 +- src/{ => run}/ci_provider/interfaces.rs | 0 src/{ => run}/ci_provider/logger.rs | 8 +- src/{ => run}/ci_provider/mod.rs | 8 +- src/{ => run}/ci_provider/provider.rs | 4 +- src/{ => run}/config.rs | 15 +-- src/{ => run}/helpers/find_repository_root.rs | 0 src/{ => run}/helpers/get_env_var.rs | 0 src/{ => run}/helpers/mod.rs | 0 src/{ => run}/instruments/mod.rs | 20 ++-- src/{ => run}/instruments/mongo_tracer.rs | 3 +- src/{ => run}/logger.rs | 9 +- src/run/mod.rs | 108 ++++++++++++++++++ src/{ => run}/runner/check_system.rs | 0 src/{ => run}/runner/helpers/download_file.rs | 0 .../runner/helpers/ignored_objects_path.rs | 0 .../runner/helpers/introspected_node/mod.rs | 0 .../runner/helpers/introspected_node/node.sh | 0 src/{ => run}/runner/helpers/mod.rs | 0 src/{ => run}/runner/helpers/perf_maps.rs | 0 .../runner/helpers/profile_folder.rs | 0 src/{ => run}/runner/mod.rs | 0 src/{ => run}/runner/run.rs | 4 +- src/{ => run}/runner/setup.rs | 3 +- src/{ => run}/runner/valgrind.rs | 9 +- src/{ => run}/uploader/interfaces.rs | 2 +- src/{ => run}/uploader/mod.rs | 0 ...ad_metadata__tests__get_metadata_hash.snap | 33 ++++++ src/{ => run}/uploader/upload.rs | 10 +- src/{ => run}/uploader/upload_metadata.rs | 2 +- 43 files changed, 221 insertions(+), 153 deletions(-) rename src/{ => run}/ci_provider/buildkite/logger.rs (98%) rename src/{ => run}/ci_provider/buildkite/mod.rs (100%) rename src/{ => run}/ci_provider/buildkite/provider.rs (99%) rename src/{ci_provider/buildkite/snapshots/codspeed_runner__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap => run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap} (86%) rename src/{ => run}/ci_provider/github_actions/logger.rs (98%) rename src/{ => run}/ci_provider/github_actions/mod.rs (100%) rename src/{ => run}/ci_provider/github_actions/provider.rs (98%) rename src/{ => run}/ci_provider/github_actions/samples/fork-pr-event.json (100%) rename src/{ => run}/ci_provider/github_actions/samples/pr-event.json (100%) rename src/{ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap => run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap} (89%) rename src/{ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap => run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap} (88%) rename src/{ => run}/ci_provider/interfaces.rs (100%) rename src/{ => run}/ci_provider/logger.rs (84%) rename src/{ => run}/ci_provider/mod.rs (72%) rename src/{ => run}/ci_provider/provider.rs (97%) rename src/{ => run}/config.rs (91%) rename src/{ => run}/helpers/find_repository_root.rs (100%) rename src/{ => run}/helpers/get_env_var.rs (100%) rename src/{ => run}/helpers/mod.rs (100%) rename src/{ => run}/instruments/mod.rs (90%) rename src/{ => run}/instruments/mongo_tracer.rs (99%) rename src/{ => run}/logger.rs (90%) create mode 100644 src/run/mod.rs rename src/{ => run}/runner/check_system.rs (100%) rename src/{ => run}/runner/helpers/download_file.rs (100%) rename src/{ => run}/runner/helpers/ignored_objects_path.rs (100%) rename src/{ => run}/runner/helpers/introspected_node/mod.rs (100%) rename src/{ => run}/runner/helpers/introspected_node/node.sh (100%) rename src/{ => run}/runner/helpers/mod.rs (100%) rename src/{ => run}/runner/helpers/perf_maps.rs (100%) rename src/{ => run}/runner/helpers/profile_folder.rs (100%) rename src/{ => run}/runner/mod.rs (100%) rename src/{ => run}/runner/run.rs (92%) rename src/{ => run}/runner/setup.rs (97%) rename src/{ => run}/runner/valgrind.rs (96%) rename src/{ => run}/uploader/interfaces.rs (90%) rename src/{ => run}/uploader/mod.rs (100%) create mode 100644 src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap rename src/{ => run}/uploader/upload.rs (94%) rename src/{ => run}/uploader/upload_metadata.rs (98%) diff --git a/README.md b/README.md index 976b986..28d3664 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The following providers are supported: If you want to use the CLI with another provider, you can open an issue or chat with us on [Discord](https://discord.com/invite/MxpaCfKSqF) 🚀 -You can check out the implementation of the [supported providers](https://github.com/CodSpeedHQ/runner/tree/main/src/ci_provider) for reference. +You can check out the implementation of the [supported providers](https://github.com/CodSpeedHQ/runner/tree/main/src/run/ci_provider) for reference. ## Installation @@ -40,11 +40,11 @@ Refer to the [releases page](https://github.com/CodSpeedHQ/runner/releases) to s Example of a command to run benchmarks with [Vitest](https://docs.codspeed.io/benchmarks/nodejs/vitest): ```bash -codspeed-runner --token=$CODSPEED_TOKEN -- pnpm vitest bench +codspeed-runner run --token=$CODSPEED_TOKEN -- pnpm vitest bench ``` ``` -Usage: codspeed-runner [OPTIONS] [COMMAND]... +Usage: codspeed-runner run [OPTIONS] [COMMAND]... Arguments: [COMMAND]... The bench command to run @@ -65,5 +65,5 @@ Options: Use the `CODSPEED_LOG` environment variable to set the logging level: ```bash -CODSPEED_LOG=debug codspeed-runner ... +CODSPEED_LOG=debug codspeed-runner run ... ``` diff --git a/src/app.rs b/src/app.rs index 29ca3b1..2ac8ee2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,97 +1,23 @@ -use crate::{ci_provider, config::Config, logger::Logger, prelude::*, runner, uploader, VERSION}; -use clap::Parser; - -fn show_banner() { - let banner = format!( - r#" - ______ __ _____ __ - / ____/____ ____/ // ___/ ____ ___ ___ ____/ / - / / / __ \ / __ / \__ \ / __ \ / _ \ / _ \ / __ / -/ /___ / /_/ // /_/ / ___/ // /_/ // __// __// /_/ / -\____/ \____/ \__,_/ /____// .___/ \___/ \___/ \__,_/ - https://codspeed.io /_/ runner v{} -"#, - VERSION - ); - println!("{}", banner); - debug!("codspeed-runner v{}", VERSION); -} +use crate::{prelude::*, run}; +use clap::{Parser, Subcommand}; #[derive(Parser, Debug)] -pub struct AppArgs { - /// The upload URL to use for uploading the results, useful for on-premises installations - #[arg(long)] - pub upload_url: Option, - - /// The token to use for uploading the results, - #[arg(long, env = "CODSPEED_TOKEN")] - pub token: Option, - - /// The directory where the command will be executed. - #[arg(long)] - pub working_directory: Option, - - /// Comma-separated list of instruments to enable. Possible values: mongodb. - #[arg(long, value_delimiter = ',')] - pub instruments: Vec, - - /// The name of the environment variable that contains the MongoDB URI to patch. - /// If not provided, user will have to provide it dynamically through a CodSpeed integration. - /// - /// Only used if the `mongodb` instrument is enabled. - #[arg(long)] - pub mongo_uri_env_name: Option, - - /// Only for debugging purposes, skips the upload of the results - #[arg( - long, - default_value = "false", - hide = true, - env = "CODSPEED_SKIP_UPLOAD" - )] - pub skip_upload: bool, - - /// Only for debugging purposes, skips the setup of the runner - #[arg(long, default_value = "false", hide = true)] - pub skip_setup: bool, - - /// The bench command to run - pub command: Vec, +struct Cli { + #[command(subcommand)] + command: Commands, } -#[cfg(test)] -impl AppArgs { - /// Constructs a new `AppArgs` with default values for testing purposes - pub fn test() -> Self { - Self { - upload_url: None, - token: None, - working_directory: None, - instruments: vec![], - mongo_uri_env_name: None, - skip_upload: false, - skip_setup: false, - command: vec![], - } - } +#[derive(Subcommand, Debug)] +enum Commands { + /// Run the bench command and upload the results to CodSpeed + Run(run::RunArgs), } pub async fn run() -> Result<()> { - let args = AppArgs::parse(); - let config = Config::try_from(args)?; - let provider = ci_provider::get_provider(&config)?; - let logger = Logger::new(&provider)?; - - show_banner(); - debug!("config: {:#?}", config); - - let run_data = runner::run(&config).await?; + let cli = Cli::parse(); - if !config.skip_upload { - start_group!("Upload the results"); - logger.persist_log_to_profile_folder(&run_data)?; - uploader::upload(&config, provider, &run_data).await?; - end_group!(); + match cli.command { + Commands::Run(args) => run::run(args).await?, } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 75a8a73..91113c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,7 @@ mod app; -mod ci_provider; -mod config; -mod helpers; -mod instruments; -mod logger; mod prelude; mod request_client; -mod runner; -mod uploader; +mod run; use prelude::*; diff --git a/src/ci_provider/buildkite/logger.rs b/src/run/ci_provider/buildkite/logger.rs similarity index 98% rename from src/ci_provider/buildkite/logger.rs rename to src/run/ci_provider/buildkite/logger.rs index 622deb6..46d3d71 100644 --- a/src/ci_provider/buildkite/logger.rs +++ b/src/run/ci_provider/buildkite/logger.rs @@ -1,4 +1,4 @@ -use crate::ci_provider::logger::{ +use crate::run::ci_provider::logger::{ get_group_event, should_provider_logger_handle_record, GroupEvent, }; use log::*; diff --git a/src/ci_provider/buildkite/mod.rs b/src/run/ci_provider/buildkite/mod.rs similarity index 100% rename from src/ci_provider/buildkite/mod.rs rename to src/run/ci_provider/buildkite/mod.rs diff --git a/src/ci_provider/buildkite/provider.rs b/src/run/ci_provider/buildkite/provider.rs similarity index 99% rename from src/ci_provider/buildkite/provider.rs rename to src/run/ci_provider/buildkite/provider.rs index 322e464..dd365f8 100644 --- a/src/ci_provider/buildkite/provider.rs +++ b/src/run/ci_provider/buildkite/provider.rs @@ -4,14 +4,14 @@ use lazy_static::lazy_static; use regex::Regex; use simplelog::SharedLogger; -use crate::{ +use crate::prelude::*; +use crate::run::{ ci_provider::{ interfaces::{ProviderMetadata, RunEvent}, provider::{CIProvider, CIProviderDetector}, }, config::Config, helpers::{find_repository_root, get_env_variable}, - prelude::*, }; use super::logger::BuildkiteLogger; diff --git a/src/ci_provider/buildkite/snapshots/codspeed_runner__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap b/src/run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap similarity index 86% rename from src/ci_provider/buildkite/snapshots/codspeed_runner__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap rename to src/run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap index dc325f9..28f1e6b 100644 --- a/src/ci_provider/buildkite/snapshots/codspeed_runner__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap +++ b/src/run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap @@ -1,5 +1,5 @@ --- -source: src/ci_provider/buildkite/provider.rs +source: src/run/ci_provider/buildkite/provider.rs expression: provider_metadata --- { diff --git a/src/ci_provider/github_actions/logger.rs b/src/run/ci_provider/github_actions/logger.rs similarity index 98% rename from src/ci_provider/github_actions/logger.rs rename to src/run/ci_provider/github_actions/logger.rs index 59f35c0..407f5db 100644 --- a/src/ci_provider/github_actions/logger.rs +++ b/src/run/ci_provider/github_actions/logger.rs @@ -1,4 +1,4 @@ -use crate::ci_provider::logger::{ +use crate::run::ci_provider::logger::{ get_group_event, should_provider_logger_handle_record, GroupEvent, }; use log::*; diff --git a/src/ci_provider/github_actions/mod.rs b/src/run/ci_provider/github_actions/mod.rs similarity index 100% rename from src/ci_provider/github_actions/mod.rs rename to src/run/ci_provider/github_actions/mod.rs diff --git a/src/ci_provider/github_actions/provider.rs b/src/run/ci_provider/github_actions/provider.rs similarity index 98% rename from src/ci_provider/github_actions/provider.rs rename to src/run/ci_provider/github_actions/provider.rs index 727e32e..3cd2cb5 100644 --- a/src/ci_provider/github_actions/provider.rs +++ b/src/run/ci_provider/github_actions/provider.rs @@ -4,14 +4,14 @@ use serde_json::Value; use simplelog::SharedLogger; use std::{env, fs}; -use crate::{ +use crate::prelude::*; +use crate::run::{ ci_provider::{ interfaces::{GhData, ProviderMetadata, RunEvent, Sender}, provider::{CIProvider, CIProviderDetector}, }, config::Config, helpers::{find_repository_root, get_env_variable}, - prelude::*, }; use super::logger::GithubActionLogger; @@ -234,7 +234,7 @@ mod tests { "GITHUB_EVENT_PATH", Some( format!( - "{}/src/ci_provider/github_actions/samples/pr-event.json", + "{}/src/run/ci_provider/github_actions/samples/pr-event.json", env!("CARGO_MANIFEST_DIR") ) .as_str(), @@ -282,7 +282,7 @@ mod tests { "GITHUB_EVENT_PATH", Some( format!( - "{}/src/ci_provider/github_actions/samples/fork-pr-event.json", + "{}/src/run/ci_provider/github_actions/samples/fork-pr-event.json", env!("CARGO_MANIFEST_DIR") ) .as_str(), diff --git a/src/ci_provider/github_actions/samples/fork-pr-event.json b/src/run/ci_provider/github_actions/samples/fork-pr-event.json similarity index 100% rename from src/ci_provider/github_actions/samples/fork-pr-event.json rename to src/run/ci_provider/github_actions/samples/fork-pr-event.json diff --git a/src/ci_provider/github_actions/samples/pr-event.json b/src/run/ci_provider/github_actions/samples/pr-event.json similarity index 100% rename from src/ci_provider/github_actions/samples/pr-event.json rename to src/run/ci_provider/github_actions/samples/pr-event.json diff --git a/src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap b/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap similarity index 89% rename from src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap rename to src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap index 8b8d6ef..f6c9bbb 100644 --- a/src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap +++ b/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap @@ -1,5 +1,5 @@ --- -source: src/ci_provider/github_actions/provider.rs +source: src/run/ci_provider/github_actions/provider.rs expression: provider_metadata --- { diff --git a/src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap b/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap similarity index 88% rename from src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap rename to src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap index 9ca0118..a4b3d7f 100644 --- a/src/ci_provider/github_actions/snapshots/codspeed_runner__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap +++ b/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap @@ -1,5 +1,5 @@ --- -source: src/ci_provider/github_actions/provider.rs +source: src/run/ci_provider/github_actions/provider.rs expression: provider_metadata --- { diff --git a/src/ci_provider/interfaces.rs b/src/run/ci_provider/interfaces.rs similarity index 100% rename from src/ci_provider/interfaces.rs rename to src/run/ci_provider/interfaces.rs diff --git a/src/ci_provider/logger.rs b/src/run/ci_provider/logger.rs similarity index 84% rename from src/ci_provider/logger.rs rename to src/run/ci_provider/logger.rs index ebd6193..17ab082 100644 --- a/src/ci_provider/logger.rs +++ b/src/run/ci_provider/logger.rs @@ -1,4 +1,4 @@ -use crate::runner::VALGRIND_EXECUTION_TARGET; +use crate::run::runner::VALGRIND_EXECUTION_TARGET; /// This target is used exclusively to handle group events. pub const GROUP_TARGET: &str = "codspeed::group"; @@ -16,7 +16,7 @@ pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened"; /// ``` macro_rules! start_group { ($name:expr) => { - log::log!(target: $crate::ci_provider::logger::GROUP_TARGET, log::Level::Info, "{}", $name); + log::log!(target: $crate::run::ci_provider::logger::GROUP_TARGET, log::Level::Info, "{}", $name); }; } @@ -32,7 +32,7 @@ macro_rules! start_group { /// ``` macro_rules! start_opened_group { ($name:expr) => { - log::log!(target: $crate::ci_provider::logger::OPENED_GROUP_TARGET, log::Level::Info, "{}", $name); + log::log!(target: $crate::run::ci_provider::logger::OPENED_GROUP_TARGET, log::Level::Info, "{}", $name); }; } @@ -41,7 +41,7 @@ macro_rules! start_opened_group { /// See [`start_group!`] for more information. macro_rules! end_group { () => { - log::log!(target: $crate::ci_provider::logger::GROUP_TARGET, log::Level::Info, ""); + log::log!(target: $crate::run::ci_provider::logger::GROUP_TARGET, log::Level::Info, ""); }; } diff --git a/src/ci_provider/mod.rs b/src/run/ci_provider/mod.rs similarity index 72% rename from src/ci_provider/mod.rs rename to src/run/ci_provider/mod.rs index 18280ea..7ebe1c2 100644 --- a/src/ci_provider/mod.rs +++ b/src/run/ci_provider/mod.rs @@ -2,11 +2,11 @@ pub mod interfaces; pub mod logger; mod provider; -use crate::ci_provider::buildkite::BuildkiteProvider; -use crate::ci_provider::github_actions::GitHubActionsProvider; -use crate::ci_provider::provider::CIProviderDetector; -use crate::config::Config; use crate::prelude::*; +use crate::run::ci_provider::buildkite::BuildkiteProvider; +use crate::run::ci_provider::github_actions::GitHubActionsProvider; +use crate::run::ci_provider::provider::CIProviderDetector; +use crate::run::config::Config; pub use self::provider::CIProvider; diff --git a/src/ci_provider/provider.rs b/src/run/ci_provider/provider.rs similarity index 97% rename from src/ci_provider/provider.rs rename to src/run/ci_provider/provider.rs index 32b2636..4de9a23 100644 --- a/src/ci_provider/provider.rs +++ b/src/run/ci_provider/provider.rs @@ -1,9 +1,9 @@ use git2::Repository; use simplelog::SharedLogger; -use crate::config::Config; use crate::prelude::*; -use crate::uploader::{Runner, UploadMetadata}; +use crate::run::config::Config; +use crate::run::uploader::{Runner, UploadMetadata}; use super::interfaces::ProviderMetadata; diff --git a/src/config.rs b/src/run/config.rs similarity index 91% rename from src/config.rs rename to src/run/config.rs index 9979071..1819056 100644 --- a/src/config.rs +++ b/src/run/config.rs @@ -1,7 +1,8 @@ -use crate::{instruments::Instruments, prelude::*}; +use crate::prelude::*; +use crate::run::instruments::Instruments; use url::Url; -use crate::app::AppArgs; +use crate::run::RunArgs; #[derive(Debug)] pub struct Config { @@ -34,9 +35,9 @@ impl Config { const DEFAULT_UPLOAD_URL: &str = "https://api.codspeed.io/upload"; -impl TryFrom for Config { +impl TryFrom for Config { type Error = Error; - fn try_from(args: AppArgs) -> Result { + fn try_from(args: RunArgs) -> Result { let instruments = Instruments::try_from(&args)?; let raw_upload_url = args.upload_url.unwrap_or_else(|| DEFAULT_UPLOAD_URL.into()); let upload_url = Url::parse(&raw_upload_url) @@ -55,13 +56,13 @@ impl TryFrom for Config { #[cfg(test)] mod tests { - use crate::instruments::MongoDBConfig; + use crate::run::instruments::MongoDBConfig; use super::*; #[test] fn test_try_from_env_empty() { - let config = Config::try_from(AppArgs { + let config = Config::try_from(RunArgs { upload_url: None, token: None, working_directory: None, @@ -83,7 +84,7 @@ mod tests { #[test] fn test_try_from_args() { - let config = Config::try_from(AppArgs { + let config = Config::try_from(RunArgs { upload_url: Some("https://example.com/upload".into()), token: Some("token".into()), working_directory: Some("/tmp".into()), diff --git a/src/helpers/find_repository_root.rs b/src/run/helpers/find_repository_root.rs similarity index 100% rename from src/helpers/find_repository_root.rs rename to src/run/helpers/find_repository_root.rs diff --git a/src/helpers/get_env_var.rs b/src/run/helpers/get_env_var.rs similarity index 100% rename from src/helpers/get_env_var.rs rename to src/run/helpers/get_env_var.rs diff --git a/src/helpers/mod.rs b/src/run/helpers/mod.rs similarity index 100% rename from src/helpers/mod.rs rename to src/run/helpers/mod.rs diff --git a/src/instruments/mod.rs b/src/run/instruments/mod.rs similarity index 90% rename from src/instruments/mod.rs rename to src/run/instruments/mod.rs index 9ad39b2..8e2e79b 100644 --- a/src/instruments/mod.rs +++ b/src/run/instruments/mod.rs @@ -3,8 +3,8 @@ use std::collections::HashSet; use log::warn; use serde::{Deserialize, Serialize}; -use crate::app::AppArgs; use crate::prelude::*; +use crate::run::RunArgs; pub mod mongo_tracer; @@ -39,9 +39,9 @@ impl Instruments { } } -impl TryFrom<&AppArgs> for Instruments { +impl TryFrom<&RunArgs> for Instruments { type Error = Error; - fn try_from(args: &AppArgs) -> Result { + fn try_from(args: &RunArgs) -> Result { let mut validated_instrument_names: HashSet = HashSet::new(); for instrument_name in &args.instruments { @@ -84,16 +84,16 @@ mod tests { #[test] fn test_from_args_empty() { - let instruments = Instruments::try_from(&AppArgs::test()).unwrap(); + let instruments = Instruments::try_from(&RunArgs::test()).unwrap(); assert!(instruments.mongodb.is_none()); } #[test] fn test_from_args() { - let args = AppArgs { + let args = RunArgs { instruments: vec!["mongodb".into()], mongo_uri_env_name: Some("MONGODB_URI".into()), - ..AppArgs::test() + ..RunArgs::test() }; let instruments = Instruments::try_from(&args).unwrap(); assert_eq!( @@ -107,10 +107,10 @@ mod tests { #[test] fn test_from_args_mongodb_disabled() { - let args = AppArgs { + let args = RunArgs { instruments: vec![], mongo_uri_env_name: Some("MONGODB_URI".into()), - ..AppArgs::test() + ..RunArgs::test() }; let instruments = Instruments::try_from(&args).unwrap(); assert_eq!(instruments.mongodb, None); @@ -119,10 +119,10 @@ mod tests { #[test] fn test_from_args_unknown_instrument_value() { - let args = AppArgs { + let args = RunArgs { instruments: vec!["unknown".into()], mongo_uri_env_name: Some("MONGODB_URI".into()), - ..AppArgs::test() + ..RunArgs::test() }; let instruments = Instruments::try_from(&args); assert!(instruments.is_err()); diff --git a/src/instruments/mongo_tracer.rs b/src/run/instruments/mongo_tracer.rs similarity index 99% rename from src/instruments/mongo_tracer.rs rename to src/run/instruments/mongo_tracer.rs index 50a5d40..9a6d4dc 100644 --- a/src/instruments/mongo_tracer.rs +++ b/src/run/instruments/mongo_tracer.rs @@ -10,7 +10,8 @@ use reqwest::Client; use tokio::fs; use url::Url; -use crate::{helpers::get_env_variable, prelude::*}; +use crate::prelude::*; +use crate::run::helpers::get_env_variable; use super::MongoDBConfig; diff --git a/src/logger.rs b/src/run/logger.rs similarity index 90% rename from src/logger.rs rename to src/run/logger.rs index beeed01..e543474 100644 --- a/src/logger.rs +++ b/src/run/logger.rs @@ -1,6 +1,9 @@ -use crate::ci_provider::logger::{GROUP_TARGET, OPENED_GROUP_TARGET}; -use crate::runner::RunData; -use crate::{ci_provider::CIProvider, prelude::*}; +use crate::prelude::*; +use crate::run::{ + ci_provider::logger::{GROUP_TARGET, OPENED_GROUP_TARGET}, + ci_provider::CIProvider, + runner::RunData, +}; use log::LevelFilter; use simplelog::{CombinedLogger, WriteLogger}; use std::fs::copy; diff --git a/src/run/mod.rs b/src/run/mod.rs new file mode 100644 index 0000000..0babfe7 --- /dev/null +++ b/src/run/mod.rs @@ -0,0 +1,108 @@ +use crate::prelude::*; +use crate::run::{config::Config, logger::Logger}; +use crate::VERSION; +use clap::Args; + +pub mod ci_provider; +mod helpers; +mod instruments; +pub mod runner; +pub mod uploader; + +pub mod config; +pub mod logger; + +fn show_banner() { + let banner = format!( + r#" + ______ __ _____ __ + / ____/____ ____/ // ___/ ____ ___ ___ ____/ / + / / / __ \ / __ / \__ \ / __ \ / _ \ / _ \ / __ / +/ /___ / /_/ // /_/ / ___/ // /_/ // __// __// /_/ / +\____/ \____/ \__,_/ /____// .___/ \___/ \___/ \__,_/ + https://codspeed.io /_/ runner v{} +"#, + VERSION + ); + println!("{}", banner); + debug!("codspeed-runner v{}", VERSION); +} + +#[derive(Args, Debug)] +pub struct RunArgs { + /// The upload URL to use for uploading the results, useful for on-premises installations + #[arg(long)] + pub upload_url: Option, + + /// The token to use for uploading the results, + #[arg(long, env = "CODSPEED_TOKEN")] + pub token: Option, + + /// The directory where the command will be executed. + #[arg(long)] + pub working_directory: Option, + + /// Comma-separated list of instruments to enable. Possible values: mongodb. + #[arg(long, value_delimiter = ',')] + pub instruments: Vec, + + /// The name of the environment variable that contains the MongoDB URI to patch. + /// If not provided, user will have to provide it dynamically through a CodSpeed integration. + /// + /// Only used if the `mongodb` instrument is enabled. + #[arg(long)] + pub mongo_uri_env_name: Option, + + /// Only for debugging purposes, skips the upload of the results + #[arg( + long, + default_value = "false", + hide = true, + env = "CODSPEED_SKIP_UPLOAD" + )] + pub skip_upload: bool, + + /// Only for debugging purposes, skips the setup of the runner + #[arg(long, default_value = "false", hide = true)] + pub skip_setup: bool, + + /// The bench command to run + pub command: Vec, +} + +#[cfg(test)] +impl RunArgs { + /// Constructs a new `RunArgs` with default values for testing purposes + pub fn test() -> Self { + Self { + upload_url: None, + token: None, + working_directory: None, + instruments: vec![], + mongo_uri_env_name: None, + skip_upload: false, + skip_setup: false, + command: vec![], + } + } +} + +pub async fn run(args: RunArgs) -> Result<()> { + let config = Config::try_from(args)?; + let provider = ci_provider::get_provider(&config)?; + let logger = Logger::new(&provider)?; + + show_banner(); + debug!("config: {:#?}", config); + + let run_data = runner::run(&config).await?; + + if !config.skip_upload { + start_group!("Upload the results"); + logger.persist_log_to_profile_folder(&run_data)?; + uploader::upload(&config, provider, &run_data).await?; + end_group!(); + } + + Ok(()) +} diff --git a/src/runner/check_system.rs b/src/run/runner/check_system.rs similarity index 100% rename from src/runner/check_system.rs rename to src/run/runner/check_system.rs diff --git a/src/runner/helpers/download_file.rs b/src/run/runner/helpers/download_file.rs similarity index 100% rename from src/runner/helpers/download_file.rs rename to src/run/runner/helpers/download_file.rs diff --git a/src/runner/helpers/ignored_objects_path.rs b/src/run/runner/helpers/ignored_objects_path.rs similarity index 100% rename from src/runner/helpers/ignored_objects_path.rs rename to src/run/runner/helpers/ignored_objects_path.rs diff --git a/src/runner/helpers/introspected_node/mod.rs b/src/run/runner/helpers/introspected_node/mod.rs similarity index 100% rename from src/runner/helpers/introspected_node/mod.rs rename to src/run/runner/helpers/introspected_node/mod.rs diff --git a/src/runner/helpers/introspected_node/node.sh b/src/run/runner/helpers/introspected_node/node.sh similarity index 100% rename from src/runner/helpers/introspected_node/node.sh rename to src/run/runner/helpers/introspected_node/node.sh diff --git a/src/runner/helpers/mod.rs b/src/run/runner/helpers/mod.rs similarity index 100% rename from src/runner/helpers/mod.rs rename to src/run/runner/helpers/mod.rs diff --git a/src/runner/helpers/perf_maps.rs b/src/run/runner/helpers/perf_maps.rs similarity index 100% rename from src/runner/helpers/perf_maps.rs rename to src/run/runner/helpers/perf_maps.rs diff --git a/src/runner/helpers/profile_folder.rs b/src/run/runner/helpers/profile_folder.rs similarity index 100% rename from src/runner/helpers/profile_folder.rs rename to src/run/runner/helpers/profile_folder.rs diff --git a/src/runner/mod.rs b/src/run/runner/mod.rs similarity index 100% rename from src/runner/mod.rs rename to src/run/runner/mod.rs diff --git a/src/runner/run.rs b/src/run/runner/run.rs similarity index 92% rename from src/runner/run.rs rename to src/run/runner/run.rs index 686d957..1f2a8d5 100644 --- a/src/runner/run.rs +++ b/src/run/runner/run.rs @@ -1,4 +1,6 @@ -use crate::{config::Config, instruments::mongo_tracer::MongoTracer, prelude::*}; +use crate::prelude::*; +use crate::run::{config::Config, instruments::mongo_tracer::MongoTracer}; + use std::path::PathBuf; use super::{ diff --git a/src/runner/setup.rs b/src/run/runner/setup.rs similarity index 97% rename from src/runner/setup.rs rename to src/run/runner/setup.rs index 3c38d4f..c75dd3c 100644 --- a/src/runner/setup.rs +++ b/src/run/runner/setup.rs @@ -8,7 +8,8 @@ use lazy_static::lazy_static; use url::Url; use super::{check_system::SystemInfo, helpers::download_file::download_file}; -use crate::{config::Config, prelude::*, MONGODB_TRACER_VERSION, VALGRIND_CODSPEED_VERSION}; +use crate::run::config::Config; +use crate::{prelude::*, MONGODB_TRACER_VERSION, VALGRIND_CODSPEED_VERSION}; /// Run a command with sudo if available fn run_with_sudo(command_args: &[&str]) -> Result<()> { diff --git a/src/runner/valgrind.rs b/src/run/runner/valgrind.rs similarity index 96% rename from src/runner/valgrind.rs rename to src/run/runner/valgrind.rs index 0877c80..962fd83 100644 --- a/src/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -1,8 +1,9 @@ -use crate::config::Config; -use crate::instruments::mongo_tracer::MongoTracer; use crate::prelude::*; -use crate::runner::helpers::ignored_objects_path::get_objects_path_to_ignore; -use crate::runner::helpers::introspected_node::setup_introspected_node; +use crate::run::{ + config::Config, instruments::mongo_tracer::MongoTracer, + runner::helpers::ignored_objects_path::get_objects_path_to_ignore, + runner::helpers::introspected_node::setup_introspected_node, +}; use lazy_static::lazy_static; use std::fs::canonicalize; use std::io::{Read, Write}; diff --git a/src/uploader/interfaces.rs b/src/run/uploader/interfaces.rs similarity index 90% rename from src/uploader/interfaces.rs rename to src/run/uploader/interfaces.rs index 3d3c102..9297a47 100644 --- a/src/uploader/interfaces.rs +++ b/src/run/uploader/interfaces.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{ci_provider::interfaces::ProviderMetadata, instruments::InstrumentNames}; +use crate::run::{ci_provider::interfaces::ProviderMetadata, instruments::InstrumentNames}; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/src/uploader/mod.rs b/src/run/uploader/mod.rs similarity index 100% rename from src/uploader/mod.rs rename to src/run/uploader/mod.rs diff --git a/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap b/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap new file mode 100644 index 0000000..d41c738 --- /dev/null +++ b/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap @@ -0,0 +1,33 @@ +--- +source: src/run/uploader/upload_metadata.rs +expression: upload_metadata +--- +{ + "version": 2, + "tokenless": true, + "profileMd5": "jp/k05RKuqP3ERQuIIvx4Q==", + "runner": { + "name": "codspeed-runner", + "version": "2.1.0", + "instruments": [ + "MongoDB" + ] + }, + "platform": "github-actions", + "commitHash": "5bd77cb0da72bef094893ed45fb793ff16ecfbe3", + "ref": "refs/pull/29/merge", + "headRef": "chore/native-action-runner", + "baseRef": "main", + "owner": "CodSpeedHQ", + "repository": "codspeed-node", + "event": "pull_request", + "ghData": { + "runId": 7044765741, + "job": "codspeed", + "sender": { + "id": 19605940, + "login": "adriencaccia" + } + }, + "repositoryRootPath": "/home/runner/work/codspeed-node/codspeed-node/" +} diff --git a/src/uploader/upload.rs b/src/run/uploader/upload.rs similarity index 94% rename from src/uploader/upload.rs rename to src/run/uploader/upload.rs index a76876f..ae375ed 100644 --- a/src/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -1,7 +1,5 @@ -use crate::{ - ci_provider::CIProvider, config::Config, prelude::*, request_client::REQUEST_CLIENT, - runner::RunData, uploader::UploadError, -}; +use crate::run::{ci_provider::CIProvider, config::Config, runner::RunData, uploader::UploadError}; +use crate::{prelude::*, request_client::REQUEST_CLIENT}; use async_compression::tokio::write::GzipEncoder; use base64::{engine::general_purpose, Engine as _}; use tokio::io::AsyncWriteExt; @@ -110,7 +108,7 @@ mod tests { use url::Url; use super::*; - use crate::runner::RunData; + use crate::run::runner::RunData; use std::path::PathBuf; // TODO: remove the ignore when implementing network mocking @@ -158,7 +156,7 @@ mod tests { ("VERSION", Some("0.1.0")), ], async { - let provider = crate::ci_provider::get_provider(&config).unwrap(); + let provider = crate::run::ci_provider::get_provider(&config).unwrap(); upload(&config, provider, &run_data).await.unwrap(); }, ) diff --git a/src/uploader/upload_metadata.rs b/src/run/uploader/upload_metadata.rs similarity index 98% rename from src/uploader/upload_metadata.rs rename to src/run/uploader/upload_metadata.rs index 425f0d8..94d36d5 100644 --- a/src/uploader/upload_metadata.rs +++ b/src/run/uploader/upload_metadata.rs @@ -13,7 +13,7 @@ impl UploadMetadata { mod tests { use insta::assert_json_snapshot; - use crate::{ + use crate::run::{ ci_provider::interfaces::{GhData, ProviderMetadata, RunEvent, Sender}, instruments::InstrumentNames, uploader::{Runner, UploadMetadata}, From 6f3aeb70f6fbe2d1ad00d60d3f94fd905da93a92 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 28 May 2024 11:08:42 -0400 Subject: [PATCH 03/26] feat(auth): first implementation of auth login command --- Cargo.lock | 181 ++++++++++++++++++++++------ Cargo.toml | 7 +- src/api_client.rs | 89 ++++++++++++++ src/app.rs | 12 +- src/auth.rs | 78 ++++++++++++ src/config.rs | 73 +++++++++++ src/logger.rs | 21 ++++ src/main.rs | 14 ++- src/queries/ConsumeLoginSession.gql | 5 + src/queries/CreateLoginSession.gql | 6 + 10 files changed, 441 insertions(+), 45 deletions(-) create mode 100644 src/api_client.rs create mode 100644 src/auth.rs create mode 100644 src/config.rs create mode 100644 src/logger.rs create mode 100644 src/queries/ConsumeLoginSession.gql create mode 100644 src/queries/CreateLoginSession.gql diff --git a/Cargo.lock b/Cargo.lock index fde62a8..e7b57f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,23 +43,24 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" @@ -116,7 +117,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -238,7 +239,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -256,11 +257,13 @@ dependencies = [ "base64", "clap", "git2", + "gql_client", "insta", "itertools", "lazy_static", "log", "md5", + "nestify", "rand", "regex", "reqwest", @@ -268,6 +271,7 @@ dependencies = [ "reqwest-retry", "serde", "serde_json", + "serde_yaml", "sha256", "simplelog", "temp-env", @@ -509,7 +513,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -586,6 +590,17 @@ dependencies = [ "url", ] +[[package]] +name = "gql_client" +version = "1.0.7" +source = "git+https://github.com/CodSpeedHQ/gql-client-rs#83610cc89083cf3b18d7b7e539e76e82121b6ebb" +dependencies = [ + "log", + "reqwest", + "serde", + "serde_json", +] + [[package]] name = "h2" version = "0.3.21" @@ -745,9 +760,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -787,6 +802,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.11.0" @@ -798,9 +819,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" @@ -896,9 +917,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "md5" @@ -908,9 +929,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -966,6 +987,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nestify" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d7249f7122d4e8a40f3b1b1850b763d2f864bf8e4b712427f024f8a167ea17" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1019,7 +1052,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1135,7 +1168,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1179,20 +1212,44 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1394,9 +1451,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -1438,31 +1495,31 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -1480,6 +1537,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.2.6", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1517,6 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", + "termcolor", "time", ] @@ -1563,9 +1634,19 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.39" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1624,6 +1705,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -1641,7 +1731,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1714,7 +1804,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1792,7 +1882,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1852,6 +1942,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "url" version = "2.4.1" @@ -1917,7 +2013,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -1951,7 +2047,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2016,6 +2112,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 51d5b94..33b15ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,9 +30,14 @@ tokio-tar = "0.3.1" md5 = "0.7.0" base64 = "0.21.0" async-compression = { version = "0.4.5", features = ["tokio", "gzip"] } -simplelog = { version = "0.12.1", default-features = false } +simplelog = { version = "0.12.1", default-features = false, features = [ + "termcolor", +] } tempfile = "3.10.0" git2 = "0.18.3" +nestify = "0.3.3" +gql_client = { git = "https://github.com/CodSpeedHQ/gql-client-rs" } +serde_yaml = "0.9.34" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/api_client.rs b/src/api_client.rs new file mode 100644 index 0000000..698c28c --- /dev/null +++ b/src/api_client.rs @@ -0,0 +1,89 @@ +use crate::app::Cli; +use crate::prelude::*; +use gql_client::{Client as GQLClient, ClientConfig}; +use nestify::nest; +use serde::{Deserialize, Serialize}; + +pub struct CodSpeedAPIClient { + pub gql_client: GQLClient, +} + +impl From<&Cli> for CodSpeedAPIClient { + fn from(args: &Cli) -> Self { + Self { + gql_client: build_gql_api_client(args.api_url.clone()), + } + } +} + +const CODSPEED_GRAPHQL_ENDPOINT: &str = "https://gql.codspeed.io/"; + +fn build_gql_api_client(api_url: Option) -> GQLClient { + let endpoint = api_url.unwrap_or_else(|| CODSPEED_GRAPHQL_ENDPOINT.to_string()); + + GQLClient::new_with_config(ClientConfig { + endpoint, + timeout: Some(10), + headers: Default::default(), + proxy: None, + }) +} + +nest! { + #[derive(Debug, Deserialize, Serialize)]* + #[serde(rename_all = "camelCase")]* + struct CreateLoginSessionData { + create_login_session: pub struct CreateLoginSessionPayload { + pub callback_url: String, + pub session_id: String, + } + } +} + +nest! { + #[derive(Debug, Deserialize, Serialize)]* + #[serde(rename_all = "camelCase")]* + struct ConsumeLoginSessionData { + consume_login_session: pub struct ConsumeLoginSessionPayload { + pub token: Option + } + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ConsumeLoginSessionVars { + session_id: String, +} + +impl CodSpeedAPIClient { + pub async fn create_login_session(&self) -> Result { + let response = self + .gql_client + .query_unwrap::(include_str!("queries/CreateLoginSession.gql")) + .await; + match response { + Ok(response) => Ok(response.create_login_session), + Err(err) => bail!("Failed to create login session: {}", err), + } + } + + pub async fn consume_login_session( + &self, + session_id: &str, + ) -> Result { + let response = self + .gql_client + .query_with_vars_unwrap::( + include_str!("queries/ConsumeLoginSession.gql"), + ConsumeLoginSessionVars { + session_id: session_id.to_string(), + }, + ) + .await; + match response { + Ok(response) => Ok(response.consume_login_session), + Err(err) => bail!("Failed to use login session: {}", err), + } + } +} diff --git a/src/app.rs b/src/app.rs index 2ac8ee2..1734c2b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,12 @@ -use crate::{prelude::*, run}; +use crate::{api_client::CodSpeedAPIClient, auth, prelude::*, run}; use clap::{Parser, Subcommand}; #[derive(Parser, Debug)] -struct Cli { +pub struct Cli { + /// The URL of the CodSpeed GraphQL API + #[arg(long, env = "CODSPEED_API_URL", global = true, hide = true)] + pub api_url: Option, + #[command(subcommand)] command: Commands, } @@ -11,13 +15,17 @@ struct Cli { enum Commands { /// Run the bench command and upload the results to CodSpeed Run(run::RunArgs), + /// Commands related to authentication with CodSpeed + Auth(auth::AuthArgs), } pub async fn run() -> Result<()> { let cli = Cli::parse(); + let api_client = CodSpeedAPIClient::from(&cli); match cli.command { Commands::Run(args) => run::run(args).await?, + Commands::Auth(args) => auth::run(args, &api_client).await?, } Ok(()) } diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..95ceac4 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,78 @@ +use std::time::Duration; + +use crate::logger::get_local_logger; +use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*}; +use clap::{Args, Subcommand}; +use simplelog::CombinedLogger; +use tokio::time::{sleep, Instant}; + +#[derive(Debug, Args)] +pub struct AuthArgs { + #[command(subcommand)] + command: AuthCommands, +} + +#[derive(Debug, Subcommand)] +enum AuthCommands { + /// Login to CodSpeed + Login, +} + +// TODO: tweak the logger to make it more user-friendly +fn init_logger() -> Result<()> { + let logger = get_local_logger(); + CombinedLogger::init(vec![logger])?; + Ok(()) +} + +pub async fn run(args: AuthArgs, api_client: &CodSpeedAPIClient) -> Result<()> { + init_logger()?; + + match args.command { + AuthCommands::Login => login(api_client).await?, + } + Ok(()) +} + +const LOGIN_SESSION_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 minutes + +async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { + debug!("Login to CodSpeed"); + debug!("Creating login session..."); + let login_session_payload = api_client.create_login_session().await?; + info!( + "Login session created, open the following URL in your browser: {}", + login_session_payload.callback_url + ); + + info!("Waiting for the login to be completed..."); + let token; + let start = Instant::now(); + loop { + if start.elapsed() > LOGIN_SESSION_MAX_DURATION { + bail!("Login session expired, please try again"); + } + + match api_client + .consume_login_session(&login_session_payload.session_id) + .await? + .token + { + Some(token_from_api) => { + token = token_from_api; + break; + } + None => sleep(Duration::from_secs(5)).await, + } + } + debug!("Login completed"); + + let mut config = CodSpeedConfig::load()?; + config.auth.token = Some(token); + config.persist()?; + debug!("Token saved to configuration file"); + + info!("Login successful, your are now authenticated on CodSpeed"); + + Ok(()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..3fc6435 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,73 @@ +use std::{env, fs, path::PathBuf}; + +use crate::prelude::*; +use nestify::nest; +use serde::{Deserialize, Serialize}; + +nest! { + #[derive(Debug, Deserialize, Serialize)]* + #[serde(rename_all = "kebab-case")]* + pub struct CodSpeedConfig { + pub auth: pub struct AuthConfig { + pub token: Option, + } + } +} + +/// Get the path to the configuration file, following the XDG Base Directory Specification +/// at https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +fn get_configuration_file_path() -> PathBuf { + let config_dir = env::var("XDG_CONFIG_HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| { + let home = env::var("HOME").expect("HOME env variable not set"); + PathBuf::from(home).join(".config") + }); + let config_dir = config_dir.join("codspeed"); + config_dir.join("config.yaml") +} + +impl Default for CodSpeedConfig { + fn default() -> Self { + Self { + auth: AuthConfig { token: None }, + } + } +} + +impl CodSpeedConfig { + /// Load the configuration. If it does not exist, store and return a default configuration + pub fn load() -> Result { + let config_path = get_configuration_file_path(); + + match fs::read(&config_path) { + Ok(config_str) => { + let config = serde_yaml::from_slice(&config_str).context(format!( + "Failed to parse CodSpeed config at {}", + config_path.display() + ))?; + debug!("Config loaded from {}", config_path.display()); + Ok(config) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + debug!("Config file not found at {}", config_path.display()); + let config = CodSpeedConfig::default(); + config.persist()?; + Ok(config) + } + Err(e) => bail!("Failed to load config: {}", e), + } + } + + /// Persist changes to the configuration + pub fn persist(&self) -> Result<()> { + let config_path = get_configuration_file_path(); + fs::create_dir_all(config_path.parent().unwrap())?; + + let config_str = serde_yaml::to_string(self)?; + fs::write(&config_path, config_str)?; + debug!("Config written to {}", config_path.display()); + + Ok(()) + } +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..a027304 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,21 @@ +use std::env; + +use simplelog::{ConfigBuilder, SharedLogger}; + +pub fn get_local_logger() -> Box { + let log_level = env::var("CODSPEED_LOG") + .ok() + .and_then(|log_level| log_level.parse::().ok()) + .unwrap_or(log::LevelFilter::Info); + + let config = ConfigBuilder::new() + .set_time_level(log::LevelFilter::Debug) + .build(); + + simplelog::TermLogger::new( + log_level, + config, + simplelog::TerminalMode::Mixed, + simplelog::ColorChoice::Auto, + ) +} diff --git a/src/main.rs b/src/main.rs index 91113c6..b46efe8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,8 @@ +mod api_client; mod app; +mod auth; +mod config; +mod logger; mod prelude; mod request_client; mod run; @@ -15,10 +19,12 @@ pub const VALGRIND_CODSPEED_VERSION: &str = "3.21.0-0codspeed1"; async fn main() { let res = crate::app::run().await; if let Err(err) = res { - if log_enabled!(log::Level::Error) { - error!("Error {}", err); - } else { - eprintln!("Error {}", err); + for cause in err.chain() { + if log_enabled!(log::Level::Error) { + error!("Error {}", cause); + } else { + eprintln!("Error {}", cause); + } } if log_enabled!(log::Level::Debug) { for e in err.chain().skip(1) { diff --git a/src/queries/ConsumeLoginSession.gql b/src/queries/ConsumeLoginSession.gql new file mode 100644 index 0000000..b246172 --- /dev/null +++ b/src/queries/ConsumeLoginSession.gql @@ -0,0 +1,5 @@ +mutation ConsumeLoginSession($sessionId: String!) { + consumeLoginSession(sessionId: $sessionId) { + token + } +} diff --git a/src/queries/CreateLoginSession.gql b/src/queries/CreateLoginSession.gql new file mode 100644 index 0000000..77f2dbb --- /dev/null +++ b/src/queries/CreateLoginSession.gql @@ -0,0 +1,6 @@ +mutation CreateLoginSession { + createLoginSession { + callbackUrl + sessionId + } +} From 59da1c1a3739c00c0bab0d54247c5a22c1d12a59 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 28 May 2024 17:57:33 -0400 Subject: [PATCH 04/26] chore(runner): remove useless code in GitHubActionsProvide commit_hash is now retrieved from the git repository --- .../ci_provider/github_actions/provider.rs | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/run/ci_provider/github_actions/provider.rs b/src/run/ci_provider/github_actions/provider.rs index 3cd2cb5..cfa6b5c 100644 --- a/src/run/ci_provider/github_actions/provider.rs +++ b/src/run/ci_provider/github_actions/provider.rs @@ -23,7 +23,6 @@ pub struct GitHubActionsProvider { pub ref_: String, pub head_ref: Option, pub base_ref: Option, - pub commit_hash: String, pub gh_data: GhData, pub event: RunEvent, pub repository_root_path: String, @@ -49,7 +48,7 @@ impl TryFrom<&Config> for GitHubActionsProvider { let (owner, repository) = Self::get_owner_and_repository()?; let ref_ = get_env_variable("GITHUB_REF")?; let is_pr = PR_REF_REGEX.is_match(&ref_); - let (head_ref, commit_hash) = if is_pr { + let head_ref = if is_pr { let github_event_path = get_env_variable("GITHUB_EVENT_PATH")?; let github_event = fs::read_to_string(github_event_path)?; let github_event: Value = serde_json::from_str(&github_event) @@ -70,12 +69,9 @@ impl TryFrom<&Config> for GitHubActionsProvider { } else { pull_request["head"]["ref"].as_str().unwrap().to_owned() }; - ( - Some(head_ref), - pull_request["head"]["sha"].as_str().unwrap().to_owned(), - ) + Some(head_ref) } else { - (None, get_env_variable("GITHUB_SHA")?) + None }; let github_event_name = get_env_variable("GITHUB_EVENT_NAME")?; @@ -95,7 +91,6 @@ impl TryFrom<&Config> for GitHubActionsProvider { owner, repository: repository.clone(), ref_, - commit_hash, head_ref, event, gh_data: GhData { @@ -187,7 +182,6 @@ mod tests { ("GITHUB_REF", Some("refs/heads/main")), ("GITHUB_REPOSITORY", Some("owner/repository")), ("GITHUB_RUN_ID", Some("1234567890")), - ("GITHUB_SHA", Some("1234567890abcdef")), ], || { let config = Config { @@ -200,7 +194,6 @@ mod tests { assert_eq!(github_actions_provider.ref_, "refs/heads/main"); assert_eq!(github_actions_provider.base_ref, Some("main".into())); assert_eq!(github_actions_provider.head_ref, None); - assert_eq!(github_actions_provider.commit_hash, "1234567890abcdef"); assert_eq!(github_actions_provider.event, RunEvent::Push); assert_eq!(github_actions_provider.gh_data.job, "job"); assert_eq!(github_actions_provider.gh_data.run_id, 1234567890); @@ -245,10 +238,6 @@ mod tests { ("GITHUB_REF", Some("refs/pull/22/merge")), ("GITHUB_REPOSITORY", Some("my-org/adrien-python-test")), ("GITHUB_RUN_ID", Some("6957110437")), - ( - "GITHUB_SHA", - Some("5bd77cb0da72bef094893ed45fb793ff16ecfbe3"), - ), ("VERSION", Some("0.1.0")), ], || { @@ -293,10 +282,6 @@ mod tests { ("GITHUB_REF", Some("refs/pull/22/merge")), ("GITHUB_REPOSITORY", Some("my-org/adrien-python-test")), ("GITHUB_RUN_ID", Some("6957110437")), - ( - "GITHUB_SHA", - Some("5bd77cb0da72bef094893ed45fb793ff16ecfbe3"), - ), ("VERSION", Some("0.1.0")), ], || { From ec91e65217cd37745710249030bff18bbc54da13 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 29 May 2024 15:29:06 -0400 Subject: [PATCH 05/26] feat(runner): add local provider --- src/run/ci_provider/interfaces.rs | 1 + src/run/ci_provider/local/mod.rs | 3 + src/run/ci_provider/local/provider.rs | 165 ++++++++++++++++++ ...l__provider__tests__provider_metadata.snap | 12 ++ src/run/ci_provider/mod.rs | 14 +- src/run/ci_provider/provider.rs | 3 +- 6 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 src/run/ci_provider/local/mod.rs create mode 100644 src/run/ci_provider/local/provider.rs create mode 100644 src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap diff --git a/src/run/ci_provider/interfaces.rs b/src/run/ci_provider/interfaces.rs index d9fb729..e802da8 100644 --- a/src/run/ci_provider/interfaces.rs +++ b/src/run/ci_provider/interfaces.rs @@ -21,6 +21,7 @@ pub enum RunEvent { PullRequest, WorkflowDispatch, Schedule, + Local, } #[derive(Deserialize, Serialize, Debug, Clone)] diff --git a/src/run/ci_provider/local/mod.rs b/src/run/ci_provider/local/mod.rs new file mode 100644 index 0000000..7822295 --- /dev/null +++ b/src/run/ci_provider/local/mod.rs @@ -0,0 +1,3 @@ +mod provider; + +pub use provider::LocalProvider; diff --git a/src/run/ci_provider/local/provider.rs b/src/run/ci_provider/local/provider.rs new file mode 100644 index 0000000..088bd49 --- /dev/null +++ b/src/run/ci_provider/local/provider.rs @@ -0,0 +1,165 @@ +use git2::Repository; +use lazy_static::lazy_static; +use simplelog::SharedLogger; + +use crate::logger::get_local_logger; +use crate::prelude::*; +use crate::run::{ + ci_provider::{ + interfaces::{ProviderMetadata, RunEvent}, + provider::{CIProvider, CIProviderDetector}, + }, + config::Config, + helpers::find_repository_root, +}; + +#[derive(Debug)] +pub struct LocalProvider { + pub ref_: String, + pub owner: String, + pub repository: String, + pub head_ref: Option, + pub base_ref: Option, + pub event: RunEvent, + pub repository_root_path: String, +} + +impl LocalProvider {} + +lazy_static! { + static ref REMOTE_REGEX: regex::Regex = + regex::Regex::new(r"[:/](?P[^/]+)/(?P[^/]+)\.git").unwrap(); +} + +fn extract_owner_and_repository_from_remote_url(remote_url: &str) -> Result<(String, String)> { + let captures = REMOTE_REGEX.captures(remote_url).ok_or_else(|| { + anyhow!( + "Could not extract owner and repository from remote url: {}", + remote_url + ) + })?; + + let owner = captures.name("owner").unwrap().as_str(); + let repository = captures.name("repository").unwrap().as_str(); + + Ok((owner.to_string(), repository.to_string())) +} + +impl TryFrom<&Config> for LocalProvider { + type Error = Error; + fn try_from(_config: &Config) -> Result { + 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 => bail!("Could not find repository root, please make sure you are running the command from inside a git repository"), + }; + + let git_repository = Repository::open(repository_root_path.clone()).context(format!( + "Failed to open repository at path: {}", + repository_root_path + ))?; + + let remote = git_repository.find_remote("origin")?; + let (owner, repository) = + extract_owner_and_repository_from_remote_url(remote.url().unwrap())?; + + let head = git_repository.head().context("Failed to get HEAD")?; + let ref_ = head + .peel_to_commit() + .context("Failed to get HEAD commit")? + .id() + .to_string(); + let head_ref = if head.is_branch() { + let branch = head.shorthand().context("Failed to get HEAD branch name")?; + Some(branch.to_string()) + } else { + None + }; + + Ok(Self { + ref_, + head_ref, + base_ref: None, + owner, + repository, + event: RunEvent::Local, + repository_root_path, + }) + } +} + +impl CIProviderDetector for LocalProvider { + fn detect() -> bool { + true + } +} + +impl CIProvider for LocalProvider { + fn get_logger(&self) -> Box { + get_local_logger() + } + + fn get_provider_name(&self) -> &'static str { + "Local" + } + + fn get_provider_slug(&self) -> &'static str { + "local" + } + + fn get_provider_metadata(&self) -> Result { + Ok(ProviderMetadata { + base_ref: self.base_ref.clone(), + head_ref: self.head_ref.clone(), + event: self.event.clone(), + gh_data: None, + owner: self.owner.clone(), + repository: self.repository.clone(), + ref_: self.ref_.clone(), + repository_root_path: self.repository_root_path.clone(), + }) + } +} + +#[cfg(test)] +mod tests { + // use crate::VERSION; + // use insta::assert_json_snapshot; + + use super::*; + + #[test] + fn test_extract_owner_and_repository_from_remote_url() { + let remote_urls = [ + "git@github.com:CodSpeedHQ/runner.git", + "https://github.com/CodSpeedHQ/runner.git", + ]; + for remote_url in remote_urls.iter() { + let (owner, repository) = + extract_owner_and_repository_from_remote_url(remote_url).unwrap(); + assert_eq!(owner, "CodSpeedHQ"); + assert_eq!(repository, "runner"); + } + } + + // TODO: uncomment later when we have a way to mock git repository + // #[test] + // fn test_provider_metadata() { + // let config = Config { + // token: Some("token".into()), + // ..Config::test() + // }; + // let local_provider = LocalProvider::try_from(&config).unwrap(); + // let provider_metadata = local_provider.get_provider_metadata().unwrap(); + + // assert_json_snapshot!(provider_metadata, { + // ".runner.version" => insta::dynamic_redaction(|value,_path| { + // assert_eq!(value.as_str().unwrap(), VERSION.to_string()); + // "[version]" + // }), + // }); + // } +} diff --git a/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap b/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap new file mode 100644 index 0000000..7388234 --- /dev/null +++ b/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap @@ -0,0 +1,12 @@ +--- +source: src/run/ci_provider/local/provider.rs +expression: provider_metadata +--- +{ + "ref": "18ec1d64b5f25fb27451d89eee03cc569bd6bbb1", + "headRef": "feat/branch", + "owner": "my-org", + "repository": "adrien-python-test", + "event": "local", + "repositoryRootPath": "/Users/adrien/projects/my-org/adrien-python-test" +} diff --git a/src/run/ci_provider/mod.rs b/src/run/ci_provider/mod.rs index 7ebe1c2..ae06ed8 100644 --- a/src/run/ci_provider/mod.rs +++ b/src/run/ci_provider/mod.rs @@ -2,10 +2,12 @@ pub mod interfaces; pub mod logger; mod provider; +use buildkite::BuildkiteProvider; +use github_actions::GitHubActionsProvider; +use local::LocalProvider; +use provider::CIProviderDetector; + use crate::prelude::*; -use crate::run::ci_provider::buildkite::BuildkiteProvider; -use crate::run::ci_provider::github_actions::GitHubActionsProvider; -use crate::run::ci_provider::provider::CIProviderDetector; use crate::run::config::Config; pub use self::provider::CIProvider; @@ -13,6 +15,7 @@ pub use self::provider::CIProvider; // Provider implementations mod buildkite; mod github_actions; +mod local; pub fn get_provider(config: &Config) -> Result> { if BuildkiteProvider::detect() { @@ -25,5 +28,10 @@ pub fn get_provider(config: &Config) -> Result> { return Ok(Box::new(provider)); } + if LocalProvider::detect() { + let provider = LocalProvider::try_from(config)?; + return Ok(Box::new(provider)); + } + bail!("No CI provider detected") } diff --git a/src/run/ci_provider/provider.rs b/src/run/ci_provider/provider.rs index 4de9a23..4e5c769 100644 --- a/src/run/ci_provider/provider.rs +++ b/src/run/ci_provider/provider.rs @@ -19,7 +19,8 @@ fn get_commit_hash(repository_root_path: &str) -> Result { ))?; let commit_hash = repo - .revparse_single("HEAD") + .head() + .and_then(|head| head.peel_to_commit()) .context("Failed to get HEAD commit")? .id() .to_string(); From 692b6339c0e644fd8fbb200ecb193b659130f0bc Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Fri, 31 May 2024 18:05:47 -0400 Subject: [PATCH 06/26] feat(runner): handle local run --- src/api_client.rs | 108 ++++++++++++++++++++++++---- src/app.rs | 14 ++-- src/queries/FetchLocalRunReport.gql | 14 ++++ src/run/config.rs | 6 ++ src/run/mod.rs | 28 ++++++-- src/run/poll_results.rs | 66 +++++++++++++++++ src/run/uploader/upload.rs | 15 ++-- 7 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 src/queries/FetchLocalRunReport.gql create mode 100644 src/run/poll_results.rs diff --git a/src/api_client.rs b/src/api_client.rs index 698c28c..7e268f8 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -1,5 +1,5 @@ -use crate::app::Cli; use crate::prelude::*; +use crate::{app::Cli, config::CodSpeedConfig}; use gql_client::{Client as GQLClient, ClientConfig}; use nestify::nest; use serde::{Deserialize, Serialize}; @@ -8,23 +8,31 @@ pub struct CodSpeedAPIClient { pub gql_client: GQLClient, } -impl From<&Cli> for CodSpeedAPIClient { - fn from(args: &Cli) -> Self { - Self { - gql_client: build_gql_api_client(args.api_url.clone()), - } +impl TryFrom<&Cli> for CodSpeedAPIClient { + type Error = Error; + fn try_from(args: &Cli) -> Result { + let codspeed_config = CodSpeedConfig::load()?; + + Ok(Self { + gql_client: build_gql_api_client(&codspeed_config, args.api_url.clone()), + }) } } -const CODSPEED_GRAPHQL_ENDPOINT: &str = "https://gql.codspeed.io/"; - -fn build_gql_api_client(api_url: Option) -> GQLClient { - let endpoint = api_url.unwrap_or_else(|| CODSPEED_GRAPHQL_ENDPOINT.to_string()); +fn build_gql_api_client(codspeed_config: &CodSpeedConfig, api_url: String) -> GQLClient { + let headers = match &codspeed_config.auth.token { + Some(token) => { + let mut headers = std::collections::HashMap::new(); + headers.insert("Authorization".to_string(), token.to_string()); + headers + } + None => Default::default(), + }; GQLClient::new_with_config(ClientConfig { - endpoint, + endpoint: api_url, timeout: Some(10), - headers: Default::default(), + headers: Some(headers), proxy: None, }) } @@ -40,6 +48,11 @@ nest! { } } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ConsumeLoginSessionVars { + session_id: String, +} nest! { #[derive(Debug, Deserialize, Serialize)]* #[serde(rename_all = "camelCase")]* @@ -50,10 +63,50 @@ nest! { } } -#[derive(Serialize)] +#[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] -struct ConsumeLoginSessionVars { - session_id: String, +pub struct FetchLocalRunReportVars { + pub owner: String, + pub name: String, + pub run_id: String, +} +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub enum ReportConclusion { + AcknowledgedFailure, + Failure, + MissingBaseRun, + Success, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FetchLocalRunReportHeadReport { + pub id: String, + pub impact: Option, + pub conclusion: ReportConclusion, +} +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RunStatus { + Pending, + Processing, + Completed, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FetchLocalRunReportRun { + pub id: String, + pub status: RunStatus, + pub url: String, + pub head_reports: Vec, +} +nest! { + #[derive(Debug, Deserialize, Serialize)]* + #[serde(rename_all = "camelCase")]* + struct FetchLocalRunReportData { + repository: pub struct FetchLocalRunReportRepository { + pub runs: Vec, + } + } } impl CodSpeedAPIClient { @@ -86,4 +139,29 @@ impl CodSpeedAPIClient { Err(err) => bail!("Failed to use login session: {}", err), } } + + pub async fn fetch_local_run_report( + &self, + vars: FetchLocalRunReportVars, + ) -> Result { + let response = self + .gql_client + .query_with_vars_unwrap::( + include_str!("queries/FetchLocalRunReport.gql"), + vars.clone(), + ) + .await; + match response { + Ok(response) => match response.repository.runs.into_iter().next() { + Some(run) => Ok(run), + None => bail!( + "No runs found for owner: {}, name: {}, run_id: {}", + vars.owner, + vars.name, + vars.run_id + ), + }, + Err(err) => bail!("Failed to fetch local run report: {}", err), + } + } } diff --git a/src/app.rs b/src/app.rs index 1734c2b..ac84f0a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,8 +4,14 @@ use clap::{Parser, Subcommand}; #[derive(Parser, Debug)] pub struct Cli { /// The URL of the CodSpeed GraphQL API - #[arg(long, env = "CODSPEED_API_URL", global = true, hide = true)] - pub api_url: Option, + #[arg( + long, + env = "CODSPEED_API_URL", + global = true, + hide = true, + default_value = "https://gql.codspeed.io/" + )] + pub api_url: String, #[command(subcommand)] command: Commands, @@ -21,10 +27,10 @@ enum Commands { pub async fn run() -> Result<()> { let cli = Cli::parse(); - let api_client = CodSpeedAPIClient::from(&cli); + let api_client = CodSpeedAPIClient::try_from(&cli)?; match cli.command { - Commands::Run(args) => run::run(args).await?, + Commands::Run(args) => run::run(args, &api_client).await?, Commands::Auth(args) => auth::run(args, &api_client).await?, } Ok(()) diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql new file mode 100644 index 0000000..9df48e0 --- /dev/null +++ b/src/queries/FetchLocalRunReport.gql @@ -0,0 +1,14 @@ +query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { + repository(owner: $owner, name: $name) { + runs(where: { id: { equals: $runId } }) { + id + status + url + headReports { + id + impact + conclusion + } + } + } +} diff --git a/src/run/config.rs b/src/run/config.rs index 1819056..6ab959a 100644 --- a/src/run/config.rs +++ b/src/run/config.rs @@ -17,6 +17,12 @@ pub struct Config { pub skip_setup: bool, } +impl Config { + pub fn set_token(&mut self, token: Option) { + self.token = token; + } +} + #[cfg(test)] impl Config { /// Constructs a new `Config` with default values for testing purposes diff --git a/src/run/mod.rs b/src/run/mod.rs index 0babfe7..c1f4f0e 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -1,3 +1,5 @@ +use crate::api_client::CodSpeedAPIClient; +use crate::config::CodSpeedConfig; use crate::prelude::*; use crate::run::{config::Config, logger::Logger}; use crate::VERSION; @@ -6,8 +8,9 @@ use clap::Args; pub mod ci_provider; mod helpers; mod instruments; -pub mod runner; -pub mod uploader; +mod poll_results; +mod runner; +mod uploader; pub mod config; pub mod logger; @@ -87,21 +90,36 @@ impl RunArgs { } } -pub async fn run(args: RunArgs) -> Result<()> { - let config = Config::try_from(args)?; +pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> { + let mut config = Config::try_from(args)?; let provider = ci_provider::get_provider(&config)?; + let codspeed_config = CodSpeedConfig::load()?; let logger = Logger::new(&provider)?; show_banner(); debug!("config: {:#?}", config); + if provider.get_provider_slug() == "local" { + if codspeed_config.auth.token.is_none() { + bail!("You have to authenticate the CLI first. Run `codspeed-runner auth login`."); + } + debug!("Using the token from the CodSpeed configuration file"); + config.set_token(codspeed_config.auth.token.clone()); + } + let run_data = runner::run(&config).await?; if !config.skip_upload { start_group!("Upload the results"); logger.persist_log_to_profile_folder(&run_data)?; - uploader::upload(&config, provider, &run_data).await?; + let upload_result = uploader::upload(&config, &provider, &run_data).await?; end_group!(); + + if provider.get_provider_slug() == "local" { + start_group!("Fetch the results"); + poll_results::poll_results(api_client, &provider, upload_result.run_id).await?; + end_group!(); + } } Ok(()) diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs new file mode 100644 index 0000000..a737217 --- /dev/null +++ b/src/run/poll_results.rs @@ -0,0 +1,66 @@ +use std::time::Duration; + +use tokio::time::{sleep, Instant}; + +use crate::api_client::{ + CodSpeedAPIClient, FetchLocalRunReportRun, FetchLocalRunReportVars, RunStatus, +}; +use crate::prelude::*; + +use super::ci_provider::CIProvider; + +const RUN_PROCESSING_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 minutes + +#[allow(clippy::borrowed_box)] +pub async fn poll_results( + api_client: &CodSpeedAPIClient, + provider: &Box, + run_id: String, +) -> Result<()> { + let start = Instant::now(); + let provider_metadata = provider.get_provider_metadata()?; + let owner = provider_metadata.owner; + let name = provider_metadata.repository; + let fetch_local_run_report_vars = FetchLocalRunReportVars { + owner: owner.clone(), + name: name.clone(), + run_id: run_id.clone(), + }; + + let run; + info!("Polling results..."); + loop { + if start.elapsed() > RUN_PROCESSING_MAX_DURATION { + bail!("Polling results timed out"); + } + + match api_client + .fetch_local_run_report(fetch_local_run_report_vars.clone()) + .await? + { + FetchLocalRunReportRun { status, .. } if status != RunStatus::Completed => { + sleep(Duration::from_secs(5)).await; + } + run_from_api => { + run = run_from_api; + break; + } + } + } + + let report = run + .head_reports + .into_iter() + .next() + .ok_or_else(|| anyhow!("No head report found in the run report"))?; + + info!("Report completed, here are the results:"); + if let Some(impact) = report.impact { + info!("Impact: {}%", (impact * 100.0).round()); + } + info!("Conclusion: {:?}", report.conclusion); + + info!("\nTo see the full report, visit: {}", response.run.url); + + Ok(()) +} diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index ae375ed..c51446f 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -74,11 +74,16 @@ async fn upload_archive_buffer( Ok(()) } +pub struct UploadResult { + pub run_id: String, +} + +#[allow(clippy::borrowed_box)] pub async fn upload( config: &Config, - provider: Box, + provider: &Box, run_data: &RunData, -) -> Result<()> { +) -> Result { let (archive_buffer, archive_hash) = get_profile_archive_buffer(run_data).await?; debug!("CI provider detected: {:#?}", provider.get_provider_name()); @@ -99,7 +104,9 @@ pub async fn upload( upload_archive_buffer(&upload_data, archive_buffer, &archive_hash).await?; info!("Results uploaded."); - Ok(()) + Ok(UploadResult { + run_id: upload_data.run_id, + }) } #[cfg(test)] @@ -157,7 +164,7 @@ mod tests { ], async { let provider = crate::run::ci_provider::get_provider(&config).unwrap(); - upload(&config, provider, &run_data).await.unwrap(); + upload(&config, &provider, &run_data).await.unwrap(); }, ) .await; From e3257e36593a3ada65a08edf04b001234340c107 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Fri, 31 May 2024 19:03:45 -0400 Subject: [PATCH 07/26] feat(runner/setup): do not install valgrind if correct version is installed --- src/run/runner/setup.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/run/runner/setup.rs b/src/run/runner/setup.rs index c75dd3c..680cede 100644 --- a/src/run/runner/setup.rs +++ b/src/run/runner/setup.rs @@ -91,7 +91,33 @@ lazy_static! { }; } +fn is_valgrind_installed() -> bool { + let is_valgrind_installed = Command::new("which") + .arg("valgrind") + .output() + .is_ok_and(|output| output.status.success()); + if !is_valgrind_installed { + return false; + } + + if let Ok(version_output) = Command::new("valgrind").arg("--version").output() { + if !version_output.status.success() { + return false; + } + + let version = String::from_utf8_lossy(&version_output.stdout); + // TODO: use only VALGRIND_CODSPEED_VERSION here, the other value is when valgrind has been built locally + version.contains("valgrind-3.21.0.codspeed") || version.contains(VALGRIND_CODSPEED_VERSION) + } else { + false + } +} + async fn install_valgrind(system_info: &SystemInfo) -> Result<()> { + if is_valgrind_installed() { + debug!("Valgrind is already installed with the correct version, skipping installation"); + return Ok(()); + } debug!("Installing valgrind"); let valgrind_deb_url = format!( "https://github.com/CodSpeedHQ/valgrind-codspeed/releases/download/{}/{}", From 9e81bc1f4e068267498cd9fc15b99acfcb6492e1 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Thu, 6 Jun 2024 11:19:51 -0400 Subject: [PATCH 08/26] feat(runner): support arm64 arch --- src/run/runner/check_system.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/run/runner/check_system.rs b/src/run/runner/check_system.rs index 5f5d0d7..d51469d 100644 --- a/src/run/runner/check_system.rs +++ b/src/run/runner/check_system.rs @@ -70,8 +70,8 @@ pub fn check_system() -> Result { } let arch = get_arch()?; debug!("Arch: {}", arch); - if arch != "amd64" { - bail!("Only amd64 is supported at the moment"); + if arch != "amd64" && arch != "arm64" { + bail!("Only amd64 and arm64 are supported at the moment"); } Ok(SystemInfo { os, From e4fc87179556ff6809482360b9a6ab9719c0a77a Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Thu, 6 Jun 2024 21:45:28 +0000 Subject: [PATCH 09/26] feat: add system info to upload metadata runner property --- Cargo.lock | 92 +++++++++++++++- Cargo.toml | 1 + src/run/check_system.rs | 83 ++++++++++++++ src/run/ci_provider/provider.rs | 11 +- src/run/mod.rs | 6 +- src/run/runner/check_system.rs | 81 -------------- src/run/runner/mod.rs | 1 - src/run/runner/run.rs | 10 +- src/run/runner/setup.rs | 103 +++++++----------- src/run/uploader/interfaces.rs | 7 +- ...ad_metadata__tests__get_metadata_hash.snap | 9 +- src/run/uploader/upload.rs | 14 ++- src/run/uploader/upload_metadata.rs | 6 +- 13 files changed, 261 insertions(+), 163 deletions(-) create mode 100644 src/run/check_system.rs delete mode 100644 src/run/runner/check_system.rs diff --git a/Cargo.lock b/Cargo.lock index e7b57f3..589f4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,7 @@ dependencies = [ "serde_yaml", "sha256", "simplelog", + "sysinfo", "temp-env", "tempfile", "tokio", @@ -333,6 +334,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -726,7 +752,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -999,6 +1025,15 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1284,6 +1319,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1653,6 +1708,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sysinfo" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "serde", + "windows", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2127,6 +2198,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.0", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -2136,6 +2217,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 33b15ad..90cc673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ git2 = "0.18.3" nestify = "0.3.3" gql_client = { git = "https://github.com/CodSpeedHQ/gql-client-rs" } serde_yaml = "0.9.34" +sysinfo = { version = "0.30.12", features = ["serde"] } [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/run/check_system.rs b/src/run/check_system.rs new file mode 100644 index 0000000..ffd182b --- /dev/null +++ b/src/run/check_system.rs @@ -0,0 +1,83 @@ +use std::process::Command; + +use serde::{Deserialize, Serialize}; +use sysinfo::System; + +use crate::prelude::*; + +fn get_user() -> Result { + let user_output = Command::new("whoami") + .output() + .map_err(|_| anyhow!("Failed to get user info"))?; + if !user_output.status.success() { + bail!("Failed to get user info"); + } + let output_str = + String::from_utf8(user_output.stdout).map_err(|_| anyhow!("Failed to parse user info"))?; + Ok(output_str.trim().to_string()) +} + +#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SystemInfo { + pub os: String, + pub os_version: String, + pub arch: String, + pub host: String, + pub user: String, +} + +#[cfg(test)] +impl SystemInfo { + pub fn test() -> Self { + SystemInfo { + os: "Ubuntu".to_string(), + os_version: "20.04".to_string(), + arch: "x86_64".to_string(), + host: "host".to_string(), + user: "user".to_string(), + } + } +} + +impl SystemInfo { + fn new() -> Result { + let os = System::name().ok_or(anyhow!("Failed to get OS name"))?; + let os_version = System::os_version().ok_or(anyhow!("Failed to get OS version"))?; + let arch = System::cpu_arch().ok_or(anyhow!("Failed to get CPU architecture"))?; + let user = get_user()?; + let host = System::host_name().ok_or(anyhow!("Failed to get host name"))?; + + Ok(SystemInfo { + os, + os_version, + arch, + host, + user, + }) + } +} + +/// Checks if the system is supported and returns the system info +/// +/// Supported systems: +/// - Ubuntu 20.04 on x86_64 +/// - Ubuntu 22.04 on x86_64 +/// - Debian 11 on x86_64 +/// - Debian 12 on x86_64 +pub fn check_system() -> Result { + let system_info = SystemInfo::new()?; + debug!("System info: {:#?}", system_info); + + match (system_info.os.as_str(), system_info.os_version.as_str()) { + ("Ubuntu", "20.04") | ("Ubuntu", "22.04") | ("Debian", "11") | ("Debian", "12") => (), + ("Ubuntu", _) => bail!("Only Ubuntu 20.04 and 22.04 are supported at the moment"), + ("Debian", _) => bail!("Only Debian 11 and 12 are supported at the moment"), + _ => bail!("Only Ubuntu and Debian are supported at the moment"), + } + if system_info.arch != "x86_64" { + bail!("Only x86_64 is supported at the moment"); + } + + Ok(system_info) +} diff --git a/src/run/ci_provider/provider.rs b/src/run/ci_provider/provider.rs index 4e5c769..fb581b4 100644 --- a/src/run/ci_provider/provider.rs +++ b/src/run/ci_provider/provider.rs @@ -2,6 +2,7 @@ use git2::Repository; use simplelog::SharedLogger; use crate::prelude::*; +use crate::run::check_system::SystemInfo; use crate::run::config::Config; use crate::run::uploader::{Runner, UploadMetadata}; @@ -71,13 +72,18 @@ pub trait CIProvider { /// let instruments = Instruments::new(); /// let metadata = provider.get_upload_metadata(&config, "abc123").unwrap(); /// ``` - fn get_upload_metadata(&self, config: &Config, archive_hash: &str) -> Result { + fn get_upload_metadata( + &self, + config: &Config, + system_info: &SystemInfo, + archive_hash: &str, + ) -> Result { let provider_metadata = self.get_provider_metadata()?; let commit_hash = get_commit_hash(&provider_metadata.repository_root_path)?; Ok(UploadMetadata { - version: Some(2), + version: Some(3), tokenless: config.token.is_none(), provider_metadata, profile_md5: archive_hash.into(), @@ -86,6 +92,7 @@ pub trait CIProvider { name: "codspeed-runner".into(), version: crate::VERSION.into(), instruments: config.instruments.get_active_instrument_names(), + system_info: system_info.clone(), }, platform: self.get_provider_slug().into(), }) diff --git a/src/run/mod.rs b/src/run/mod.rs index c1f4f0e..1f7ec82 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -5,6 +5,7 @@ use crate::run::{config::Config, logger::Logger}; use crate::VERSION; use clap::Args; +mod check_system; pub mod ci_provider; mod helpers; mod instruments; @@ -107,12 +108,13 @@ pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> { config.set_token(codspeed_config.auth.token.clone()); } - let run_data = runner::run(&config).await?; + let system_info = check_system::check_system()?; + let run_data = runner::run(&config, &system_info).await?; if !config.skip_upload { start_group!("Upload the results"); logger.persist_log_to_profile_folder(&run_data)?; - let upload_result = uploader::upload(&config, &provider, &run_data).await?; + let upload_result = uploader::upload(&config, &system_info, &provider, &run_data).await?; end_group!(); if provider.get_provider_slug() == "local" { diff --git a/src/run/runner/check_system.rs b/src/run/runner/check_system.rs deleted file mode 100644 index d51469d..0000000 --- a/src/run/runner/check_system.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::process::Command; - -use crate::prelude::*; - -/// Returns the OS and version of the system -/// -/// ## Example output -/// ``` -/// ("Ubuntu", "20.04") -/// ("Ubuntu", "22.04") -/// ("Debian", "11") -/// ("Debian", "12") -/// ``` -fn get_os_details() -> Result<(String, String)> { - let lsb_output = Command::new("lsb_release") - .args(["-i", "-r", "-s"]) - .output() - .map_err(|_| anyhow!("Failed to get system info"))?; - if !lsb_output.status.success() { - bail!("Failed to get system info"); - } - let output_str = - String::from_utf8(lsb_output.stdout).map_err(|_| anyhow!("Failed to parse system info"))?; - let mut lines = output_str.trim().lines(); - let os = lines - .next() - .ok_or_else(|| anyhow!("Failed to get OS info"))?; - let os_version = lines - .next() - .ok_or_else(|| anyhow!("Failed to get OS version"))?; - Ok((os.to_string(), os_version.to_string())) -} - -/// NOTE: Since this relies on `dpkg` this will only work on Debian based systems -fn get_arch() -> Result { - let arch_output = Command::new("dpkg") - .args(["--print-architecture"]) - .output() - .map_err(|_| anyhow!("Failed to get architecture info"))?; - if !arch_output.status.success() { - bail!("Failed to get architecture info"); - } - let output_str = String::from_utf8(arch_output.stdout) - .map_err(|_| anyhow!("Failed to parse architecture info"))?; - Ok(output_str.trim().to_string()) -} - -#[derive(Eq, PartialEq, Hash)] -pub struct SystemInfo { - pub os: String, - pub os_version: String, - pub arch: String, -} - -/// Checks if the system is supported -/// -/// Supported systems: -/// - Ubuntu 20.04 on amd64 -/// - Ubuntu 22.04 on amd64 -/// - Debian 11 on amd64 -/// - Debian 12 on amd64 -pub fn check_system() -> Result { - let (os, os_version) = get_os_details()?; - debug!("OS: {}, Version: {}", os, os_version); - match (os.as_str(), os_version.as_str()) { - ("Ubuntu", "20.04") | ("Ubuntu", "22.04") | ("Debian", "11") | ("Debian", "12") => (), - ("Ubuntu", _) => bail!("Only Ubuntu 20.04 and 22.04 are supported at the moment"), - ("Debian", _) => bail!("Only Debian 11 and 12 are supported at the moment"), - _ => bail!("Only Ubuntu and Debian are supported at the moment"), - } - let arch = get_arch()?; - debug!("Arch: {}", arch); - if arch != "amd64" && arch != "arm64" { - bail!("Only amd64 and arm64 are supported at the moment"); - } - Ok(SystemInfo { - os, - os_version, - arch, - }) -} diff --git a/src/run/runner/mod.rs b/src/run/runner/mod.rs index ecd9468..5ae8c8c 100644 --- a/src/run/runner/mod.rs +++ b/src/run/runner/mod.rs @@ -1,4 +1,3 @@ -mod check_system; mod helpers; mod run; mod setup; diff --git a/src/run/runner/run.rs b/src/run/runner/run.rs index 1f2a8d5..8afa8cd 100644 --- a/src/run/runner/run.rs +++ b/src/run/runner/run.rs @@ -1,10 +1,11 @@ use crate::prelude::*; -use crate::run::{config::Config, instruments::mongo_tracer::MongoTracer}; +use crate::run::{ + check_system::SystemInfo, config::Config, instruments::mongo_tracer::MongoTracer, +}; use std::path::PathBuf; use super::{ - check_system::check_system, helpers::{perf_maps::harvest_perf_maps, profile_folder::create_profile_folder}, setup::setup, valgrind, @@ -14,11 +15,10 @@ pub struct RunData { pub profile_folder: PathBuf, } -pub async fn run(config: &Config) -> Result { +pub async fn run(config: &Config, system_info: &SystemInfo) -> Result { if !config.skip_setup { start_group!("Prepare the environment"); - let system_info = check_system()?; - setup(&system_info, config).await?; + setup(system_info, config).await?; end_group!(); } //TODO: add valgrind version check diff --git a/src/run/runner/setup.rs b/src/run/runner/setup.rs index 680cede..a039b47 100644 --- a/src/run/runner/setup.rs +++ b/src/run/runner/setup.rs @@ -1,14 +1,12 @@ use std::{ - collections::HashMap, env, process::{Command, Stdio}, }; -use lazy_static::lazy_static; use url::Url; -use super::{check_system::SystemInfo, helpers::download_file::download_file}; -use crate::run::config::Config; +use super::helpers::download_file::download_file; +use crate::run::{check_system::SystemInfo, config::Config}; use crate::{prelude::*, MONGODB_TRACER_VERSION, VALGRIND_CODSPEED_VERSION}; /// Run a command with sudo if available @@ -40,55 +38,24 @@ fn run_with_sudo(command_args: &[&str]) -> Result<()> { Ok(()) } -lazy_static! { - static ref SYSTEM_INFO_TO_CODSPEED_VALGRIND_FILENAME: HashMap = { - let mut m = HashMap::new(); - m.insert( - SystemInfo { - os: "Ubuntu".to_string(), - os_version: "20.04".to_string(), - arch: "amd64".to_string(), - }, - format!( - "valgrind_{}_ubuntu-{}_amd64.deb", - VALGRIND_CODSPEED_VERSION, "20.04" - ), - ); - m.insert( - SystemInfo { - os: "Ubuntu".to_string(), - os_version: "22.04".to_string(), - arch: "amd64".to_string(), - }, - format!( - "valgrind_{}_ubuntu-{}_amd64.deb", - VALGRIND_CODSPEED_VERSION, "22.04" - ), - ); - m.insert( - SystemInfo { - os: "Debian".to_string(), - os_version: "11".to_string(), - arch: "amd64".to_string(), - }, - format!( - "valgrind_{}_ubuntu-{}_amd64.deb", - VALGRIND_CODSPEED_VERSION, "20.04" - ), - ); - m.insert( - SystemInfo { - os: "Debian".to_string(), - os_version: "12".to_string(), - arch: "amd64".to_string(), - }, - format!( - "valgrind_{}_ubuntu-{}_amd64.deb", - VALGRIND_CODSPEED_VERSION, "20.04" - ), - ); - m +fn get_codspeed_valgrind_filename(system_info: &SystemInfo) -> Result { + let version = match ( + system_info.os.as_str(), + system_info.os_version.as_str(), + system_info.arch.as_str(), + ) { + ("Ubuntu", "20.04", "x86_64") | ("Debian", "11", "x86_64") | ("Debian", "12", "x86_64") => { + "20.04" + } + ("Ubuntu", "22.04", "x86_64") => "22.04", + + _ => bail!("Unsupported system"), }; + + Ok(format!( + "valgrind_{}_ubuntu_{}_amd64.deb", + VALGRIND_CODSPEED_VERSION, version + )) } fn is_valgrind_installed() -> bool { @@ -122,9 +89,7 @@ async fn install_valgrind(system_info: &SystemInfo) -> Result<()> { let valgrind_deb_url = format!( "https://github.com/CodSpeedHQ/valgrind-codspeed/releases/download/{}/{}", VALGRIND_CODSPEED_VERSION, - SYSTEM_INFO_TO_CODSPEED_VALGRIND_FILENAME - .get(system_info) - .context("Unsupported system")? + get_codspeed_valgrind_filename(system_info)? ); let deb_path = env::temp_dir().join("valgrind-codspeed.deb"); download_file(&Url::parse(valgrind_deb_url.as_str()).unwrap(), &deb_path).await?; @@ -177,18 +142,32 @@ mod tests { use super::*; #[test] - fn test_system_info_to_codspeed_valgrind_version() { + fn test_system_info_to_codspeed_valgrind_version_ubuntu() { + let system_info = SystemInfo { + os: "Ubuntu".to_string(), + os_version: "22.04".to_string(), + arch: "x86_64".to_string(), + host: "host".to_string(), + user: "user".to_string(), + }; + assert_eq!( + get_codspeed_valgrind_filename(&system_info).unwrap(), + "valgrind_3.21.0-0codspeed1_ubuntu_22.04_amd64.deb" + ); + } + + #[test] + fn test_system_info_to_codspeed_valgrind_version_debian() { let system_info = SystemInfo { os: "Debian".to_string(), os_version: "11".to_string(), - arch: "amd64".to_string(), + arch: "x86_64".to_string(), + host: "host".to_string(), + user: "user".to_string(), }; assert_eq!( - SYSTEM_INFO_TO_CODSPEED_VALGRIND_FILENAME[&system_info], - format!( - "valgrind_{}_ubuntu-{}_amd64.deb", - VALGRIND_CODSPEED_VERSION, "20.04" - ) + get_codspeed_valgrind_filename(&system_info).unwrap(), + "valgrind_3.21.0-0codspeed1_ubuntu_20.04_amd64.deb" ); } } diff --git a/src/run/uploader/interfaces.rs b/src/run/uploader/interfaces.rs index 9297a47..72423d8 100644 --- a/src/run/uploader/interfaces.rs +++ b/src/run/uploader/interfaces.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; -use crate::run::{ci_provider::interfaces::ProviderMetadata, instruments::InstrumentNames}; +use crate::run::{ + check_system::SystemInfo, ci_provider::interfaces::ProviderMetadata, + instruments::InstrumentNames, +}; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -21,6 +24,8 @@ pub struct Runner { pub name: String, pub version: String, pub instruments: Vec, + #[serde(flatten)] + pub system_info: SystemInfo, } #[derive(Deserialize, Serialize, Debug)] diff --git a/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap b/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap index d41c738..118e888 100644 --- a/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap +++ b/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap @@ -3,7 +3,7 @@ source: src/run/uploader/upload_metadata.rs expression: upload_metadata --- { - "version": 2, + "version": 3, "tokenless": true, "profileMd5": "jp/k05RKuqP3ERQuIIvx4Q==", "runner": { @@ -11,7 +11,12 @@ expression: upload_metadata "version": "2.1.0", "instruments": [ "MongoDB" - ] + ], + "os": "Ubuntu", + "osVersion": "20.04", + "arch": "x86_64", + "host": "host", + "user": "user" }, "platform": "github-actions", "commitHash": "5bd77cb0da72bef094893ed45fb793ff16ecfbe3", diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index c51446f..530a970 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -1,4 +1,7 @@ -use crate::run::{ci_provider::CIProvider, config::Config, runner::RunData, uploader::UploadError}; +use crate::run::{ + check_system::SystemInfo, ci_provider::CIProvider, config::Config, runner::RunData, + uploader::UploadError, +}; use crate::{prelude::*, request_client::REQUEST_CLIENT}; use async_compression::tokio::write::GzipEncoder; use base64::{engine::general_purpose, Engine as _}; @@ -81,6 +84,7 @@ pub struct UploadResult { #[allow(clippy::borrowed_box)] pub async fn upload( config: &Config, + system_info: &SystemInfo, provider: &Box, run_data: &RunData, ) -> Result { @@ -88,7 +92,7 @@ pub async fn upload( debug!("CI provider detected: {:#?}", provider.get_provider_name()); - let upload_metadata = provider.get_upload_metadata(config, &archive_hash)?; + let upload_metadata = provider.get_upload_metadata(config, system_info, &archive_hash)?; debug!("Upload metadata: {:#?}", upload_metadata); if upload_metadata.tokenless { let hash = upload_metadata.get_hash(); @@ -115,7 +119,6 @@ mod tests { use url::Url; use super::*; - use crate::run::runner::RunData; use std::path::PathBuf; // TODO: remove the ignore when implementing network mocking @@ -134,6 +137,7 @@ mod tests { env!("CARGO_MANIFEST_DIR") )), }; + let system_info = SystemInfo::test(); async_with_vars( [ ("GITHUB_ACTIONS", Some("true")), @@ -164,7 +168,9 @@ mod tests { ], async { let provider = crate::run::ci_provider::get_provider(&config).unwrap(); - upload(&config, &provider, &run_data).await.unwrap(); + upload(&config, &system_info, &provider, &run_data) + .await + .unwrap(); }, ) .await; diff --git a/src/run/uploader/upload_metadata.rs b/src/run/uploader/upload_metadata.rs index 94d36d5..081bfa2 100644 --- a/src/run/uploader/upload_metadata.rs +++ b/src/run/uploader/upload_metadata.rs @@ -14,6 +14,7 @@ mod tests { use insta::assert_json_snapshot; use crate::run::{ + check_system::SystemInfo, ci_provider::interfaces::{GhData, ProviderMetadata, RunEvent, Sender}, instruments::InstrumentNames, uploader::{Runner, UploadMetadata}, @@ -22,13 +23,14 @@ mod tests { #[test] fn test_get_metadata_hash() { let upload_metadata = UploadMetadata { - version: Some(2), + version: Some(3), tokenless: true, profile_md5: "jp/k05RKuqP3ERQuIIvx4Q==".into(), runner: Runner { name: "codspeed-runner".into(), version: "2.1.0".into(), instruments: vec![InstrumentNames::MongoDB], + system_info: SystemInfo::test(), }, platform: "github-actions".into(), commit_hash: "5bd77cb0da72bef094893ed45fb793ff16ecfbe3".into(), @@ -54,7 +56,7 @@ mod tests { let hash = upload_metadata.get_hash(); assert_eq!( hash, - "8beb149c4645c666156e24fe0f68d24a63cec1d7756f35dd17cab1d84528ed7b" + "ada5057b0c440844a1558eed80a1993a41756984cc6147fdef459ce8a289f1d7" ); assert_json_snapshot!(upload_metadata); } From 2545517d80c994703a9fbd2fca8ffa9b3e625283 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Mon, 10 Jun 2024 12:46:53 -0400 Subject: [PATCH 10/26] refactor: rename bin to codspeed --- Cargo.toml | 4 ++++ README.md | 14 +++++++------- ...er__tests__pull_request_provider_metadata.snap} | 0 ...ests__fork_pull_request_provider_metadata.snap} | 0 ...er__tests__pull_request_provider_metadata.snap} | 0 src/run/mod.rs | 4 ++-- ...upload_metadata__tests__get_metadata_hash.snap} | 0 7 files changed, 13 insertions(+), 9 deletions(-) rename src/run/ci_provider/buildkite/snapshots/{codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap => codspeed__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap} (100%) rename src/run/ci_provider/github_actions/snapshots/{codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap => codspeed__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap} (100%) rename src/run/ci_provider/github_actions/snapshots/{codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap => codspeed__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap} (100%) rename src/run/uploader/snapshots/{codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap => codspeed__run__uploader__upload_metadata__tests__get_metadata_hash.snap} (100%) diff --git a/Cargo.toml b/Cargo.toml index 90cc673..1d75a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" repository = "https://github.com/CodSpeedHQ/runner" publish = false +[[bin]] +name = "codspeed" +path = "src/main.rs" + [dependencies] anyhow = "1.0.75" diff --git a/README.md b/README.md index 28d3664..9d76722 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
-

codspeed-runner

+

CodSpeed CLI

-CLI to gather performance data from CI environments and upload performance reports to [CodSpeed](https://codspeed.io) +CLI to gather performance data and upload performance reports to [CodSpeed](https://codspeed.io) [![CI](https://github.com/CodSpeedHQ/runner/actions/workflows/ci.yml/badge.svg)](https://github.com/CodSpeedHQ/runner/actions/workflows/ci.yml) [![Discord](https://img.shields.io/badge/chat%20on-discord-7289da.svg)](https://discord.com/invite/MxpaCfKSqF) @@ -9,9 +9,9 @@ CLI to gather performance data from CI environments and upload performance repor
-The `codspeed-runner` CLI is designed to be used in CI environments. +The `codspeed` CLI is designed to be used both in local in CI environments. -The following providers are supported: +The following CI providers are supported: - [GitHub Actions](https://docs.codspeed.io/ci/github-actions): Usage with [`@CodSpeedHQ/action`](https://github.com/CodSpeedHQ/action) is recommended. - [Buildkite](https://docs.codspeed.io/ci/buildkite) @@ -40,11 +40,11 @@ Refer to the [releases page](https://github.com/CodSpeedHQ/runner/releases) to s Example of a command to run benchmarks with [Vitest](https://docs.codspeed.io/benchmarks/nodejs/vitest): ```bash -codspeed-runner run --token=$CODSPEED_TOKEN -- pnpm vitest bench +codspeed run --token=$CODSPEED_TOKEN -- pnpm vitest bench ``` ``` -Usage: codspeed-runner run [OPTIONS] [COMMAND]... +Usage: codspeed run [OPTIONS] [COMMAND]... Arguments: [COMMAND]... The bench command to run @@ -65,5 +65,5 @@ Options: Use the `CODSPEED_LOG` environment variable to set the logging level: ```bash -CODSPEED_LOG=debug codspeed-runner run ... +CODSPEED_LOG=debug codspeed run ... ``` diff --git a/src/run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap b/src/run/ci_provider/buildkite/snapshots/codspeed__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap similarity index 100% rename from src/run/ci_provider/buildkite/snapshots/codspeed_runner__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap rename to src/run/ci_provider/buildkite/snapshots/codspeed__run__ci_provider__buildkite__provider__tests__pull_request_provider_metadata.snap diff --git a/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap b/src/run/ci_provider/github_actions/snapshots/codspeed__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap similarity index 100% rename from src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap rename to src/run/ci_provider/github_actions/snapshots/codspeed__run__ci_provider__github_actions__provider__tests__fork_pull_request_provider_metadata.snap diff --git a/src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap b/src/run/ci_provider/github_actions/snapshots/codspeed__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap similarity index 100% rename from src/run/ci_provider/github_actions/snapshots/codspeed_runner__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap rename to src/run/ci_provider/github_actions/snapshots/codspeed__run__ci_provider__github_actions__provider__tests__pull_request_provider_metadata.snap diff --git a/src/run/mod.rs b/src/run/mod.rs index 1f7ec82..e187a74 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -29,7 +29,7 @@ fn show_banner() { VERSION ); println!("{}", banner); - debug!("codspeed-runner v{}", VERSION); + debug!("codspeed v{}", VERSION); } #[derive(Args, Debug)] @@ -102,7 +102,7 @@ pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> { if provider.get_provider_slug() == "local" { if codspeed_config.auth.token.is_none() { - bail!("You have to authenticate the CLI first. Run `codspeed-runner auth login`."); + 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()); diff --git a/src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap b/src/run/uploader/snapshots/codspeed__run__uploader__upload_metadata__tests__get_metadata_hash.snap similarity index 100% rename from src/run/uploader/snapshots/codspeed_runner__run__uploader__upload_metadata__tests__get_metadata_hash.snap rename to src/run/uploader/snapshots/codspeed__run__uploader__upload_metadata__tests__get_metadata_hash.snap From eb1479d959dabd54f3b622dd1fe00dd6f4dcc460 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Mon, 10 Jun 2024 12:47:52 -0400 Subject: [PATCH 11/26] feat: update CLI style --- Cargo.toml | 2 +- src/app.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d75a4f..eec6fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ path = "src/main.rs" [dependencies] anyhow = "1.0.75" -clap = { version = "4.4.8", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env", "color"] } itertools = "0.11.0" lazy_static = "1.4.0" log = "0.4.20" diff --git a/src/app.rs b/src/app.rs index ac84f0a..b323b1b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,19 @@ use crate::{api_client::CodSpeedAPIClient, auth, prelude::*, run}; -use clap::{Parser, Subcommand}; +use clap::{ + builder::{styling, Styles}, + Parser, Subcommand, +}; + +fn create_styles() -> Styles { + styling::Styles::styled() + .header(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) + .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) + .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD) + .placeholder(styling::AnsiColor::Cyan.on_default()) +} #[derive(Parser, Debug)] +#[command(about = "The CodSpeed CLI tool", styles = create_styles())] pub struct Cli { /// The URL of the CodSpeed GraphQL API #[arg( From 4822152fbc1e42d43e2029300bb82344356c1d54 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 19 Jun 2024 18:55:14 +0000 Subject: [PATCH 12/26] refactor(logger): move logger group logic to root logger --- src/logger.rs | 74 ++++++++++++++++++++ src/run/ci_provider/buildkite/logger.rs | 5 +- src/run/ci_provider/github_actions/logger.rs | 5 +- src/run/ci_provider/logger.rs | 74 -------------------- src/run/logger.rs | 7 +- 5 files changed, 82 insertions(+), 83 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index a027304..bd3639a 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -2,6 +2,80 @@ use std::env; use simplelog::{ConfigBuilder, SharedLogger}; +/// This target is used exclusively to handle group events. +pub const GROUP_TARGET: &str = "codspeed::group"; +pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened"; + +#[macro_export] +/// Start a new log group. All logs between this and the next `end_group!` will be grouped together. +/// +/// # Example +/// +/// ```rust +/// start_group!("My group"); +/// info!("This will be grouped"); +/// end_group!(); +/// ``` +macro_rules! start_group { + ($name:expr) => { + log::log!(target: $crate::logger::GROUP_TARGET, log::Level::Info, "{}", $name); + }; +} + +#[macro_export] +/// Start a new opened log group. All logs between this and the next `end_group!` will be grouped together. +/// +/// # Example +/// +/// ```rust +/// start_opened_group!("My group"); +/// info!("This will be grouped"); +/// end_group!(); +/// ``` +macro_rules! start_opened_group { + ($name:expr) => { + log::log!(target: $crate::logger::OPENED_GROUP_TARGET, log::Level::Info, "{}", $name); + }; +} + +#[macro_export] +/// End the current log group. +/// See [`start_group!`] for more information. +macro_rules! end_group { + () => { + log::log!(target: $crate::logger::GROUP_TARGET, log::Level::Info, ""); + }; +} + +pub enum GroupEvent { + Start(String), + StartOpened(String), + End, +} + +/// Returns the group event if the record is a group event, otherwise returns `None`. +pub(super) fn get_group_event(record: &log::Record) -> Option { + match record.target() { + OPENED_GROUP_TARGET => { + let args = record.args().to_string(); + if args.is_empty() { + None + } else { + Some(GroupEvent::StartOpened(args)) + } + } + GROUP_TARGET => { + let args = record.args().to_string(); + if args.is_empty() { + Some(GroupEvent::End) + } else { + Some(GroupEvent::Start(args)) + } + } + _ => None, + } +} + pub fn get_local_logger() -> Box { let log_level = env::var("CODSPEED_LOG") .ok() diff --git a/src/run/ci_provider/buildkite/logger.rs b/src/run/ci_provider/buildkite/logger.rs index 46d3d71..df727c5 100644 --- a/src/run/ci_provider/buildkite/logger.rs +++ b/src/run/ci_provider/buildkite/logger.rs @@ -1,5 +1,6 @@ -use crate::run::ci_provider::logger::{ - get_group_event, should_provider_logger_handle_record, GroupEvent, +use crate::{ + logger::{get_group_event, GroupEvent}, + run::ci_provider::logger::should_provider_logger_handle_record, }; use log::*; use simplelog::SharedLogger; diff --git a/src/run/ci_provider/github_actions/logger.rs b/src/run/ci_provider/github_actions/logger.rs index 407f5db..5a24f87 100644 --- a/src/run/ci_provider/github_actions/logger.rs +++ b/src/run/ci_provider/github_actions/logger.rs @@ -1,5 +1,6 @@ -use crate::run::ci_provider::logger::{ - get_group_event, should_provider_logger_handle_record, GroupEvent, +use crate::{ + logger::{get_group_event, GroupEvent}, + run::ci_provider::logger::should_provider_logger_handle_record, }; use log::*; use simplelog::SharedLogger; diff --git a/src/run/ci_provider/logger.rs b/src/run/ci_provider/logger.rs index 17ab082..85ba733 100644 --- a/src/run/ci_provider/logger.rs +++ b/src/run/ci_provider/logger.rs @@ -1,79 +1,5 @@ use crate::run::runner::VALGRIND_EXECUTION_TARGET; -/// This target is used exclusively to handle group events. -pub const GROUP_TARGET: &str = "codspeed::group"; -pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened"; - -#[macro_export] -/// Start a new log group. All logs between this and the next `end_group!` will be grouped together. -/// -/// # Example -/// -/// ```rust -/// start_group!("My group"); -/// info!("This will be grouped"); -/// end_group!(); -/// ``` -macro_rules! start_group { - ($name:expr) => { - log::log!(target: $crate::run::ci_provider::logger::GROUP_TARGET, log::Level::Info, "{}", $name); - }; -} - -#[macro_export] -/// Start a new opened log group. All logs between this and the next `end_group!` will be grouped together. -/// -/// # Example -/// -/// ```rust -/// start_opened_group!("My group"); -/// info!("This will be grouped"); -/// end_group!(); -/// ``` -macro_rules! start_opened_group { - ($name:expr) => { - log::log!(target: $crate::run::ci_provider::logger::OPENED_GROUP_TARGET, log::Level::Info, "{}", $name); - }; -} - -#[macro_export] -/// End the current log group. -/// See [`start_group!`] for more information. -macro_rules! end_group { - () => { - log::log!(target: $crate::run::ci_provider::logger::GROUP_TARGET, log::Level::Info, ""); - }; -} - -pub enum GroupEvent { - Start(String), - StartOpened(String), - End, -} - -/// Returns the group event if the record is a group event, otherwise returns `None`. -pub(super) fn get_group_event(record: &log::Record) -> Option { - match record.target() { - OPENED_GROUP_TARGET => { - let args = record.args().to_string(); - if args.is_empty() { - None - } else { - Some(GroupEvent::StartOpened(args)) - } - } - GROUP_TARGET => { - let args = record.args().to_string(); - if args.is_empty() { - Some(GroupEvent::End) - } else { - Some(GroupEvent::Start(args)) - } - } - _ => None, - } -} - pub(super) fn should_provider_logger_handle_record(record: &log::Record) -> bool { // Provider logger should handle all records except the ones from the valgrind execution target record.target() != VALGRIND_EXECUTION_TARGET diff --git a/src/run/logger.rs b/src/run/logger.rs index e543474..a048337 100644 --- a/src/run/logger.rs +++ b/src/run/logger.rs @@ -1,9 +1,6 @@ +use crate::logger::{GROUP_TARGET, OPENED_GROUP_TARGET}; use crate::prelude::*; -use crate::run::{ - ci_provider::logger::{GROUP_TARGET, OPENED_GROUP_TARGET}, - ci_provider::CIProvider, - runner::RunData, -}; +use crate::run::{ci_provider::CIProvider, runner::RunData}; use log::LevelFilter; use simplelog::{CombinedLogger, WriteLogger}; use std::fs::copy; From 8eb6cd10dc8a29470f0c4a8be93e307d275dfbd9 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 19 Jun 2024 18:56:15 +0000 Subject: [PATCH 13/26] feat(cli): create custom local logger with spinner --- Cargo.lock | 106 +++++++++---------------- Cargo.toml | 2 + src/logger.rs | 154 +++++++++++++++++++++++++++++++++---- src/run/poll_results.rs | 1 - src/run/runner/valgrind.rs | 5 +- 5 files changed, 180 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 589f4de..5515850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,8 +256,10 @@ dependencies = [ "async-compression", "base64", "clap", + "console", "git2", "gql_client", + "indicatif", "insta", "itertools", "lazy_static", @@ -290,14 +292,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", - "windows-sys 0.45.0", + "unicode-width", + "windows-sys 0.52.0", ] [[package]] @@ -794,6 +797,19 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "insta" version = "1.34.0" @@ -1049,6 +1065,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -1235,6 +1257,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -2013,6 +2041,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -2226,15 +2260,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2253,21 +2278,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -2298,12 +2308,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2316,12 +2320,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2334,12 +2332,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2352,12 +2344,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2370,12 +2356,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2388,12 +2368,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2406,12 +2380,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index eec6fdc..fba7cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ nestify = "0.3.3" gql_client = { git = "https://github.com/CodSpeedHQ/gql-client-rs" } serde_yaml = "0.9.34" sysinfo = { version = "0.30.12", features = ["serde"] } +indicatif = "0.17.8" +console = "0.15.8" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/logger.rs b/src/logger.rs index bd3639a..050ea38 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,6 +1,15 @@ -use std::env; +use std::{ + env, + sync::{Arc, Mutex}, + time::Duration, +}; -use simplelog::{ConfigBuilder, SharedLogger}; +use console::Style; +use indicatif::{ProgressBar, ProgressStyle}; +use lazy_static::lazy_static; +use log::Log; +use simplelog::SharedLogger; +use std::io::Write; /// This target is used exclusively to handle group events. pub const GROUP_TARGET: &str = "codspeed::group"; @@ -76,20 +85,131 @@ pub(super) fn get_group_event(record: &log::Record) -> Option { } } +lazy_static! { + pub static ref SPINNER: Arc>> = Arc::new(Mutex::new(None)); + pub static ref IS_TTY: bool = std::io::IsTerminal::is_terminal(&std::io::stdout()); +} + +/// Hide the progress bar temporarily, execute `f`, then redraw the progress bar. +/// +/// If the output is not a TTY, `f` will be executed without hiding the progress bar. +pub fn suspend_progress_bar R, R>(f: F) -> R { + // If the output is a TTY, and there is a spinner, suspend it + if *IS_TTY { + if let Ok(mut spinner) = SPINNER.lock() { + if let Some(spinner) = spinner.as_mut() { + return spinner.suspend(f); + } + } + } + + // Otherwise, just run the function + f() +} + +pub struct LocalLogger { + log_level: log::LevelFilter, +} + +impl LocalLogger { + pub fn new() -> Self { + let log_level = env::var("CODSPEED_LOG") + .ok() + .and_then(|log_level| log_level.parse::().ok()) + .unwrap_or(log::LevelFilter::Info); + + LocalLogger { log_level } + } +} + +impl Log for LocalLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.log_level + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + if let Some(group_event) = get_group_event(record) { + match group_event { + GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { + if *IS_TTY { + let spinner = ProgressBar::new_spinner(); + spinner.set_style( + ProgressStyle::with_template( + " {spinner:>.cyan} {wide_msg:.cyan.bold}", + ) + .unwrap(), + ); + spinner.set_message(format!("{}...", name)); + spinner.enable_steady_tick(Duration::from_millis(100)); + SPINNER.lock().unwrap().replace(spinner); + } else { + println!("{}...", name); + } + } + GroupEvent::End => { + if *IS_TTY { + let mut spinner = SPINNER.lock().unwrap(); + if let Some(spinner) = spinner.as_mut() { + spinner.finish_and_clear(); + // Separate groups with a newline + println!(); + } + } + } + } + + return; + } + + suspend_progress_bar(|| print_record(record)); + } + + fn flush(&self) { + std::io::stdout().flush().unwrap(); + } +} + +/// Print a log record to the console with the appropriate style +fn print_record(record: &log::Record) { + let error_style = Style::new().red(); + let info_style = Style::new().white(); + let warn_style = Style::new().yellow(); + let debug_style = Style::new().blue().dim(); + let trace_style = Style::new().black().dim(); + + match record.level() { + log::Level::Error => eprintln!("{}", error_style.apply_to(record.args())), + log::Level::Warn => eprintln!("{}", warn_style.apply_to(record.args())), + log::Level::Info => println!("{}", info_style.apply_to(record.args())), + log::Level::Debug => println!( + "{}", + debug_style.apply_to(format!("[DEBUG::{}] {}", record.target(), record.args())), + ), + log::Level::Trace => println!( + "{}", + trace_style.apply_to(format!("[TRACE::{}] {}", record.target(), record.args())) + ), + } +} + +impl SharedLogger for LocalLogger { + fn level(&self) -> log::LevelFilter { + self.log_level + } + + fn config(&self) -> Option<&simplelog::Config> { + None + } + + fn as_log(self: Box) -> Box { + Box::new(*self) + } +} + pub fn get_local_logger() -> Box { - let log_level = env::var("CODSPEED_LOG") - .ok() - .and_then(|log_level| log_level.parse::().ok()) - .unwrap_or(log::LevelFilter::Info); - - let config = ConfigBuilder::new() - .set_time_level(log::LevelFilter::Debug) - .build(); - - simplelog::TermLogger::new( - log_level, - config, - simplelog::TerminalMode::Mixed, - simplelog::ColorChoice::Auto, - ) + Box::new(LocalLogger::new()) } diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index a737217..d3117f1 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -28,7 +28,6 @@ pub async fn poll_results( }; let run; - info!("Polling results..."); loop { if start.elapsed() > RUN_PROCESSING_MAX_DURATION { bail!("Polling results timed out"); diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index 962fd83..e7f8909 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -1,3 +1,4 @@ +use crate::logger::suspend_progress_bar; use crate::prelude::*; use crate::run::{ config::Config, instruments::mongo_tracer::MongoTracer, @@ -72,7 +73,9 @@ fn run_command_with_log_pipe(mut cmd: Command) -> Result { if bytes_read == 0 { break; } - writer.write_all(&buffer[..bytes_read])?; + suspend_progress_bar(|| { + writer.write_all(&buffer[..bytes_read]).unwrap(); + }); trace!(target: VALGRIND_EXECUTION_TARGET, "{}{}", prefix, String::from_utf8_lossy(&buffer[..bytes_read])); } Ok(()) From 3410f2b6efba44235c81fcbb0ac50f2fb21415a7 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Mon, 1 Jul 2024 12:57:17 +0000 Subject: [PATCH 14/26] feat(auth): add log groups --- src/auth.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 95ceac4..612a2af 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -18,7 +18,6 @@ enum AuthCommands { Login, } -// TODO: tweak the logger to make it more user-friendly fn init_logger() -> Result<()> { let logger = get_local_logger(); CombinedLogger::init(vec![logger])?; @@ -38,14 +37,16 @@ const LOGIN_SESSION_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 m async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { debug!("Login to CodSpeed"); - debug!("Creating login session..."); + start_group!("Creating login session"); let login_session_payload = api_client.create_login_session().await?; + end_group!(); + info!( - "Login session created, open the following URL in your browser: {}", + "Login session created, open the following URL in your browser: {}\n", login_session_payload.callback_url ); - info!("Waiting for the login to be completed..."); + start_group!("Waiting for the login to be completed"); let token; let start = Instant::now(); loop { @@ -65,7 +66,7 @@ async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { None => sleep(Duration::from_secs(5)).await, } } - debug!("Login completed"); + end_group!(); let mut config = CodSpeedConfig::load()?; config.auth.token = Some(token); From b601ca85ad2ab411055eac598fd0e25ad2b67946 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 2 Jul 2024 10:02:29 +0000 Subject: [PATCH 15/26] feat(auth): style auth link log --- src/auth.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth.rs b/src/auth.rs index 612a2af..8592a82 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -3,6 +3,7 @@ use std::time::Duration; use crate::logger::get_local_logger; use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*}; use clap::{Args, Subcommand}; +use console::style; use simplelog::CombinedLogger; use tokio::time::{sleep, Instant}; @@ -43,7 +44,10 @@ async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { info!( "Login session created, open the following URL in your browser: {}\n", - login_session_payload.callback_url + style(login_session_payload.callback_url) + .blue() + .bold() + .underlined() ); start_group!("Waiting for the login to be completed"); From 68b39485bcab4c823dda4668f0c520fd42373c41 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 2 Jul 2024 10:03:37 +0000 Subject: [PATCH 16/26] feat(runner/poll_results): add regressions threshold, colors and better style to logs --- src/api_client.rs | 56 +++++++++++++++++++++++------ src/queries/FetchLocalRunReport.gql | 3 ++ src/run/poll_results.rs | 37 +++++++++++++------ 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index 7e268f8..497f9d4 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -1,5 +1,8 @@ +use std::fmt::Display; + use crate::prelude::*; use crate::{app::Cli, config::CodSpeedConfig}; +use console::style; use gql_client::{Client as GQLClient, ClientConfig}; use nestify::nest; use serde::{Deserialize, Serialize}; @@ -77,6 +80,22 @@ pub enum ReportConclusion { MissingBaseRun, Success, } + +impl Display for ReportConclusion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReportConclusion::AcknowledgedFailure => { + write!(f, "{}", style("Acknowledged Failure").yellow().bold()) + } + ReportConclusion::Failure => write!(f, "{}", style("Failure").red().bold()), + ReportConclusion::MissingBaseRun => { + write!(f, "{}", style("Missing Base Run").yellow().bold()) + } + ReportConclusion::Success => write!(f, "{}", style("Success").green().bold()), + } + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct FetchLocalRunReportHeadReport { @@ -104,11 +123,19 @@ nest! { #[serde(rename_all = "camelCase")]* struct FetchLocalRunReportData { repository: pub struct FetchLocalRunReportRepository { - pub runs: Vec, + settings: struct FetchLocalRunReportSettings { + allowed_regression: f64, + }, + runs: Vec, } } } +pub struct FetchLocalRunReportResponse { + pub allowed_regression: f64, + pub run: FetchLocalRunReportRun, +} + impl CodSpeedAPIClient { pub async fn create_login_session(&self) -> Result { let response = self @@ -143,7 +170,7 @@ impl CodSpeedAPIClient { pub async fn fetch_local_run_report( &self, vars: FetchLocalRunReportVars, - ) -> Result { + ) -> Result { let response = self .gql_client .query_with_vars_unwrap::( @@ -152,15 +179,22 @@ impl CodSpeedAPIClient { ) .await; match response { - Ok(response) => match response.repository.runs.into_iter().next() { - Some(run) => Ok(run), - None => bail!( - "No runs found for owner: {}, name: {}, run_id: {}", - vars.owner, - vars.name, - vars.run_id - ), - }, + Ok(response) => { + let allowed_regression = response.repository.settings.allowed_regression; + + match response.repository.runs.into_iter().next() { + Some(run) => Ok(FetchLocalRunReportResponse { + allowed_regression, + run, + }), + None => bail!( + "No runs found for owner: {}, name: {}, run_id: {}", + vars.owner, + vars.name, + vars.run_id + ), + } + } Err(err) => bail!("Failed to fetch local run report: {}", err), } } diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql index 9df48e0..20e8f61 100644 --- a/src/queries/FetchLocalRunReport.gql +++ b/src/queries/FetchLocalRunReport.gql @@ -1,5 +1,8 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { repository(owner: $owner, name: $name) { + settings { + allowedRegression + } runs(where: { id: { equals: $runId } }) { id status diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index d3117f1..0f19223 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -1,15 +1,17 @@ use std::time::Duration; +use console::style; use tokio::time::{sleep, Instant}; use crate::api_client::{ - CodSpeedAPIClient, FetchLocalRunReportRun, FetchLocalRunReportVars, RunStatus, + CodSpeedAPIClient, FetchLocalRunReportResponse, FetchLocalRunReportVars, RunStatus, }; use crate::prelude::*; use super::ci_provider::CIProvider; const RUN_PROCESSING_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 minutes +const POLLING_INTERVAL: Duration = Duration::from_secs(1); #[allow(clippy::borrowed_box)] pub async fn poll_results( @@ -27,7 +29,7 @@ pub async fn poll_results( run_id: run_id.clone(), }; - let run; + let response; loop { if start.elapsed() > RUN_PROCESSING_MAX_DURATION { bail!("Polling results timed out"); @@ -37,17 +39,18 @@ pub async fn poll_results( .fetch_local_run_report(fetch_local_run_report_vars.clone()) .await? { - FetchLocalRunReportRun { status, .. } if status != RunStatus::Completed => { - sleep(Duration::from_secs(5)).await; + FetchLocalRunReportResponse { run, .. } if run.status != RunStatus::Completed => { + sleep(POLLING_INTERVAL).await; } - run_from_api => { - run = run_from_api; + response_from_api => { + response = response_from_api; break; } } } - let report = run + let report = response + .run .head_reports .into_iter() .next() @@ -55,11 +58,25 @@ pub async fn poll_results( info!("Report completed, here are the results:"); if let Some(impact) = report.impact { - info!("Impact: {}%", (impact * 100.0).round()); + 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() + ); } - info!("Conclusion: {:?}", report.conclusion); + info!("Conclusion: {}", report.conclusion); - info!("\nTo see the full report, visit: {}", response.run.url); + info!( + "\nTo see the full report, visit: {}", + style(response.run.url).blue().bold().underlined() + ); Ok(()) } From 20a36f37c7de2c8162699aecfbca0e4803b4e63d Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:25:49 +0000 Subject: [PATCH 17/26] feat(run): change verbs tense to continuous --- src/run/mod.rs | 4 ++-- src/run/runner/run.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/run/mod.rs b/src/run/mod.rs index e187a74..2ff74c5 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -112,13 +112,13 @@ pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> { let run_data = runner::run(&config, &system_info).await?; if !config.skip_upload { - start_group!("Upload the results"); + start_group!("Uploading performance data"); logger.persist_log_to_profile_folder(&run_data)?; let upload_result = uploader::upload(&config, &system_info, &provider, &run_data).await?; end_group!(); if provider.get_provider_slug() == "local" { - start_group!("Fetch the results"); + start_group!("Fetching the results"); poll_results::poll_results(api_client, &provider, upload_result.run_id).await?; end_group!(); } diff --git a/src/run/runner/run.rs b/src/run/runner/run.rs index 8afa8cd..46c7889 100644 --- a/src/run/runner/run.rs +++ b/src/run/runner/run.rs @@ -17,12 +17,12 @@ pub struct RunData { pub async fn run(config: &Config, system_info: &SystemInfo) -> Result { if !config.skip_setup { - start_group!("Prepare the environment"); + start_group!("Preparing the environment"); setup(system_info, config).await?; end_group!(); } //TODO: add valgrind version check - start_opened_group!("Run the benchmarks"); + start_opened_group!("Running the benchmarks"); let profile_folder = create_profile_folder()?; let mongo_tracer = if let Some(mongodb_config) = &config.instruments.mongodb { let mut mongo_tracer = MongoTracer::try_from(&profile_folder, mongodb_config)?; From f6b089fab4bfb24622e47e250f6172e202da8182 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:27:11 +0000 Subject: [PATCH 18/26] feat(cli): update style of terminal output --- src/app.rs | 6 ++++-- src/logger.rs | 29 ++++++++++++++++++++++++++--- src/main.rs | 7 +++++-- src/run/uploader/upload.rs | 7 +++++-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/app.rs b/src/app.rs index b323b1b..d0cedd0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use crate::{api_client::CodSpeedAPIClient, auth, prelude::*, run}; +use crate::{api_client::CodSpeedAPIClient, auth, logger::CODSPEED_U8_COLOR_CODE, prelude::*, run}; use clap::{ builder::{styling, Styles}, Parser, Subcommand, @@ -8,7 +8,9 @@ fn create_styles() -> Styles { styling::Styles::styled() .header(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) - .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD) + .literal( + styling::Ansi256Color(CODSPEED_U8_COLOR_CODE).on_default() | styling::Effects::BOLD, + ) .placeholder(styling::AnsiColor::Cyan.on_default()) } diff --git a/src/logger.rs b/src/logger.rs index 050ea38..dbcea0d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -4,13 +4,16 @@ use std::{ time::Duration, }; -use console::Style; +use console::{style, Style}; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; use log::Log; use simplelog::SharedLogger; use std::io::Write; +pub const CODSPEED_U8_COLOR_CODE: u8 = 208; // #FF8700 +const BLACK_U8_COLOR_CODE: u8 = 16; // #000 + /// This target is used exclusively to handle group events. pub const GROUP_TARGET: &str = "codspeed::group"; pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened"; @@ -135,11 +138,24 @@ impl Log for LocalLogger { if let Some(group_event) = get_group_event(record) { match group_event { GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { + println!( + " {}", + style(format!(" {} ", name.to_uppercase())) + .bold() + .color256(BLACK_U8_COLOR_CODE) + .on_color256(CODSPEED_U8_COLOR_CODE) + ); + println!(); + if *IS_TTY { let spinner = ProgressBar::new_spinner(); spinner.set_style( ProgressStyle::with_template( - " {spinner:>.cyan} {wide_msg:.cyan.bold}", + format!( + " {{spinner:>.{}}} {{wide_msg:.{}.bold}}", + CODSPEED_U8_COLOR_CODE, CODSPEED_U8_COLOR_CODE + ) + .as_str(), ) .unwrap(), ); @@ -155,10 +171,10 @@ impl Log for LocalLogger { let mut spinner = SPINNER.lock().unwrap(); if let Some(spinner) = spinner.as_mut() { spinner.finish_and_clear(); - // Separate groups with a newline println!(); } } + println!(); } } @@ -213,3 +229,10 @@ impl SharedLogger for LocalLogger { pub fn get_local_logger() -> Box { Box::new(LocalLogger::new()) } + +pub fn clean_logger() { + let mut spinner = SPINNER.lock().unwrap(); + if let Some(spinner) = spinner.as_mut() { + spinner.finish_and_clear(); + } +} diff --git a/src/main.rs b/src/main.rs index b46efe8..fb849dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod prelude; mod request_client; mod run; +use console::style; use prelude::*; use log::log_enabled; @@ -21,9 +22,9 @@ async fn main() { if let Err(err) = res { for cause in err.chain() { if log_enabled!(log::Level::Error) { - error!("Error {}", cause); + error!("{} {}", style("Error:").bold().red(), style(cause).red()); } else { - eprintln!("Error {}", cause); + eprintln!("Error: {}", cause); } } if log_enabled!(log::Level::Debug) { @@ -31,6 +32,8 @@ async fn main() { debug!("Caused by: {}", e); } } + logger::clean_logger(); + std::process::exit(1); } } diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index 530a970..cdb2173 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -5,6 +5,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 console::style; use tokio::io::AsyncWriteExt; use tokio_tar::Builder; @@ -48,9 +49,11 @@ async fn retrieve_upload_data( .map(|body| body.error) .unwrap_or(text); bail!( - "Failed to retrieve upload data: {}\n{}", + "Failed to retrieve upload data: {}\n -> {} {}", status, - error_message + style("Reason:").bold(), + // we have to manually apply the style to the error message, because nesting styles is not supported by the console crate: https://github.com/console-rs/console/issues/106 + style(error_message).red() ); } From dc177a1b5a4bc820d96bea1cc471666763d7152e Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:27:58 +0000 Subject: [PATCH 19/26] feat(valgrind): prevent trace valgrind logs to duplicate spinner lines --- src/run/runner/valgrind.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index e7f8909..bebf145 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -75,8 +75,8 @@ fn run_command_with_log_pipe(mut cmd: Command) -> Result { } suspend_progress_bar(|| { writer.write_all(&buffer[..bytes_read]).unwrap(); + trace!(target: VALGRIND_EXECUTION_TARGET, "{}{}", prefix, String::from_utf8_lossy(&buffer[..bytes_read])); }); - trace!(target: VALGRIND_EXECUTION_TARGET, "{}{}", prefix, String::from_utf8_lossy(&buffer[..bytes_read])); } Ok(()) } From aaf0e795dcf95d41ab644d5223795ea67933adb6 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:28:20 +0000 Subject: [PATCH 20/26] feat(run): disallow empty bench command --- src/run/runner/valgrind.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index bebf145..5af2771 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -51,11 +51,16 @@ lazy_static! { }; } -fn get_bench_command(config: &Config) -> String { - let bench_command = &config.command; - bench_command +fn get_bench_command(config: &Config) -> Result { + let bench_command = &config.command.trim(); + + if bench_command.is_empty() { + bail!("The bench command is empty"); + } + + Ok(bench_command // Fixes a compatibility issue with cargo 1.66+ running directly under valgrind <3.20 - .replace("cargo codspeed", "cargo-codspeed") + .replace("cargo codspeed", "cargo-codspeed")) } pub const VALGRIND_EXECUTION_TARGET: &str = "valgrind::execution"; @@ -137,7 +142,7 @@ pub fn measure( .arg(format!("--log-file={}", log_path.to_str().unwrap()).as_str()); // Set the command to execute - cmd.args(["sh", "-c", get_bench_command(config).as_str()]); + cmd.args(["sh", "-c", get_bench_command(config)?.as_str()]); // TODO: refactor and move this to the `Instrumentation` trait if let Some(mongo_tracer) = mongo_tracer { @@ -158,13 +163,23 @@ pub fn measure( mod tests { use super::*; + #[test] + fn test_get_bench_command_empty() { + let config = Config::test(); + assert!(get_bench_command(&config).is_err()); + assert_eq!( + get_bench_command(&config).unwrap_err().to_string(), + "The bench command is empty" + ); + } + #[test] fn test_get_bench_command_cargo() { let config = Config { command: "cargo codspeed bench".into(), ..Config::test() }; - assert_eq!(get_bench_command(&config), "cargo-codspeed bench"); + assert_eq!(get_bench_command(&config).unwrap(), "cargo-codspeed bench"); } #[test] @@ -180,12 +195,10 @@ pytest tests/ --codspeed ..Config::test() }; assert_eq!( - get_bench_command(&config), - r#" -cargo-codspeed bench --features "foo bar" + get_bench_command(&config).unwrap(), + r#"cargo-codspeed bench --features "foo bar" pnpm vitest bench "my-app" -pytest tests/ --codspeed -"# +pytest tests/ --codspeed"# ); } } From e35253ec127dbe520003dc0e81b0024ccfe53ff6 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:28:57 +0000 Subject: [PATCH 21/26] feat(run): do not display codspeed banner during local run --- src/run/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/run/mod.rs b/src/run/mod.rs index 2ff74c5..b1b03aa 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -97,7 +97,9 @@ pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> { let codspeed_config = CodSpeedConfig::load()?; let logger = Logger::new(&provider)?; - show_banner(); + if provider.get_provider_slug() != "local" { + show_banner(); + } debug!("config: {:#?}", config); if provider.get_provider_slug() == "local" { From 64ea4f8a9bcc305b7423812c16fbce74ae8447b3 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 17 Jul 2024 14:29:21 +0000 Subject: [PATCH 22/26] feat(run): update some logging --- src/run/poll_results.rs | 4 ++-- src/run/uploader/upload.rs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index 0f19223..cc97922 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -56,7 +56,6 @@ pub async fn poll_results( .next() .ok_or_else(|| anyhow!("No head report found in the run report"))?; - info!("Report completed, here are the results:"); if let Some(impact) = report.impact { let rounded_impact = (impact * 100.0).round(); let impact_text = if impact > 0.0 { @@ -70,8 +69,9 @@ pub async fn poll_results( impact_text, (response.allowed_regression * 100.0).round() ); + } else { + info!("No impact detected, reason: {}", report.conclusion); } - info!("Conclusion: {}", report.conclusion); info!( "\nTo see the full report, visit: {}", diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index cdb2173..031a57b 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -97,6 +97,14 @@ pub async fn upload( let upload_metadata = provider.get_upload_metadata(config, system_info, &archive_hash)?; debug!("Upload metadata: {:#?}", upload_metadata); + info!( + "Linked repository: {}\n", + style(format!( + "{}/{}", + upload_metadata.provider_metadata.owner, upload_metadata.provider_metadata.repository + )) + .bold(), + ); if upload_metadata.tokenless { let hash = upload_metadata.get_hash(); info!("CodSpeed Run Hash: \"{}\"", hash); @@ -106,10 +114,10 @@ pub async fn upload( let upload_data = retrieve_upload_data(config, &upload_metadata).await?; debug!("runId: {}", upload_data.run_id); - info!("Uploading profile data..."); + info!("Uploading performance data..."); debug!("Uploading {} bytes...", archive_buffer.len()); upload_archive_buffer(&upload_data, archive_buffer, &archive_hash).await?; - info!("Results uploaded."); + info!("Performance data uploaded"); Ok(UploadResult { run_id: upload_data.run_id, From 0e0258cca6e4b1afc9dcf365cf084c2c55c878da Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 23 Jul 2024 13:01:14 +0000 Subject: [PATCH 23/26] docs: update readme with CLI usage --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9d76722..f8afcb1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ CLI to gather performance data and upload performance reports to [CodSpeed](http -The `codspeed` CLI is designed to be used both in local in CI environments. +The `codspeed` CLI is designed to be used both in **local** in **CI environments**. The following CI providers are supported: @@ -35,12 +35,27 @@ Refer to the [releases page](https://github.com/CodSpeedHQ/runner/releases) to s ## Usage > [!NOTE] -> For now, the CLI only supports Ubuntu 20.04 and 22.04. +> For now, the CLI only supports Ubuntu 20.04, 22.04, and Debian 11, 12. -Example of a command to run benchmarks with [Vitest](https://docs.codspeed.io/benchmarks/nodejs/vitest): +First, authenticate with your CodSpeed account: ```bash -codspeed run --token=$CODSPEED_TOKEN -- pnpm vitest bench +codspeed auth login +``` + +Then, run benchmarks with the following command: + +```bash +codspeed run + +# Example, using https://github.com/CodSpeedHQ/codspeed-rust +codspeed run cargo codspeed run + +# Example, using https://github.com/CodSpeedHQ/pytest-codspeed +codspeed run pytest ./tests --codspeed + +# Example, using https://github.com/CodSpeedHQ/codspeed-node/tree/main/packages/vitest-plugin +codspeed run pnpm vitest bench ``` ``` From 3b6cbdc9d6de0cb82830b9b36c0594d790dcf941 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 23 Jul 2024 16:32:46 +0000 Subject: [PATCH 24/26] feat(cli): handle invalid token --- src/api_client.rs | 39 ++++++++++++++++++++++++++------------ src/run/uploader/upload.rs | 11 ++++++++++- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index 497f9d4..40a2e25 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -8,7 +8,8 @@ use nestify::nest; use serde::{Deserialize, Serialize}; pub struct CodSpeedAPIClient { - pub gql_client: GQLClient, + gql_client: GQLClient, + unauthenticated_gql_client: GQLClient, } impl TryFrom<&Cli> for CodSpeedAPIClient { @@ -17,19 +18,30 @@ impl TryFrom<&Cli> for CodSpeedAPIClient { let codspeed_config = CodSpeedConfig::load()?; Ok(Self { - gql_client: build_gql_api_client(&codspeed_config, args.api_url.clone()), + gql_client: build_gql_api_client(&codspeed_config, args.api_url.clone(), true), + unauthenticated_gql_client: build_gql_api_client( + &codspeed_config, + args.api_url.clone(), + false, + ), }) } } -fn build_gql_api_client(codspeed_config: &CodSpeedConfig, api_url: String) -> GQLClient { - let headers = match &codspeed_config.auth.token { - Some(token) => { - let mut headers = std::collections::HashMap::new(); - headers.insert("Authorization".to_string(), token.to_string()); - headers - } - None => Default::default(), +fn build_gql_api_client( + codspeed_config: &CodSpeedConfig, + api_url: String, + with_auth: bool, +) -> GQLClient { + let headers = if with_auth && codspeed_config.auth.token.is_some() { + let mut headers = std::collections::HashMap::new(); + headers.insert( + "Authorization".to_string(), + codspeed_config.auth.token.clone().unwrap(), + ); + headers + } else { + Default::default() }; GQLClient::new_with_config(ClientConfig { @@ -139,7 +151,7 @@ pub struct FetchLocalRunReportResponse { impl CodSpeedAPIClient { pub async fn create_login_session(&self) -> Result { let response = self - .gql_client + .unauthenticated_gql_client .query_unwrap::(include_str!("queries/CreateLoginSession.gql")) .await; match response { @@ -153,7 +165,7 @@ impl CodSpeedAPIClient { session_id: &str, ) -> Result { let response = self - .gql_client + .unauthenticated_gql_client .query_with_vars_unwrap::( include_str!("queries/ConsumeLoginSession.gql"), ConsumeLoginSessionVars { @@ -195,6 +207,9 @@ impl CodSpeedAPIClient { ), } } + Err(err) if err.contains_error_code("UNAUTHENTICATED") => { + bail!("Your session has expired, please login again using `codspeed auth login`") + } Err(err) => bail!("Failed to fetch local run report: {}", err), } } diff --git a/src/run/uploader/upload.rs b/src/run/uploader/upload.rs index 031a57b..3cbaf78 100644 --- a/src/run/uploader/upload.rs +++ b/src/run/uploader/upload.rs @@ -6,6 +6,7 @@ use crate::{prelude::*, request_client::REQUEST_CLIENT}; use async_compression::tokio::write::GzipEncoder; use base64::{engine::general_purpose, Engine as _}; use console::style; +use reqwest::StatusCode; use tokio::io::AsyncWriteExt; use tokio_tar::Builder; @@ -45,9 +46,17 @@ async fn retrieve_upload_data( if response.status().is_client_error() { let status = response.status(); let text = response.text().await?; - let error_message = serde_json::from_str::(&text) + let mut error_message = serde_json::from_str::(&text) .map(|body| body.error) .unwrap_or(text); + if status == StatusCode::UNAUTHORIZED { + let additional_message = if upload_metadata.platform == "local" { + "Run `codspeed auth login` to authenticate the CLI" + } else { + "Check that CODSPEED_TOKEN is set and has the correct value" + }; + error_message.push_str(&format!("\n\n{}", additional_message)); + } bail!( "Failed to retrieve upload data: {}\n -> {} {}", status, From 7af9b55037f63eec5956fd8d019aa2798883c989 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 24 Jul 2024 14:46:04 +0000 Subject: [PATCH 25/26] chore(runner): remove useless code in BuildkiteProvider --- src/run/ci_provider/buildkite/provider.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/run/ci_provider/buildkite/provider.rs b/src/run/ci_provider/buildkite/provider.rs index dd365f8..1dac710 100644 --- a/src/run/ci_provider/buildkite/provider.rs +++ b/src/run/ci_provider/buildkite/provider.rs @@ -23,7 +23,6 @@ pub struct BuildkiteProvider { pub ref_: String, pub head_ref: Option, pub base_ref: Option, - pub commit_hash: String, pub event: RunEvent, pub repository_root_path: String, } @@ -121,7 +120,6 @@ impl TryFrom<&Config> for BuildkiteProvider { } else { None }, - commit_hash: get_env_variable("BUILDKITE_COMMIT")?, event: get_run_event()?, repository_root_path, }) @@ -230,7 +228,6 @@ mod tests { assert_eq!(provider.ref_, "refs/heads/main"); assert_eq!(provider.base_ref, None); assert_eq!(provider.head_ref, None); - assert_eq!(provider.commit_hash, "abc123"); assert_eq!(provider.event, RunEvent::Push); assert_eq!( provider.repository_root_path, @@ -270,7 +267,6 @@ mod tests { assert_eq!(provider.ref_, "refs/pull/22/merge"); assert_eq!(provider.base_ref, Some("main".into())); assert_eq!(provider.head_ref, Some("feat/codspeed-runner".into())); - assert_eq!(provider.commit_hash, "abc123"); assert_eq!(provider.event, RunEvent::PullRequest); assert_eq!( provider.repository_root_path, From 04f0e4d49b5577efb157bb100c42c46f160e8223 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 24 Jul 2024 15:50:30 +0000 Subject: [PATCH 26/26] refactor(cli): move local logger to its own file --- src/app.rs | 4 +- src/auth.rs | 2 +- src/local_logger.rs | 166 ++++++++++++++++++++++++++ src/logger.rs | 165 ------------------------- src/main.rs | 4 +- src/run/ci_provider/local/provider.rs | 2 +- src/run/runner/valgrind.rs | 2 +- 7 files changed, 175 insertions(+), 170 deletions(-) create mode 100644 src/local_logger.rs diff --git a/src/app.rs b/src/app.rs index d0cedd0..47d8ebf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,6 @@ -use crate::{api_client::CodSpeedAPIClient, auth, logger::CODSPEED_U8_COLOR_CODE, prelude::*, run}; +use crate::{ + api_client::CodSpeedAPIClient, auth, local_logger::CODSPEED_U8_COLOR_CODE, prelude::*, run, +}; use clap::{ builder::{styling, Styles}, Parser, Subcommand, diff --git a/src/auth.rs b/src/auth.rs index 8592a82..23f27fc 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::logger::get_local_logger; +use crate::local_logger::get_local_logger; use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*}; use clap::{Args, Subcommand}; use console::style; diff --git a/src/local_logger.rs b/src/local_logger.rs new file mode 100644 index 0000000..652d49b --- /dev/null +++ b/src/local_logger.rs @@ -0,0 +1,166 @@ +use std::{ + env, + sync::{Arc, Mutex}, + time::Duration, +}; + +use console::{style, Style}; +use indicatif::{ProgressBar, ProgressStyle}; +use lazy_static::lazy_static; +use log::Log; +use simplelog::SharedLogger; +use std::io::Write; + +use crate::logger::{get_group_event, GroupEvent}; + +pub const CODSPEED_U8_COLOR_CODE: u8 = 208; // #FF8700 +const BLACK_U8_COLOR_CODE: u8 = 16; // #000 + +lazy_static! { + pub static ref SPINNER: Arc>> = Arc::new(Mutex::new(None)); + pub static ref IS_TTY: bool = std::io::IsTerminal::is_terminal(&std::io::stdout()); +} + +/// Hide the progress bar temporarily, execute `f`, then redraw the progress bar. +/// +/// If the output is not a TTY, `f` will be executed without hiding the progress bar. +pub fn suspend_progress_bar R, R>(f: F) -> R { + // If the output is a TTY, and there is a spinner, suspend it + if *IS_TTY { + if let Ok(mut spinner) = SPINNER.lock() { + if let Some(spinner) = spinner.as_mut() { + return spinner.suspend(f); + } + } + } + + // Otherwise, just run the function + f() +} + +pub struct LocalLogger { + log_level: log::LevelFilter, +} + +impl LocalLogger { + pub fn new() -> Self { + let log_level = env::var("CODSPEED_LOG") + .ok() + .and_then(|log_level| log_level.parse::().ok()) + .unwrap_or(log::LevelFilter::Info); + + LocalLogger { log_level } + } +} + +impl Log for LocalLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.log_level + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + if let Some(group_event) = get_group_event(record) { + match group_event { + GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { + println!( + " {}", + style(format!(" {} ", name.to_uppercase())) + .bold() + .color256(BLACK_U8_COLOR_CODE) + .on_color256(CODSPEED_U8_COLOR_CODE) + ); + println!(); + + if *IS_TTY { + let spinner = ProgressBar::new_spinner(); + spinner.set_style( + ProgressStyle::with_template( + format!( + " {{spinner:>.{}}} {{wide_msg:.{}.bold}}", + CODSPEED_U8_COLOR_CODE, CODSPEED_U8_COLOR_CODE + ) + .as_str(), + ) + .unwrap(), + ); + spinner.set_message(format!("{}...", name)); + spinner.enable_steady_tick(Duration::from_millis(100)); + SPINNER.lock().unwrap().replace(spinner); + } else { + println!("{}...", name); + } + } + GroupEvent::End => { + if *IS_TTY { + let mut spinner = SPINNER.lock().unwrap(); + if let Some(spinner) = spinner.as_mut() { + spinner.finish_and_clear(); + println!(); + } + } + println!(); + } + } + + return; + } + + suspend_progress_bar(|| print_record(record)); + } + + fn flush(&self) { + std::io::stdout().flush().unwrap(); + } +} + +/// Print a log record to the console with the appropriate style +fn print_record(record: &log::Record) { + let error_style = Style::new().red(); + let info_style = Style::new().white(); + let warn_style = Style::new().yellow(); + let debug_style = Style::new().blue().dim(); + let trace_style = Style::new().black().dim(); + + match record.level() { + log::Level::Error => eprintln!("{}", error_style.apply_to(record.args())), + log::Level::Warn => eprintln!("{}", warn_style.apply_to(record.args())), + log::Level::Info => println!("{}", info_style.apply_to(record.args())), + log::Level::Debug => println!( + "{}", + debug_style.apply_to(format!("[DEBUG::{}] {}", record.target(), record.args())), + ), + log::Level::Trace => println!( + "{}", + trace_style.apply_to(format!("[TRACE::{}] {}", record.target(), record.args())) + ), + } +} + +impl SharedLogger for LocalLogger { + fn level(&self) -> log::LevelFilter { + self.log_level + } + + fn config(&self) -> Option<&simplelog::Config> { + None + } + + fn as_log(self: Box) -> Box { + Box::new(*self) + } +} + +pub fn get_local_logger() -> Box { + Box::new(LocalLogger::new()) +} + +pub fn clean_logger() { + let mut spinner = SPINNER.lock().unwrap(); + if let Some(spinner) = spinner.as_mut() { + spinner.finish_and_clear(); + } +} diff --git a/src/logger.rs b/src/logger.rs index dbcea0d..0933238 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,19 +1,3 @@ -use std::{ - env, - sync::{Arc, Mutex}, - time::Duration, -}; - -use console::{style, Style}; -use indicatif::{ProgressBar, ProgressStyle}; -use lazy_static::lazy_static; -use log::Log; -use simplelog::SharedLogger; -use std::io::Write; - -pub const CODSPEED_U8_COLOR_CODE: u8 = 208; // #FF8700 -const BLACK_U8_COLOR_CODE: u8 = 16; // #000 - /// This target is used exclusively to handle group events. pub const GROUP_TARGET: &str = "codspeed::group"; pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened"; @@ -87,152 +71,3 @@ pub(super) fn get_group_event(record: &log::Record) -> Option { _ => None, } } - -lazy_static! { - pub static ref SPINNER: Arc>> = Arc::new(Mutex::new(None)); - pub static ref IS_TTY: bool = std::io::IsTerminal::is_terminal(&std::io::stdout()); -} - -/// Hide the progress bar temporarily, execute `f`, then redraw the progress bar. -/// -/// If the output is not a TTY, `f` will be executed without hiding the progress bar. -pub fn suspend_progress_bar R, R>(f: F) -> R { - // If the output is a TTY, and there is a spinner, suspend it - if *IS_TTY { - if let Ok(mut spinner) = SPINNER.lock() { - if let Some(spinner) = spinner.as_mut() { - return spinner.suspend(f); - } - } - } - - // Otherwise, just run the function - f() -} - -pub struct LocalLogger { - log_level: log::LevelFilter, -} - -impl LocalLogger { - pub fn new() -> Self { - let log_level = env::var("CODSPEED_LOG") - .ok() - .and_then(|log_level| log_level.parse::().ok()) - .unwrap_or(log::LevelFilter::Info); - - LocalLogger { log_level } - } -} - -impl Log for LocalLogger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= self.log_level - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - if let Some(group_event) = get_group_event(record) { - match group_event { - GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { - println!( - " {}", - style(format!(" {} ", name.to_uppercase())) - .bold() - .color256(BLACK_U8_COLOR_CODE) - .on_color256(CODSPEED_U8_COLOR_CODE) - ); - println!(); - - if *IS_TTY { - let spinner = ProgressBar::new_spinner(); - spinner.set_style( - ProgressStyle::with_template( - format!( - " {{spinner:>.{}}} {{wide_msg:.{}.bold}}", - CODSPEED_U8_COLOR_CODE, CODSPEED_U8_COLOR_CODE - ) - .as_str(), - ) - .unwrap(), - ); - spinner.set_message(format!("{}...", name)); - spinner.enable_steady_tick(Duration::from_millis(100)); - SPINNER.lock().unwrap().replace(spinner); - } else { - println!("{}...", name); - } - } - GroupEvent::End => { - if *IS_TTY { - let mut spinner = SPINNER.lock().unwrap(); - if let Some(spinner) = spinner.as_mut() { - spinner.finish_and_clear(); - println!(); - } - } - println!(); - } - } - - return; - } - - suspend_progress_bar(|| print_record(record)); - } - - fn flush(&self) { - std::io::stdout().flush().unwrap(); - } -} - -/// Print a log record to the console with the appropriate style -fn print_record(record: &log::Record) { - let error_style = Style::new().red(); - let info_style = Style::new().white(); - let warn_style = Style::new().yellow(); - let debug_style = Style::new().blue().dim(); - let trace_style = Style::new().black().dim(); - - match record.level() { - log::Level::Error => eprintln!("{}", error_style.apply_to(record.args())), - log::Level::Warn => eprintln!("{}", warn_style.apply_to(record.args())), - log::Level::Info => println!("{}", info_style.apply_to(record.args())), - log::Level::Debug => println!( - "{}", - debug_style.apply_to(format!("[DEBUG::{}] {}", record.target(), record.args())), - ), - log::Level::Trace => println!( - "{}", - trace_style.apply_to(format!("[TRACE::{}] {}", record.target(), record.args())) - ), - } -} - -impl SharedLogger for LocalLogger { - fn level(&self) -> log::LevelFilter { - self.log_level - } - - fn config(&self) -> Option<&simplelog::Config> { - None - } - - fn as_log(self: Box) -> Box { - Box::new(*self) - } -} - -pub fn get_local_logger() -> Box { - Box::new(LocalLogger::new()) -} - -pub fn clean_logger() { - let mut spinner = SPINNER.lock().unwrap(); - if let Some(spinner) = spinner.as_mut() { - spinner.finish_and_clear(); - } -} diff --git a/src/main.rs b/src/main.rs index fb849dd..b9bfac5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,14 @@ mod api_client; mod app; mod auth; mod config; +mod local_logger; mod logger; mod prelude; mod request_client; mod run; use console::style; +use local_logger::clean_logger; use prelude::*; use log::log_enabled; @@ -32,7 +34,7 @@ async fn main() { debug!("Caused by: {}", e); } } - logger::clean_logger(); + clean_logger(); std::process::exit(1); } diff --git a/src/run/ci_provider/local/provider.rs b/src/run/ci_provider/local/provider.rs index 088bd49..f0cbf71 100644 --- a/src/run/ci_provider/local/provider.rs +++ b/src/run/ci_provider/local/provider.rs @@ -2,7 +2,7 @@ use git2::Repository; use lazy_static::lazy_static; use simplelog::SharedLogger; -use crate::logger::get_local_logger; +use crate::local_logger::get_local_logger; use crate::prelude::*; use crate::run::{ ci_provider::{ diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index 5af2771..3c6d7ea 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -1,4 +1,4 @@ -use crate::logger::suspend_progress_bar; +use crate::local_logger::suspend_progress_bar; use crate::prelude::*; use crate::run::{ config::Config, instruments::mongo_tracer::MongoTracer,