Load Testing without the
Runtime Overhead.
Script in JavaScript. Execute in Rust.
The world's most efficient load testing engine. Sub-millisecond latency at 50,000 concurrent users.
$ fusi run scenario.js -w 10000
[INFO] Spawning 10,000 workers...
[DONE] 215,618 RPS | P95: 231µs | 100% success
Raw Performance
Benchmarked at 50,000 concurrent virtual users
Benchmarked on localhost against a simple Rust/Axum server. Real-world results will vary based on network latency and server complexity.
Why Rust?
Traditional load testing tools are built on garbage-collected runtimes. At high concurrency, GC pauses introduce latency spikes that pollute your metrics. Fusillade eliminates this with zero-allocation hot paths and predictable memory usage.
Thread-per-Worker
Each virtual user runs on a dedicated OS thread with its own QuickJS runtime. No async runtime contention. No coroutine scheduling overhead. Pure synchronous blocking I/O with predictable latency characteristics.
Memory Efficiency
50,000 concurrent users in ~400MB of RAM. Rust's ownership model eliminates memory leaks and fragmentation. No GC pauses means consistent sub-millisecond latencies even under extreme load.
Accurate Metrics
Latency measurements exclude JavaScript execution overhead—only network time and body reads are counted. HDR histograms capture precise percentiles without bucket quantization errors.
Write Tests in JavaScript
Familiar syntax. Full ES Module support. Powerful built-ins.
Load Test with Stages
export const options = {
stages: [
{ duration: '30s', target: 100 }, // Ramp to 100 VUs
{ duration: '1m', target: 100 }, // Hold at peak
{ duration: '10s', target: 0 }, // Ramp down
],
thresholds: {
'http_req_duration': ['p95 < 500'],
'http_req_failed': ['rate < 0.01'],
}
};
export default function () {
const res = http.post('https://api.example.com/checkout',
JSON.stringify({ item_id: '12345' }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(res, {
'status is 200': (r) => r.status === 200,
'response time OK': (r) => r.timings.duration < 500,
});
sleep(1);
}Lifecycle Hooks
// Load test data once, share across all workers
const users = new SharedArray('users', () =>
JSON.parse(open('./data/users.json'))
);
export function setup() {
// Runs once before test starts
const loginRes = http.post('/api/login', {
username: 'admin', password: 'secret'
});
return { token: JSON.parse(loginRes.body).token };
}
export default function(data) {
// Each worker receives setup data
const user = users[__WORKER_ID % users.length];
http.get('/api/profile', {
headers: { 'Authorization': `Bearer ${data.token}` }
});
}
export function teardown(data) {
// Cleanup after all workers finish
http.post('/api/logout');
}Multi-Protocol Support
Test any backend with native protocol implementations
HTTP/1.1 & HTTP/2
const res = http.post(url, body, {
headers: { 'Content-Type': 'application/json' },
timeout: '10s',
name: 'CreateOrder'
});
// res.proto → "h1" or "h2"WebSockets
const socket = ws.connect('wss://api.example.com');
socket.send(JSON.stringify({ type: 'subscribe' }));
const msg = socket.recv();
socket.close();gRPC
const client = new GrpcClient();
client.load(['./hello.proto'], ['./protos']);
client.connect('http://localhost:50051');
const res = client.invoke('helloworld.Greeter/SayHello', { name: 'World' });MQTT
const mqtt = new JsMqttClient();
mqtt.connect('localhost', 1883, 'client-1');
mqtt.publish('sensors/temp', '22.5');
mqtt.close();AMQP (RabbitMQ)
const amqp = new JsAmqpClient();
amqp.connect('amqp://localhost');
amqp.publish('', 'my-queue', JSON.stringify({ event: 'order.created' }));Server-Sent Events
const client = sse.connect('/events');
const event = client.recv();
// { event: 'update', data: '...', id: '1' }
client.close();Browser Automation (Chromium)
const browser = chromium.launch();
const page = browser.newPage();
page.goto('https://example.com/login');
page.type('#username', 'testuser');
page.type('#password', 'secret');
page.click('#submit');
const perf = page.metrics();
print(`DOM interactive: ${perf.domInteractive - perf.navigationStart}ms`);
const screenshot = page.screenshot(); // Auto-captured on check() failures
browser.close();Flexible Execution
Multiple execution models for different testing scenarios
Load Profiles
- Ramping VUs — Gradually scale workers up/down with stages
- Constant Arrival Rate — Fixed RPS regardless of response time
- Per-VU Iterations — Each worker runs N iterations then exits
- Multiple Scenarios — Run different user behaviors concurrently
Multi-Scenario Example
export const options = {
scenarios: {
browse: {
workers: 50,
duration: '2m',
exec: 'browseProducts',
},
checkout: {
workers: 10,
duration: '90s',
exec: 'checkoutFlow',
startTime: '30s', // Delayed start
},
},
};
export function browseProducts() { ... }
export function checkoutFlow() { ... }Distributed Load Generation
Scale horizontally across multiple machines or Kubernetes pods
Architecture
+-------------------------------------+
| Controller (:9000) |
| Dashboard / Orchestration / DB |
+-----------------+-------------------+
| gRPC
+------------+------------+
| | |
v v v
+--------+ +--------+ +--------+
| Worker | | Worker | | Worker |
| :8080 | | :8080 | | :8080 |
+--------+ +--------+ +--------+Quick Start
# Start workers
fusillade worker --connect controller:9001
# Or run the controller
fusillade controller --listen 0.0.0.0:9000
# Dispatch via API
curl -X POST http://controller:9000/api/dispatch \
-d '{"script_content": "...", "config": {...}}'Kubernetes Native
- • HorizontalPodAutoscaler scales workers 3 → 50 pods
- • Automatic asset distribution (scripts + data files)
- • Real-time dashboard at
/dashboard - • SQLite history for performance trend analysis
Chaos Engineering Built-In
Test system resilience with native fault injection
Jitter Injection
Add artificial latency to simulate slow networks or upstream services
fusi run test.js --jitter 500msRequest Dropping
Randomly fail requests to simulate packet loss or connection resets
fusi run test.js --drop 0.05Config-Based
Define chaos parameters in your script options
export const options = {
jitter: '250ms',
drop: 0.03,
};Developer Experience
Tools that fit into your existing workflow
HAR Recording
Record browser sessions and convert to test scripts
fusi record -o flow.js
fusi convert --input recording.harTypeScript Support
Generate type definitions for IDE autocomplete
fusi types -o index.d.tsCI/CD Integration
Export to JUnit XML, CSV, JSON, or OTLP
fusi run test.js --headless \
--out junit=results.xml \
--out csv=metrics.csvCost Estimation
Estimate bandwidth costs before running large tests
fusi run heavy.js --estimate-cost
# Est. AWS Cost: ~$0.22 (2.45 GB)Smart Error Replay
Debug failed requests after a test run
# Export failures to cURL commands
fusi export fusillade-errors.json --format curl
# Replay failed requests
fusi replay fusillade-errors.json --parallelInteractive Control
Control running tests in real-time
fusi run test.js --interactive
> ramp 100 # Scale to 100 workers
> pause # Pause execution
> resume # Resume
> status # Show current statsBatteries Included
Built-in utilities for common load testing needs
crypto
crypto.md5(data)
crypto.sha1(data)
crypto.sha256(data)
crypto.hmac('sha256', key, data)encoding
encoding.b64encode('user:pass')
encoding.b64decode(token)utils
utils.uuid()
utils.randomInt(1, 100)
utils.randomString(16)
utils.randomItem(users)Custom Metrics
Track business-level metrics with thresholds
// Define thresholds in options
export const options = {
thresholds: {
'checkout_duration': ['p95 < 500'],
'payment_success': ['rate > 0.99'],
}
};
// Record custom metrics
metrics.histogramAdd('checkout_duration', Date.now() - start);
metrics.counterAdd('items_sold', 3);
metrics.rateAdd('payment_success', true);
metrics.gaugeSet('queue_depth', 42);
// Query stats programmatically
const s = stats.get('CreateOrder');
if (s.p95 > 500) print('Warning: P95 latency degrading');Get Started in Seconds
$ fusi types -o index.d.ts # IDE autocomplete
$ fusi run scenario.js -w 1000 -d 30s
~35MB binary. No runtime needed. Works on Linux, macOS, and Windows.