This project provides a fast & deterministic simulation framework for testing and benchmarking distributed systems. It simulates network latency, bandwidth constraints, and process execution in a single-threaded, event-driven environment.
To use the matrix, you need to implement the ProcessHandle trait for your distributed system and the Message trait for the data exchanged between processes.
Messages must implement the Message trait, which requires defining a VirtualSize for bandwidth simulation.
use matrix::Message;
struct MyMessage {
data: u32,
}
impl Message for MyMessage {
fn VirtualSize(&self) -> usize {
// Much bigger than real size, but zero-cost
1000
}
}Implement ProcessHandle to define how your process reacts to initialization, messages, and timers.
use matrix::{ProcessHandle, Configuration, ProcessId, MessagePtr, TimerId};
use matrix::{Broadcast, SendTo, ScheduleTimerAfter, CurrentId, Debug};
struct MyProcess;
impl ProcessHandle for MyProcess {
fn Bootstrap(&mut self, config: Configuration) {
Debug!("Starting process {}", config.proc_num);
// Schedule initial events or broadcast messages
}
fn OnMessage(&mut self, from: ProcessId, message: MessagePtr) {
if let Some(msg) = message.TryAs::<MyMessage>() {
Debug!("Received message from {}: {}", from, msg.data);
}
}
fn OnTimer(&mut self, id: TimerId) {
// Handle timeouts
}
}Use SimulationBuilder to configure and start the simulation.
use matrix::SimulationBuilder;
fn main() {
let mut simulation = SimulationBuilder::NewDefault()
.AddPool("PoolName", 4, MyProcess::New)
.Build();
simulation.Run();
}SimulationBuilder: Configures the simulation environment.NewDefault(): Creates simulation with no processes and with default params.Seed(u64): Sets the random seed for deterministic execution.TimeBudget(Jiffies): Sets the maximum duration of the simulation.MaxLatency(Jiffies): Sets the maximum network latency.AddPool<P: ProcessHandle + 'static>(&str, usize, impl Fn() -> P): Creates pool of processes with specified name and size.NICBandwidth(BandwidthType): Configures network bandwidth limits.Bounded(usize)- Or
Unbounded
Build() -> Simulation: Clears global vars and builds simulation
Simulation: The engine driving the event loop.Run(): Starts the simulation loop.
These functions are available globally but must be called within the context of a running process step (e.g., inside OnMessage, Bootstrap, or OnTimer).
Broadcast(impl Message): Sends a message to all other processes.SendTo(ProcessId, impl Message): Sends a message to a specific process.ScheduleTimerAfter(Jiffies) -> TimerId: Schedules a timer interrupt for the current process after a delay.CurrentId() -> ProcessId: Returns the ID of the currently executing process.Now() -> Jiffies: Current time.ListPool(&str) -> Vec<ProcessId>: List all processes that are in the pool with specified name. Panics if pool does not exist.GlobalUniqueId() -> usize: Generates globally-unique id.
Get<T>(&str) -> TSet<T>(&str, T)Modify<T>(&str,impl FnOnce(&mut T)): Modify in-place.
Matrix integrates with the log crate and env_logger.
Debug!(fmt, ...): A macro wrapper aroundlog::debug!that automatically prepends current simulation time and process ID.
Debug builds (without the --release flag) additionally enable monotonous time-tracking.
Matrix output is controlled via the RUST_LOG environment variable.
RUST_LOG=info:- Shows high-level simulation status
- Progress bar is enabled
RUST_LOG=debug:- Enables the
Debug!macro output from within processes. - Shows crucial event timepoints during execution
- In order to filter events use the same variable (read docs for log crate)
- Enables the
Example run:
RUST_LOG=info cargo run --bin pingpong --release // With progress bar
RUST_LOG=debug cargo run --bin pingpong --release // All debug messages
RUST_LOG=pingpong=debug cargo run --bin pingpong --release // Only Debug! macro enabled for user crate
RUST_LOG=matrix=debug cargo run --bin pingpong --release // All debug messages for matrix crate