System Architecture
Comprehensive technical architecture, node taxonomy, and execution model of the Budment engine.
Budment is architected around a decoupled, compilation-based execution model. Test scenarios defined in TypeScript are translated into an intermediate Static Execution Graph, separating high-level workflow definitions from the high-throughput, low-latency requirements of native systems execution.
(Scenario Orchestrator)"] subgraph WORKERS ["Worker Fleet (Concurrent Goroutines)"] direction LR W1["Worker 1
Native FSM"] W2["Worker 2
Native FSM"] WD["..."] WN["Worker N
Native FSM"] end subgraph SHARED ["Shared Infrastructure"] direction LR IO["Shared Transport & I/O Network Engine"] POOL["Goja VM Pool (sync.Pool)
(Borrowed on-demand for JS hooks)"] end DIR --> W1 DIR --> W2 DIR --> WN W1 --> IO W2 --> IO WN --> IO WORKERS -.-> |"On-demand hook execution"| POOL end COMPILATION ==> |"Serialized State Graph"| DIR style COMPILATION fill:transparent,stroke:#64748b,stroke-width:1.5px,color:inherit style TS fill:transparent,stroke:#94a3b8,color:inherit style ES fill:transparent,stroke:#94a3b8,color:inherit style BN fill:transparent,stroke:#94a3b8,color:inherit style VM fill:transparent,stroke:#60a5fa,color:inherit style IR fill:transparent,stroke:#3b82f6,stroke-width:1.5px,color:inherit style RUNTIME fill:transparent,stroke:#60a5fa,stroke-width:1.5px,color:inherit style DIR fill:transparent,stroke:#60a5fa,stroke-width:1.5px,color:inherit style WORKERS fill:transparent,stroke:#93c5fd,stroke-width:1px,color:inherit style W1 fill:transparent,stroke:#94a3b8,color:inherit style W2 fill:transparent,stroke:#94a3b8,color:inherit style WD fill:transparent,stroke:#94a3b8,color:inherit style WN fill:transparent,stroke:#94a3b8,color:inherit style SHARED fill:transparent,stroke:#94a3b8,stroke-width:1px,color:inherit style IO fill:transparent,stroke:#94a3b8,stroke-width:1.5px,color:inherit style POOL fill:transparent,stroke:#94a3b8,stroke-dasharray:4 4,stroke-width:1.5px,color:inherit
1. Two-Phase Execution Lifecycle
Phase 1: Static Graph Compilation (The Plan Phase)
The engine executes the input bundle within an isolated, side-effect-free compiler VM:
- Dependency Packaging: The scenario definition is bundled in-memory using an embedded compilation pipeline.
- DSL Evaluation: The compiler VM evaluates the bundle against mock builders. Calls to structural definitions (
http.get,branch,poll) construct intermediate node descriptors rather than executing network I/O. - Graph Assembly: The descriptors compile into an immutable Directed Graph (IR) encoded via Protobuf structures, preserving pipeline hierarchy, conditions, and metadata.
Phase 2: Native FSM Execution (The Run Phase)
Once compilation concludes, the compiler VM is fully decommissioned and the native runtime engine takes control:
- Scenario Scheduling: The
Directorallocates execution groups according to configured order dependencies (Order) and timing offsets (StartAt). - Worker Allocation: Virtual Users (VUs) execute as lightweight Go goroutines governed by a Finite State Machine (FSM).
- Deterministic State Walking: Each worker traverses the pre-compiled graph nodes sequentially or conditionally without querying an external script interpreter for routing decisions.
2. The Three-Tier Node Taxonomy
Nodes within the Budment architecture are categorized into three distinct functional tiers, governing where and how they are evaluated:
| 1. STRUCTURAL NODES | 2. OPERATIONAL NODES | 3. EXPRESSION NODES |
|---|---|---|
Action (HTTP) |
Log / Abort / Fail |
Context Get |
Branch (if/else) |
Script |
Random Generators |
Match (switch) |
Set / Distribute |
Environment Config |
Loop (for/while) |
Metric |
Binary / File Buffers |
Poll (retry) |
Barrier/ Sleep |
Execution Info |
| Governs topology and routing in the static execution graph. | Represents discrete lifecycle actions executed natively by worker goroutines. | Resolves dynamic values and expressions without adding structural graph nodes. |
Tier 1: Structural Flow Nodes (Graph Topology)
Structural nodes define the branching, iteration, and protocol routing boundaries of a scenario.
- Nodes:
ActionNode(HTTP),BranchNode,MatchNode,LoopNode,PollNode. - Behavior: Compiled during Phase 1 into native Go graph structures. During Phase 2, the worker FSM evaluates conditional routing directly in native Go code. JavaScript is only consulted if a condition explicitly requires dynamic hook evaluation.
Tier 2: Operational Executable Nodes (Lifecycle Actions)
Operational nodes represent discrete instructions executed along a pipeline path.
Nodes:
SleepNode,LogNode,SetNode,DistributeNode,MetricNode,AbortNode,FailNode,BarrierNode...Dual-Context Role:
- In Phase 1: They emit lightweight AST descriptor nodes containing configuration parameters (e.g., target duration, metric labels, variable keys).
- In Phase 2: They trigger immediate native system actions: goroutine timers for sleep, atomic counters for metrics, or cross-worker coordination via
BarrierManager.
Tier 3: Expression & Dynamic Data Nodes (Value Providers)
Expression nodes provide dynamic data resolution without adding structural nodes to the AST graph.
Nodes:
get(),env(),open(),random.*,info.*.Template Substitution vs. Hook Evaluation:
- Declarative Templates: In static declarations, these nodes compile down to native template tokens (e.g.,
{{@env:API_KEY:default}},{{@random:uuid}},{{@open:/path/file:b}}). At runtime, the native Go template engine interpolates these tokens. - Imperative Hooks: Inside JavaScript callbacks, these nodes interact directly with the attached
WorkerScopevia memory bridges, permitting zero-copy reads and writes to worker-local or scenario-shared memory.
- Declarative Templates: In static declarations, these nodes compile down to native template tokens (e.g.,
3. The "Blind SDK" Pattern
The TypeScript SDK contains no business logic or operational runtime code. It serves strictly as a type-safe definition layer.
Calls SDK function"] DISPATCH["globalThis Context Dispatcher
Resolves active Goja execution environment"] subgraph P1 ["PHASE 1: PLANNING"] direction TB P1_NODE["Goja injected with: Mock Node Builders
• Evaluates scenario structure statically
• Returns AST node definitions"] end subgraph P2 ["PHASE 2: RUNTIME"] direction TB P2_NODE["Goja injected with: Bridge Functions
• Executes operational callbacks and assertions"] end TS --> DISPATCH DISPATCH -->|"During Static Compilation"| P1 DISPATCH -->|"During Goroutine Execution"| P2 style TS fill:transparent,stroke:#94a3b8,stroke-width:1.5px,color:inherit style DISPATCH fill:transparent,stroke:#94a3b8,stroke-width:1.5px,color:inherit style P1 fill:transparent,stroke:#94a3b8,stroke-width:1.5px,color:inherit style P2 fill:transparent,stroke:#60a5fa,stroke-width:1.5px,color:inherit style P1_NODE fill:transparent,stroke:#94a3b8,stroke-width:1px,color:inherit style P2_NODE fill:transparent,stroke:#60a5fa,stroke-width:1px,color:inherit
All functions exported by the SDK are direct bindings to globalThis. The Go engine binds concrete implementations into the VM based entirely on the active lifecycle phase:
- During Phase 1,
sleep(1)returns a descriptor object{ build: () => ({ type: "sleep", duration: 1 }) }. - During Phase 2, calling
sleep(1)within an imperative hook records the sleep interval and yields thread control back to the native Go runtime.
4. Inter-Language Yielding: The Panic-Recovery Protocol
Executing blocking operations (such as timers, abort signals, or synchronization barriers) from within synchronous JavaScript callbacks requires transferring control without blocking operating system threads or locking the shared VM pool.
Budment implements a deterministic Panic-Recovery Control Transfer Protocol:
2. Execute native time.Sleep() in Go runtime
- Parameter Staging: When a blocking operation (
sleep,barrier,abort) is invoked inside a JS hook, the Go bridge captures the relevant metadata into the active worker context. - Stack Termination: The bridge triggers a controlled panic with a known sentinel string (e.g.,
BUDMENT_SLEEP,BUDMENT_ABORT). This immediately unwinds the JavaScript execution stack, terminating script execution safely. - Host Interception: The enclosing Go worker intercepts the sentinel panic via
recover(), disengages the VM, and returns the instance to thesync.Pool. - Native Execution: The blocking operation is carried out natively in Go using standard synchronization primitives (e.g.,
time.NewTimer, channels, orsync.Cond).