From 53ba4209220f0961e30069dafe3faf9dbd030a63 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Wed, 18 Feb 2026 09:58:52 +0100 Subject: [PATCH 1/6] Responsive progress --- dscale/src/progress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dscale/src/progress.rs b/dscale/src/progress.rs index bc3dc4a..9fa6726 100644 --- a/dscale/src/progress.rs +++ b/dscale/src/progress.rs @@ -3,7 +3,7 @@ use log::log_enabled; use crate::time::Jiffies; -const K_PROGRESS_TIMES: usize = 20; +const K_PROGRESS_TIMES: usize = 100; pub(crate) struct Bar { bar: ProgressBar, From 780a2010c50c1b2090e2a5cd897e88a4474cf513 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Wed, 18 Feb 2026 10:01:07 +0100 Subject: [PATCH 2/6] Omit logs in CI --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb9306..4a12577 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,5 +49,3 @@ jobs: - name: Verify DScale run: cargo run --bin ${{ matrix.binary }} --release --package examples - env: - RUST_LOG: info From 7756273a3e0c9c7636bb063c03e969ab4b5be013 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Sat, 21 Feb 2026 10:18:14 +0100 Subject: [PATCH 3/6] Update docs --- README.md | 72 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a72df50..0cc02c0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ This project provides a fast & deterministic simulation framework for testing an To use the DScale, you need to implement the `ProcessHandle` trait for your distributed system and the `Message` trait for the data exchanged between processes. -### 1. Define Messages +### 1. Install + +```shell +cargo add dscale +``` + +### 2. Define Messages Messages must implement the `Message` trait, which allows defining a `virtual_size` for bandwidth simulation. @@ -26,7 +32,7 @@ impl Message for MyMessage { } ``` -### 2. Implement Process Logic +### 3. Implement Process Logic Implement `ProcessHandle` to define how your process reacts to initialization, messages, and timers. @@ -58,7 +64,7 @@ impl ProcessHandle for MyProcess { } ``` -### 3. run the Simulation +### 4. Run the Simulation Use `Simulationbuilder` to configure the topology, network constraints, and start the simulation. @@ -86,59 +92,59 @@ fn main() { ### Simulation Control - **`SimulationBuilder`**: Configures the simulation environment. - - `default()`: Creates simulation with no processes and default parameters. - - `seed(u64)`: Sets the random seed for deterministic execution. - - `time_budget(Jiffies)`: Sets the maximum duration of the simulation. - - `add_pool(&str, usize)`: Creates a pool of processes. - - `latency_topology(&[LatencyDescription])`: Configures network latency between pools or within them. - - `nic_bandwidth(BandwidthDescription)`: Configures network bandwidth limits (per process). - - `Bounded(usize)`: Limits bandwidth (bytes per jiffy). + - `default`: Creates simulation with no processes and default parameters. + - `seed`: Sets the random seed for deterministic execution. + - `time_budget`: Sets the maximum duration of the simulation. + - `add_pool`: Creates a pool of processes. + - `latency_topology`: Configures network latency between pools or within them. + - `nic_bandwidth`: Configures network bandwidth limits (per process). + - `Bounded`: Limits bandwidth (bytes per jiffy). - `Unbounded`: No bandwidth limits. - - `build() -> Simulation`: Finalizes configuration and builds the simulation engine. + - `build`: Finalizes configuration and builds the simulation engine. - **`Simulation`**: The engine driving the event loop. - - `run()`: Starts the simulation loop. + - `run`: Starts the simulation loop. ### Network Topology - **`LatencyDescription`**: - - `WithinPool(&str, Distributions)`: Latency for messages between processes in the same pool. - - `BetweenPools(&str, &str, Distributions)`: Latency for messages between processes in different pools. + - `WithinPool`: Latency for messages between processes in the same pool. + - `BetweenPools`: Latency for messages between processes in different pools. - **`Distributions`**: - - `Uniform(Jiffies, Jiffies)` - - `Bernoulli(f64, Jiffies)` - - `Normal(Jiffies, Jiffies)` + - `Uniform` + - `Bernoulli` + - `Normal` ### Process Interaction (Context-Aware) These functions are available globally but must be called within the context of a running process step. -- **`broadcasst(impl Message)`**: Sends a message to all other processes. -- **`broadcasst_within_pool(&str, impl Message)`**: Sends a message to all other processes within a specific pool. -- **`send_to(ProcessId, impl Message)`**: Sends a message to a specific process. -- **`send_random_from_pool(&str, impl Message)`**: Sends a message to random process whithin pool. -- **`schedule_timer_after(Jiffies) -> TimerId`**: Schedules a timer interrupt for the current process. -- **`rank() -> ProcessId`**: Returns the ID of the currently executing process. -- **`now() -> Jiffies`**: Returns current simulation time. -- **`list_pool(&str) -> Vec`**: List all processes in a pool. -- **`choose_from_pool(&str) -> ProcessId`**: Choose random process id from specified pool. -- **`global_unique_id() -> usize`**: Generates a globally unique ID. +- **`broadcasst`**: Sends a message to all other processes. +- **`broadcasst_within_pool`**: Sends a message to all other processes within a specific pool. +- **`send_to`**: Sends a message to a specific process. +- **`send_random_from_pool`**: Sends a message to random process whithin pool. +- **`schedule_timer_after`**: Schedules a timer interrupt for the current process. +- **`rank`**: Returns the ID of the currently executing process. +- **`now`**: Returns current simulation time. +- **`list_pool`**: List all processes in a pool. +- **`choose_from_pool`**: Choose random process id from specified pool. +- **`global_unique_id`**: Generates a globally unique ID. ### Configuration (`dscale::global::configuration`) -- **`seed() -> u64`**: Returns the specific seed for the current process. -- **`process_number() -> usize`**: Returns total number of processes in the simulation. +- **`seed`**: Returns the specific seed for the current process. +- **`process_number`**: Returns total number of processes in the simulation. ### Any Key-Value (`dscale::global::anykv`) Useful for passing shared state, metrics, or configuration between processes or back to the host. -- **`get(&str) -> T`** -- **`set(&str, T)`** -- **`modify(&str, impl FnOnce(&mut T))`**: Modify in-place. +- **`get -> T`** +- **`set(T)`** +- **`modify`**: Modify in-place. ### Helpers (`dscale::helpers`) -- **`debug_process!(fmt, ...)`**: A macro that automatically prepends current simulation time and process ID. +- **`debug_process!`**: A macro that automatically prepends current simulation time and process ID. - **`Combiner`**: Structure which allows combining any values up to some known threshols. Can be useful for waiting for quorums. ## Logging Configuration (`RUST_LOG`) From 52718c80db06b64300e6cbb14277145cf8dfb280 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Sat, 21 Feb 2026 10:31:22 +0100 Subject: [PATCH 4/6] GLOBAL_POOL --- README.md | 6 +++-- dscale/src/communication/destination.rs | 1 - dscale/src/global/access.rs | 4 ++-- dscale/src/lib.rs | 1 + dscale/src/network/mod.rs | 9 +++----- dscale/src/process/handle.rs | 5 ++--- dscale/src/process/mod.rs | 1 - dscale/src/simulation_builder.rs | 29 ++++++++++++++++++------- dscale/src/topology.rs | 4 ++++ 9 files changed, 37 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0cc02c0..b7b819a 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ fn main() { - `default`: Creates simulation with no processes and default parameters. - `seed`: Sets the random seed for deterministic execution. - `time_budget`: Sets the maximum duration of the simulation. - - `add_pool`: Creates a pool of processes. + - `add_pool`: Creates a pool of processes. (At the same time all procs become part of GLOBAL_POOL) - `latency_topology`: Configures network latency between pools or within them. - `nic_bandwidth`: Configures network bandwidth limits (per process). - `Bounded`: Limits bandwidth (bytes per jiffy). @@ -106,6 +106,8 @@ fn main() { ### Network Topology +- **`GLOBAL_POOL`**: + - Contains all processes. Simple broadcast uses this pool - **`LatencyDescription`**: - `WithinPool`: Latency for messages between processes in the same pool. - `BetweenPools`: Latency for messages between processes in different pools. @@ -118,7 +120,7 @@ fn main() { These functions are available globally but must be called within the context of a running process step. -- **`broadcasst`**: Sends a message to all other processes. +- **`broadcasst`**: Sends a message to all other processes. (GLOBAL_POOL) - **`broadcasst_within_pool`**: Sends a message to all other processes within a specific pool. - **`send_to`**: Sends a message to a specific process. - **`send_random_from_pool`**: Sends a message to random process whithin pool. diff --git a/dscale/src/communication/destination.rs b/dscale/src/communication/destination.rs index e76358d..bcc72bd 100644 --- a/dscale/src/communication/destination.rs +++ b/dscale/src/communication/destination.rs @@ -1,7 +1,6 @@ use crate::ProcessId; pub enum Destination { - Broadcast, BroadcastWithinPool(&'static str), To(ProcessId), } diff --git a/dscale/src/global/access.rs b/dscale/src/global/access.rs index 9f0c72f..4b83f25 100644 --- a/dscale/src/global/access.rs +++ b/dscale/src/global/access.rs @@ -9,7 +9,7 @@ use crate::{ Jiffies, timer_manager::{TimerId, TimerManagerActor, next_timer_id}, }, - topology::Topology, + topology::{GLOBAL_POOL, Topology}, }; pub struct SimulationAccess { @@ -68,7 +68,7 @@ impl SimulationAccess { fn broadcast(&mut self, message: impl Message + 'static) { self.scheduled_messages.push(( self.process_on_execution, - Destination::Broadcast, + Destination::BroadcastWithinPool(GLOBAL_POOL), Rc::new(message), )); } diff --git a/dscale/src/lib.rs b/dscale/src/lib.rs index 599a762..eed64ff 100644 --- a/dscale/src/lib.rs +++ b/dscale/src/lib.rs @@ -35,6 +35,7 @@ pub use global::send_to; pub use network::BandwidthDescription; +pub use topology::GLOBAL_POOL; pub use topology::LatencyDescription; pub use random::Distributions; diff --git a/dscale/src/network/mod.rs b/dscale/src/network/mod.rs index 9ba5574..a46e1cd 100644 --- a/dscale/src/network/mod.rs +++ b/dscale/src/network/mod.rs @@ -43,16 +43,13 @@ impl Network { destination: Destination, ) { let targets = match destination { - Destination::Broadcast => self.nursery.keys().copied().collect::>(), - Destination::BroadcastWithinPool(pool_name) => { - self.topology.list_pool(pool_name).to_vec() - } - Destination::To(to) => vec![to], + Destination::BroadcastWithinPool(pool_name) => self.topology.list_pool(pool_name), + Destination::To(to) => &[to], }; debug!("Submitting message from {source}, targets of the message: {targets:?}",); - targets.into_iter().for_each(|target| { + targets.into_iter().copied().for_each(|target| { let routed_message = RoutedMessage { arrival_time: now() + Jiffies(1), // Without any latency message will arrive on next timepoint; step: ProcessStep { diff --git a/dscale/src/process/handle.rs b/dscale/src/process/handle.rs index 6305b52..7e63d21 100644 --- a/dscale/src/process/handle.rs +++ b/dscale/src/process/handle.rs @@ -4,7 +4,7 @@ //! by all processes in DScale simulations, as well as the `ProcessId` type used //! for process identification throughout the system. -use std::cell::RefCell; +use std::{cell::RefCell, rc::Rc}; use crate::{MessagePtr, time::timer_manager::TimerId}; @@ -58,8 +58,7 @@ use crate::{MessagePtr, time::timer_manager::TimerId}; /// [`ProcessHandle::on_message`]: ProcessHandle::on_message pub type ProcessId = usize; -pub(crate) type UniqueProcessHandle = Box; -pub(crate) type MutableProcessHandle = RefCell; +pub(crate) type MutableProcessHandle = Rc>; /// Core trait that defines the behavior of a process in DScale simulations. /// diff --git a/dscale/src/process/mod.rs b/dscale/src/process/mod.rs index c0f6fca..c522c7d 100644 --- a/dscale/src/process/mod.rs +++ b/dscale/src/process/mod.rs @@ -3,4 +3,3 @@ mod handle; pub(crate) use handle::MutableProcessHandle; pub use handle::ProcessHandle; pub use handle::ProcessId; -pub(crate) use handle::UniqueProcessHandle; diff --git a/dscale/src/simulation_builder.rs b/dscale/src/simulation_builder.rs index 1123765..ded6687 100644 --- a/dscale/src/simulation_builder.rs +++ b/dscale/src/simulation_builder.rs @@ -8,15 +8,16 @@ use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, + rc::Rc, }; use crate::{ ProcessHandle, ProcessId, Simulation, network::BandwidthDescription, - process::UniqueProcessHandle, + process::MutableProcessHandle, random::Seed, time::Jiffies, - topology::{LatencyDescription, LatencyTopology}, + topology::{GLOBAL_POOL, LatencyDescription, LatencyTopology}, }; fn init_logger() { @@ -74,7 +75,7 @@ pub struct SimulationBuilder { seed: Seed, time_budget: Jiffies, proc_id: usize, - pools: HashMap>, + pools: HashMap>, latency_topology: LatencyTopology, bandwidth: BandwidthDescription, } @@ -151,15 +152,27 @@ impl SimulationBuilder { name: &str, size: usize, ) -> SimulationBuilder { - let pool = self.pools.entry(name.to_string()).or_default(); - for _ in 0..size { + (0..size).for_each(|_| { let id = self.proc_id; self.proc_id += 1; - pool.push((id, Box::new(P::default()))); - } + let handle = Rc::new(RefCell::new(P::default())); + self.add_to_pool::

(name, id, handle.clone()); + self.add_to_pool::

(GLOBAL_POOL, id, handle.clone()); + }); + self } + fn add_to_pool( + &mut self, + name: &str, + id: usize, + handle: MutableProcessHandle, + ) { + let pool = self.pools.entry(name.to_string()).or_default(); + pool.push((id, handle)); + } + /// Sets the random seed for deterministic simulation execution. /// /// The seed controls all random behavior in the simulation, including network @@ -430,7 +443,7 @@ impl SimulationBuilder { let mut ids = Vec::new(); for (id, handle) in pool { ids.push(id); - procs.insert(id, RefCell::new(handle)); + procs.insert(id, handle); } pool_listing.insert(name, ids); } diff --git a/dscale/src/topology.rs b/dscale/src/topology.rs index b1c7c70..579eedc 100644 --- a/dscale/src/topology.rs +++ b/dscale/src/topology.rs @@ -12,6 +12,10 @@ use crate::{ProcessId, random::Distributions}; pub(crate) type LatencyTopology = HashMap<(ProcessId, ProcessId), Distributions>; pub(crate) type PoolListing = HashMap>; +/// Default pool for all processes within simulation. +/// Broadcasts by default use this pool. +pub const GLOBAL_POOL: &str = "global_pool"; + /// Describes network latency characteristics for different process relationships. /// /// `LatencyDescription` allows you to configure different latency patterns From 72c959459c021acf67ba0558ebe1f8fb8090fcb2 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Sat, 21 Feb 2026 10:39:38 +0100 Subject: [PATCH 5/6] Debug access function --- dscale/src/global/access.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dscale/src/global/access.rs b/dscale/src/global/access.rs index 4b83f25..49c454f 100644 --- a/dscale/src/global/access.rs +++ b/dscale/src/global/access.rs @@ -1,8 +1,11 @@ use std::{cell::RefCell, rc::Rc}; +use crate::now; + use crate::{ Destination, Message, ProcessId, actor::EventSubmitter, + debug_process, network::NetworkActor, random::Randomizer, time::{ @@ -144,22 +147,27 @@ pub(crate) fn schedule() { } pub fn schedule_timer_after(after: Jiffies) -> TimerId { + debug_process!("Access: scheduling timer after {after}"); with_access(|access| access.schedule_timer_after(after)) } pub fn broadcast(message: impl Message + 'static) { + debug_process!("Access: broadcasting globally"); with_access(|access| access.broadcast(message)); } pub fn broadcast_within_pool(pool: &'static str, message: impl Message + 'static) { + debug_process!("Access: broadcasting within: {pool}"); with_access(|access| access.broadcast_within_pool(pool, message)); } pub fn send_to(to: ProcessId, message: impl Message + 'static) { + debug_process!("Access: send to: {to}"); with_access(|access| access.send_to(to, message)); } pub fn send_random_from_pool(pool: &'static str, message: impl Message + 'static) { + debug_process!("Access: sending random from pool: {}", pool); with_access(|access| access.send_random_from_pool(pool, message)); } @@ -168,9 +176,11 @@ pub fn rank() -> ProcessId { } pub fn list_pool(name: &str) -> Vec { + debug_process!("Access: listing pool: {name}"); with_access(|access| access.list_pool(name).to_vec()) } pub fn choose_from_pool(name: &str) -> ProcessId { + debug_process!("Access: choosing random from pool: {name}"); with_access(|access| access.choose_from_pool(name)) } From 5d4e7174aa688565beda5dc43e0c01a64b956969 Mon Sep 17 00:00:00 2001 From: kshprenger Date: Sat, 21 Feb 2026 10:41:00 +0100 Subject: [PATCH 6/6] Better docs on debug --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b7b819a..6ad3ffe 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,10 @@ Useful for passing shared state, metrics, or configuration between processes or DScale output is controlled via the `RUST_LOG` environment variable. - **`RUST_LOG=info`**: Shows high-level simulation status and a progress bar. -- **`RUST_LOG=debug`**: Enables the `debug_process!` macro output and internal simulation events. -- **`RUST_LOG=your_crate=debug`**: Filter events only for your specific crate. +- **`RUST_LOG=debug`**: Enables all `debug_process!` macro output and all internal simulation events. +- **`RUST_LOG=full::path::to::your::file::or::crate=debug`**: Filter events only for your specific file or crate. -- Note `RUST_LOG=debug or RUST_LOG=any=debug` will work only without the `--release` flag. +- Note `RUST_LOG=debug or RUST_LOG=any::path=debug` will work only without the `--release` flag. ## Thanks to