Clocks¶
Clock abstraction and per-node clock models for simulating time skew and drift.
Simulation clock for tracking current simulation time.
The Clock provides a shared time reference for all entities in a simulation. The Simulation advances the clock as it processes events. Entities access the current time via their injected clock reference.
Clock ¶
Tracks the current simulation time.
The simulation creates one Clock instance and shares it with all entities. As events are processed, the simulation calls update() to advance time. Entities query now to get the current time for their logic.
This indirection allows entities to access simulation time without direct coupling to the Simulation class.
Attributes:
| Name | Type | Description |
|---|---|---|
now |
Instant
|
The current simulation time. |
Initialize clock at the given start time.
Per-node clocks with skew and drift models.
In real distributed systems, every node has its own clock with skew and drift. NodeClock transforms true simulation time into perceived local time, enabling modeling of clock-sensitive protocols (leader election, lease expiry, cache TTLs, distributed tracing).
Key insight: NodeClock is a view layer over the shared Clock. Events are still ordered by true simulation time (the global Clock). NodeClock only transforms the read side — what an entity perceives as "now". This avoids causality issues.
Usage::
from happysimulator import NodeClock, FixedSkew, LinearDrift, Duration
# Fixed offset: node clock is 50ms ahead of true time
clock = NodeClock(FixedSkew(Duration.from_seconds(0.05)))
# Drifting clock: 1000 ppm = 1ms drift per second
clock = NodeClock(LinearDrift(rate_ppm=1000))
# In an entity:
class RaftNode(Entity):
def __init__(self, name, node_clock):
super().__init__(name)
self._node_clock = node_clock
def set_clock(self, clock):
super().set_clock(clock)
self._node_clock.set_clock(clock)
@property
def local_now(self):
return self._node_clock.now
ClockModel ¶
Bases: Protocol
Protocol for time transformation models.
A ClockModel transforms true simulation time into perceived local time. Implementations define how a node's clock deviates from the global clock.
FixedSkew ¶
Constant time offset — clock is always ahead or behind by a fixed amount.
A positive offset means the clock reads ahead of true time (fast clock). A negative offset means the clock reads behind true time (slow clock).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
offset
|
Duration
|
The constant offset to apply. Positive = ahead, negative = behind. |
required |
LinearDrift ¶
Clock that runs faster or slower than true time, accumulating drift.
Drift is specified in parts per million (ppm). A rate of 1000 ppm means the clock gains 1ms per second of elapsed true time. Negative ppm means the clock runs slow.
The drift accumulates linearly: at true time T seconds from epoch, the perceived time is T + (T * rate_ppm / 1_000_000) seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rate_ppm
|
float
|
Drift rate in parts per million. Positive = fast, negative = slow. |
required |
NodeClock ¶
Per-node clock that transforms true simulation time via a ClockModel.
NodeClock wraps a base Clock (the shared simulation clock) and applies a ClockModel to transform what the node perceives as "now". The base clock is injected via set_clock(), typically forwarded from Entity.set_clock().
This is a plain class, NOT an Entity. Entities hold a NodeClock reference and forward clock injection to it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ClockModel | None
|
The clock model defining how time is transformed. If None, the node clock returns true time (identity). |
None
|
now
property
¶
The perceived local time, transformed by the clock model.
Returns true time if no model is set.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If accessed before clock injection. |