Time & Concurrency
Why packets carry time, and how to observe runtime concurrency.
Days is designed around many concurrent actors (sources, sinks, schedulers, switches). Two design choices are especially important for performance and correctness:
- Time is carried in messages (packets/frames), and most models maintain a local
time: f64. - Concurrency is optionally observable via
tracinghooks and a small runtime sampler.
Time propagation via Packet.time
Most message handlers treat Packet.time as “now”:
- sources create packets with
Packet::new(..., creation_time)and setpacket.timeto the current simulated time, - schedulers compute serialization delay and update the packet with
packet.departure_update(now + timeout), - sinks record statistics at the packet’s arrival time.
This reduces pressure on cx.time(), which can be a contention point when many actors run concurrently.
When built with the test feature, many handlers assert:
packet.timematchescx.time()within a small epsilon,- local time does not move backwards.
These checks validate that “time-in-message” is consistent with the simulation engine.
Local time fields
Core models keep a local time: f64:
PacketSwitch.time(src/switches/switch.rs)Port.time,DRRServer.time, etc. (src/schedulers/)- endpoint time in sources/sinks (
src/flows/) - link-layer time in
Link/PFC components (src/l2/)
The local time is updated from the incoming message (or from cx.time() only in limited cases).
Concurrency tracing
Days supports two complementary mechanisms:
- A tracing layer (
ConcurrencyTrackerLayer,src/utils/tracing.rs) increments/decrements a globalACTIVE_TASKScounter on span enter/exit. - A wall-clock sampler (
start_wall_clock_concurrency_sampler,src/utils/tracing.rs) samplesACTIVE_TASKSduringSimulation::step_untiland logs a wall-clock average concurrency (plus the peak observed by the layer).
Enable it via config:
tracing_active = true
tracing_interval = 1.0This is intended as a lightweight way to correlate workload shape with runtime behavior.