# Cluster Simulator The simulator (`turnstone-sim`) creates lightweight simulated nodes that talk to a real Redis instance using the standard turnstone protocol. External observers — `TurnstoneClient`, `turnstone-console`, real bridges — see identical behavior. No LLM backend is needed. ## Quick Start ```bash pip install turnstone[sim] # 10 nodes, steady load, 60 seconds turnstone-sim --nodes 10 --scenario steady --duration 60 --mps 5 # 100 nodes via Docker docker compose --profile sim up redis console sim ``` ## How It Works Each simulated node is an asyncio coroutine (not a thread or process), so 1000 nodes run efficiently on a single event loop. The simulator: 1. Registers nodes via Redis heartbeats (same keys as real bridges) 2. Accepts messages from per-node and shared inbound queues 3. Simulates LLM responses with configurable latency and token generation 4. Simulates tool execution with configurable latency and failure rates 5. Publishes real protocol events (`ContentEvent`, `StateChangeEvent`, `TurnCompleteEvent`, etc.) 6. Reports latency, throughput, and utilization metrics at completion ``` TurnstoneClient → Redis Queue → SimNode → Redis Pub/Sub → TurnstoneClient ↓ turnstone-console (cluster dashboard) ``` ## Scenarios | Scenario | Description | |----------|-------------| | `steady` | Inject messages at a constant rate (`--mps`) for `--duration` seconds | | `burst` | Push `--burst-size` messages instantly, then wait for completion | | `node_failure` | Steady load + periodically kill nodes to test redistribution | | `directed` | Send messages to specific nodes via `target_node` routing | | `lifecycle` | Create, use, and close workstreams across nodes | ## CLI Reference ``` turnstone-sim [options] ``` ### Cluster | Flag | Default | Description | |------|---------|-------------| | `--nodes` | `10` | Number of simulated nodes | ### Scenario | Flag | Default | Description | |------|---------|-------------| | `--scenario` | `steady` | Scenario name | | `--duration` | `60` | Duration in seconds | | `--mps` | `5.0` | Messages per second (steady) | | `--burst-size` | `100` | Messages to send (burst) | | `--node-kill-interval` | `15` | Seconds between kills (node_failure) | | `--node-kill-count` | `1` | Nodes per kill cycle | ### Simulation | Flag | Default | Description | |------|---------|-------------| | `--llm-latency` | `2.0` | Mean LLM response latency (seconds) | | `--tool-latency` | `0.5` | Mean tool execution latency (seconds) | | `--tool-failure-rate` | `0.02` | Tool failure probability (0.0–1.0) | | `--seed` | — | Random seed for reproducibility | ### Redis | Flag | Default | Description | |------|---------|-------------| | `--redis-host` | `localhost` | Redis host | | `--redis-port` | `6379` | Redis port | | `--redis-password` | — | Redis password | | `--prefix` | `turnstone` | Redis key prefix | ### Output | Flag | Default | Description | |------|---------|-------------| | `--metrics-file` | — | Write JSON report to file | | `--log-level` | `INFO` | Log verbosity | ## Example: Load Testing ```bash # 100 nodes, high throughput, 2 minutes turnstone-sim --nodes 100 --scenario steady --duration 120 --mps 50 # Burst of 500 messages across 50 nodes turnstone-sim --nodes 50 --scenario burst --burst-size 500 --duration 60 # Node failure resilience (kill 2 nodes every 10 seconds) turnstone-sim --nodes 20 --scenario node_failure --duration 120 \ --node-kill-interval 10 --node-kill-count 2 # Fast simulation (low latency, no failures) turnstone-sim --nodes 10 --scenario steady --duration 30 \ --llm-latency 0.1 --tool-latency 0.05 --tool-failure-rate 0 --mps 10 ``` ## Metrics Report The simulator prints a summary at completion: ``` ============================================================ SIMULATION REPORT ============================================================ Scenario: steady Nodes: 100 Duration: 60.2s Total turns: 295 Total errors: 5 Node kills: 0 ------------------------------------------------------------ THROUGHPUT Messages/sec: 4.97 Turns/sec: 4.89 ------------------------------------------------------------ LATENCY (seconds) p50: 3.21 p90: 5.44 p99: 8.12 mean: 3.56 max: 12.1 ------------------------------------------------------------ UTILIZATION Mean ws/node: 2.3 Max ws/node: 8 Idle nodes: 12 ============================================================ ``` Use `--metrics-file report.json` to write the full report as JSON. ## Console Integration The simulator's nodes appear in `turnstone-console` exactly like real nodes. Run them together to see the dashboard populate with simulated workstreams: ```bash # Terminal 1: start Redis and console docker compose up redis console # Terminal 2: run simulator docker compose --profile sim up sim ``` Or all at once: ```bash SIM_NODES=50 SIM_DURATION=120 docker compose --profile sim up redis console sim ``` Open http://localhost:8090 to see simulated nodes, workstream states, token counts, and load bars updating in real time. ## Architecture > See also: [Simulator Architecture diagram](diagrams/png/10-simulator-architecture.png) ``` turnstone/sim/ ├── __init__.py # Public API: SimCluster, SimConfig ├── config.py # SimConfig — all simulation parameters ├── engine.py # SimEngine — LLM + tool execution simulation ├── node.py # SimNode + SimWorkstream — protocol-compatible node ├── cluster.py # SimCluster + InboundDispatcher + PooledBroker ├── scenario.py # 5 scenario classes ├── metrics.py # MetricsCollector — latency, throughput, utilization └── cli.py # CLI entry point ``` **Key design:** The `InboundDispatcher` batches ~50 node queues into a single Redis `BLPOP` call, keeping connection count bounded at ~20 regardless of node count. All nodes share a single `ConnectionPool(max_connections=64)`. ## Programmatic Use ```python import asyncio from turnstone.sim import SimCluster, SimConfig async def main(): config = SimConfig( num_nodes=10, scenario="steady", duration=30, messages_per_second=2.0, llm_latency_mean=0.5, ) cluster = SimCluster(config) await cluster.start() await cluster.run_scenario() print(cluster.report()) await cluster.stop() asyncio.run(main()) ```