From 596d82fe480248ce6c89b95990245f213145bba3 Mon Sep 17 00:00:00 2001 From: Thanos Makatos Date: Mon, 6 Jul 2026 09:20:11 +0000 Subject: [PATCH] add optional dry run mode This allows for all the decisions to be made and reported except the actual decisions to be executed. This helps debugging. Signed-off-by: Thanos Makatos --- io-thread-controller.json | 5 +++-- src/config.rs | 5 +++++ src/controller.rs | 25 +++++++++++++++++++++++++ src/main.rs | 6 ++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/io-thread-controller.json b/io-thread-controller.json index c6bb0f9..6f0dd44 100644 --- a/io-thread-controller.json +++ b/io-thread-controller.json @@ -8,6 +8,7 @@ "cooldown_secs": 10.0, "engine": "threshold", "engine_config_dir": "/etc/io-thread-controller.d/engines", - "backend_config_dir": "/etc/io-thread-controller.d/backends" - "vm_state_path": "/run/io-thread-controller/vm-ownership.json" + "backend_config_dir": "/etc/io-thread-controller.d/backends", + "vm_state_path": "/run/io-thread-controller/vm-ownership.json", + "dry_run": false } diff --git a/src/config.rs b/src/config.rs index 58a4584..5f4f796 100644 --- a/src/config.rs +++ b/src/config.rs @@ -75,6 +75,10 @@ pub struct Config { /// Emit a one-time legend for status-line fields at startup. #[serde(default)] pub print_status_header: bool, + /// When true, log the scaling verdict but skip the actuation + /// call to `set_thread_count`. + #[serde(default)] + pub dry_run: bool, } fn default_true() -> bool { @@ -127,6 +131,7 @@ impl Default for Config { enable_per_vm_status_line: true, enable_aggregate_status_line: true, print_status_header: false, + dry_run: false, } } } diff --git a/src/controller.rs b/src/controller.rs index 59e2557..09b64de 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -650,6 +650,31 @@ impl Controller { return Ok(()); } + if self.cfg.dry_run { + tracing::info!( + target: "controller", + vm = %instance.id, + action = %action, + target, + "dry-run: would scale" + ); + if matches!(action, ScaleAction::Up(_) | ScaleAction::Down(_)) { + instance.status.write().await.cooldown_until = + Some(Instant::now() + Duration::from_secs_f64(self.cfg.cooldown_secs)); + } + self.engine + .on_applied( + &instance.id, + AppliedOutcome::DryRun { + action, + prev_thread_count: previous_count, + prev_io_count_total: previous_io_count, + }, + ) + .await; + return Ok(()); + } + match instance.client.set_thread_count(target).await { Ok(()) => { let mut status = instance.status.write().await; diff --git a/src/main.rs b/src/main.rs index dd0a159..4d6f959 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,6 +77,9 @@ struct Cli { /// Emit a one-time legend for uptime-style status fields. #[arg(long)] print_status_header: bool, + /// Log scaling decisions without calling set_thread_count. + #[arg(long)] + dry_run: bool, } /// See `Cli::log_style`. @@ -123,6 +126,9 @@ async fn main() -> Result<(), IoThreadControllerError> { if cli.print_status_header { cfg.print_status_header = true; } + if cli.dry_run { + cfg.dry_run = true; + } validate_config(&cfg)?; let backends = registered_backends(&cfg)?; run(cfg, backends).await?;