diff --git a/src/cli/exec/mod.rs b/src/cli/exec/mod.rs index 765f78ec..8719128d 100644 --- a/src/cli/exec/mod.rs +++ b/src/cli/exec/mod.rs @@ -89,6 +89,7 @@ fn build_orchestrator_config( show_full_output: args.shared.show_full_output, poll_results_options, extra_env: HashMap::new(), + fair_sched: args.shared.experimental.experimental_fair_sched, }) } diff --git a/src/cli/experimental.rs b/src/cli/experimental.rs new file mode 100644 index 00000000..fdebba20 --- /dev/null +++ b/src/cli/experimental.rs @@ -0,0 +1,53 @@ +use crate::local_logger::icons::Icon; +use clap::Args; +use console::style; + +/// Experimental flags that may change or be removed without notice. +/// +/// These flags are under active development and their behavior is not guaranteed +/// to remain stable across releases. +#[derive(Args, Debug, Clone)] +pub struct ExperimentalArgs { + /// Enable valgrind's --fair-sched option. + #[arg( + long, + default_value_t = false, + help_heading = "Experimental", + env = "CODSPEED_EXPERIMENTAL_FAIR_SCHED" + )] + pub experimental_fair_sched: bool, +} + +impl ExperimentalArgs { + /// Returns the names of all experimental flags that were explicitly set by the user. + pub fn active_flags(&self) -> Vec<&'static str> { + let mut flags = Vec::new(); + if self.experimental_fair_sched { + flags.push("--experimental-fair-sched"); + } + flags + } + + /// If any experimental flags are active, prints a warning to stderr. + pub fn warn_if_active(&self) { + let flags = self.active_flags(); + if flags.is_empty() { + return; + } + + let flag_list = flags + .iter() + .map(|f| style(*f).bold().to_string()) + .collect::>() + .join(", "); + + eprintln!( + "\n {} Experimental flags enabled: {}\n \ + These may change or be removed without notice.\n \ + Share feedback at {}.\n", + style(Icon::Warning.to_string()).yellow(), + flag_list, + style("https://github.com/CodSpeedHQ/codspeed/issues").underlined(), + ); + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 95553b5b..15ba60db 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,5 +1,6 @@ mod auth; pub(crate) mod exec; +pub(crate) mod experimental; pub(crate) mod run; mod setup; mod shared; @@ -15,7 +16,7 @@ use std::path::PathBuf; use crate::{ api_client::CodSpeedAPIClient, config::CodSpeedConfig, - local_logger::{CODSPEED_U8_COLOR_CODE, init_local_logger}, + local_logger::{CODSPEED_U8_COLOR_CODE, IS_TTY, init_local_logger}, prelude::*, project_config::DiscoveredProjectConfig, }; @@ -23,6 +24,27 @@ use clap::{ Parser, Subcommand, builder::{Styles, styling}, }; +use console::Term; + +/// Guard that hides the terminal cursor on creation and restores it on drop. +struct CursorGuard; + +impl CursorGuard { + fn new() -> Self { + if *IS_TTY { + let _ = Term::stderr().hide_cursor(); + } + Self + } +} + +impl Drop for CursorGuard { + fn drop(&mut self) { + if *IS_TTY { + let _ = Term::stderr().show_cursor(); + } + } +} fn create_styles() -> Styles { styling::Styles::styled() @@ -99,6 +121,8 @@ enum Commands { pub async fn run() -> Result<()> { let cli = Cli::parse(); + // Important: keep this after the Cli::parse() because the function can exit the process by itself, skipping the drop of the CursorGuard + let _cursor_guard = CursorGuard::new(); let codspeed_config = CodSpeedConfig::load_with_override(cli.config_name.as_deref(), cli.oauth_token.as_deref())?; let api_client = CodSpeedAPIClient::try_from((&cli, &codspeed_config))?; @@ -125,6 +149,7 @@ pub async fn run() -> Result<()> { match cli.command { Commands::Run(args) => { + args.shared.experimental.warn_if_active(); run::run( *args, &api_client, @@ -135,6 +160,7 @@ pub async fn run() -> Result<()> { .await? } Commands::Exec(args) => { + args.shared.experimental.warn_if_active(); exec::run( *args, &api_client, diff --git a/src/cli/run/mod.rs b/src/cli/run/mod.rs index 2036b34e..fa0b6a93 100644 --- a/src/cli/run/mod.rs +++ b/src/cli/run/mod.rs @@ -49,6 +49,7 @@ impl RunArgs { /// Constructs a new `RunArgs` with default values for testing purposes pub fn test() -> Self { use super::PerfRunArgs; + use super::experimental::ExperimentalArgs; use crate::RunnerMode; Self { @@ -72,6 +73,9 @@ impl RunArgs { enable_perf: false, perf_unwinding_mode: None, }, + experimental: ExperimentalArgs { + experimental_fair_sched: false, + }, }, instruments: vec![], mongo_uri_env_name: None, @@ -119,6 +123,7 @@ fn build_orchestrator_config( show_full_output: args.shared.show_full_output, poll_results_options, extra_env: HashMap::new(), + fair_sched: args.shared.experimental.experimental_fair_sched, }) } diff --git a/src/cli/shared.rs b/src/cli/shared.rs index 24e821ef..9de640f5 100644 --- a/src/cli/shared.rs +++ b/src/cli/shared.rs @@ -1,3 +1,4 @@ +use super::experimental::ExperimentalArgs; use crate::VERSION; use crate::executor::config::SimulationTool; use crate::local_logger::CODSPEED_U8_COLOR_CODE; @@ -116,6 +117,9 @@ pub struct ExecAndRunSharedArgs { #[command(flatten)] pub perf_run_args: PerfRunArgs, + + #[command(flatten)] + pub experimental: ExperimentalArgs, } impl ExecAndRunSharedArgs { diff --git a/src/executor/config.rs b/src/executor/config.rs index 390673a9..057fd2fd 100644 --- a/src/executor/config.rs +++ b/src/executor/config.rs @@ -79,6 +79,8 @@ pub struct OrchestratorConfig { pub poll_results_options: PollResultsOptions, /// Additional environment variables forwarded to executor subprocesses. pub extra_env: HashMap, + /// Enable valgrind's --fair-sched option. + pub fair_sched: bool, } /// Per-execution configuration passed to executors. @@ -111,6 +113,8 @@ pub struct ExecutorConfig { /// Whether to enable language-level introspection (Node.js, Go wrappers in PATH). /// Disabled for exec-harness targets since they don't need it. pub enable_introspection: bool, + /// Enable valgrind's --fair-sched option. + pub fair_sched: bool, } #[derive(Debug, Clone, PartialEq)] @@ -186,6 +190,7 @@ impl OrchestratorConfig { go_runner_version: self.go_runner_version.clone(), extra_env: self.extra_env.clone(), enable_introspection, + fair_sched: self.fair_sched, } } } @@ -223,6 +228,7 @@ impl OrchestratorConfig { show_full_output: false, poll_results_options: PollResultsOptions::new(false, None), extra_env: HashMap::new(), + fair_sched: false, } } } diff --git a/src/executor/valgrind/measure.rs b/src/executor/valgrind/measure.rs index 6517f64a..6a52f13c 100644 --- a/src/executor/valgrind/measure.rs +++ b/src/executor/valgrind/measure.rs @@ -17,7 +17,7 @@ use std::{env::consts::ARCH, process::Command}; use tempfile::TempPath; /// Builds the Valgrind argument list for the given simulation tool. -fn get_valgrind_args(tool: &SimulationTool) -> Vec { +fn get_valgrind_args(tool: &SimulationTool, config: &ExecutorConfig) -> Vec { let mut args: Vec = [ "-q", "--trace-children=yes", @@ -50,6 +50,11 @@ fn get_valgrind_args(tool: &SimulationTool) -> Vec { "--trace-children-skip={}", children_skip_patterns.join(",") )); + + if config.fair_sched { + args.push("--fair-sched=yes".to_string()); + } + args } @@ -114,7 +119,7 @@ pub async fn measure( cmd.current_dir(abs_cwd); } // Configure valgrind - let valgrind_args = get_valgrind_args(&config.simulation_tool); + let valgrind_args = get_valgrind_args(&config.simulation_tool, config); let log_path = profile_folder.join("valgrind.log"); cmd.arg("valgrind").args(valgrind_args.iter()); if config.simulation_tool == SimulationTool::Callgrind { diff --git a/src/main.rs b/src/main.rs index 35cef6f8..518b8d3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,9 @@ use codspeed_runner::{clean_logger, cli}; -use console::{Term, style}; +use console::style; use log::log_enabled; -struct HiddenCursor(Term); - -impl HiddenCursor { - fn new() -> Self { - let term = Term::stderr(); - let _ = term.hide_cursor(); - Self(term) - } -} - -impl Drop for HiddenCursor { - fn drop(&mut self) { - let _ = self.0.show_cursor(); - } -} - #[tokio::main(flavor = "current_thread")] async fn main() { - let _cursor = HiddenCursor::new(); let res = cli::run().await; if let Err(err) = res { // Show the primary error