DScale
A fast, deterministic simulation framework for testing and benchmarking distributed systems. It simulates network latency, bandwidth constraints, and process execution in an event-driven environment with support for both single-threaded and parallel execution modes.
Usage
1. Install
In your project
cargo add dscale
2. Define Messages
Messages must implement the Message trait, which allows defining a virtual_size for bandwidth simulation.
use Message;
// Or (if there is no need in bandwidth)
3. Implement Process Logic
Implement ProcessHandle to define how your process reacts to initialization, messages, and timers.
use ;
use ;
use configuration;
;
4. Run the Simulation
Use SimulationBuilder to configure the topology, network constraints, and start the simulation.
use ;
Parallel Execution
For large simulations, enable parallel execution to distribute process steps across multiple threads:
let mut runner = default
.
.within_pool_latency
.time_budget
.parallel // use 8 worker threads
.build;
runner.run_full_budget;
When is parallel mode efficient?
- A lot of simulated processes (at least 200-300)
- on_message execution takes most of simulation time
- Independent work inside on_message (not so much synchronization)
[!CAUTION] You can safely run only one simulation in parallel (simple or parallel), because of thread local state. Simulation lifetime is bounded to runner instance. Once it drops you can run another simulation.
Fault Injection
DScale supports injecting network faults into simulations. Faults are scheduled as timed events — you specify when a fault starts and when it ends.
[!IMPORTANT] Fault injection requires building with the
FAULTenvironment variable set toon:FAULT=on cargo run --bin my_exampleWithout
FAULT=on, all fault-related events will not do anything.
[!CAUTION] Fault injection is only supported in single-threaded (
simple) mode. Using faults withparallelmode will panic at build time.
let mut runner = default
.
.within_pool_latency
.time_budget
// Break the link between rank 0 and rank 1 from time 100 to 500
.break_link
// Isolate rank 2 (all links broken) from time 200 to 800
.isolate
.simple
.build;
runner.run_full_budget;
| Method | Description |
|---|---|
break_link(start, end, rank1, rank2) |
Breaks the link between two ranks for the given time interval |
isolate(start, end, rank) |
Isolates a rank (breaks all its links) for the given time interval |
Public API
Simulation Control
SimulationBuilder — Configures the simulation environment.
| Method | Description |
|---|---|
default |
Creates simulation with no processes and default parameters |
seed |
Sets the random seed for deterministic execution |
time_budget |
Sets the maximum simulation duration |
add_pool |
Creates a named pool of processes (all processes also join GLOBAL_POOL) |
within_pool_latency(pool, distribution) |
Configures latency between processes within a pool |
between_pool_latency(pool_a, pool_b, distribution) |
Configures latency between two pools (symmetric). Every pool pair must have latency configured before calling build |
vnic_bandwidth |
Configures per-process network bandwidth limits for "virtual" NIC. Bounded(usize): limits bandwidth (bytes per jiffy). Unbounded: no bandwidth limits (default) |
simple |
Selects single-threaded execution (default). Mutually exclusive with parallel — calling both panics |
parallel(threads) |
Selects parallel execution with the given number of worker threads. Mutually exclusive with simple — calling both panics |
break_link(start, end, rank1, rank2) |
Breaks the link between two ranks for the given time interval. Requires FAULT=on. Simple mode only |
isolate(start, end, rank) |
Isolates a rank (breaks all its links) for the given time interval. Requires FAULT=on. Simple mode only |
build |
Finalizes configuration and returns a simulation runner |
SimulationRunner
| Method | Description |
|---|---|
run_full_budget |
Runs the simulation until the time budget is exhausted |
run_steps |
Runs the simulation until it performs the requested number of steps or the global budget is exhausted |
run_sub_budget |
Runs the simulation until the sub-budget starting from current timepoint or global budget are exhausted |
Network Topology
Constants
| Constant | Description |
|---|---|
GLOBAL_POOL |
Implicit pool containing all processes. broadcast uses this pool |
Distributions
| Variant | Description |
|---|---|
Uniform(low, high) |
Uniform distribution over [low, high] |
Bernoulli(p, value) |
With probability p the latency is value, otherwise 0 |
Normal { mean, std_dev, low, high } |
Truncated normal distribution clamped to [low, high] |
Process Interaction (Context-Aware)
These functions are available globally but must be called within the context of a running process step.
| Function | Description |
|---|---|
broadcast |
Shortcut for broadcast_within_pool(GLOBAL_POOL) |
broadcast_within_pool |
Sends a message to all processes within a named pool |
send_to |
Sends a message to a specific process by rank |
send_random |
Shortcut for send_random_from_pool(GLOBAL_POOL) |
send_random_from_pool |
Sends a message to a random process within a named pool |
schedule_timer_after |
Schedules a timer for the current process, returns a TimerId |
rank |
Returns the rank of the currently executing process (ranks start at 0) |
now |
Returns the current simulation time |
list_pool |
Returns a slice of all process ranks in a pool |
choose_from_pool |
Picks a random process rank from a named pool |
global_unique_id |
Generates a globally unique monotonic ID |
Configuration (dscale::global::configuration)
| Function | Description |
|---|---|
seed |
Returns the deterministic seed for the current process |
process_number |
Returns total number of processes in the simulation |
Key-Value Store (dscale::global::kv)
Thread-safe store for passing shared state, metrics, or configuration between processes or back to the host.
| Function | Description |
|---|---|
set(key, value) |
Stores a value under the given key |
get(key) -> T |
Retrieves a clone of the value (panics if missing or wrong type) |
modify(key, f) |
Mutates the value in place |
Macros
All logging macros prefix output with the current simulation time and process rank ([Now: ... | P...]). Controlled by the RUST_LOG environment variable.
| Macro | Description |
|---|---|
dscale_trace! |
Logs at trace level |
dscale_debug! |
Logs at debug level |
dscale_info! |
Logs at info level |
dscale_warn! |
Logs at warn level |
dscale_error! |
Logs at error level |
Helpers (dscale::helpers)
| Item | Description |
|---|---|
Combiner |
Collects values until a threshold is reached, then yields them all at once. Useful for quorum-based logic |
Message Downcasting (MessagePtr)
| Method | Description |
|---|---|
try_as_type::<T>() |
Attempts to downcast to T, returns Option<&T> |
as_type::<T>() |
Downcasts to T, panics if the type does not match |
is::<T>() |
Returns true if the message is of type T |
Logging Configuration (RUST_LOG)
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 alldscale_debug!macro output and all internal simulation events.RUST_LOG=full::path::to::your::file::or::crate=debug,another::path=debug: Filter events only for your specific file or crate.
[!WARNING]
RUST_LOG=debugand path-level debug filters only work without the--releaseflag.