Skip to content

Commit efdafc1

Browse files
committed
feat: multi runner proposal
Signed-off-by: Jefferson <[email protected]> feat: multi runner proposal
1 parent 41c75ee commit efdafc1

File tree

23 files changed

+804
-266
lines changed

23 files changed

+804
-266
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
results
33
perf/load/*/package-lock.json
4+
.DS_Store

README.md

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,247 @@ The Performance Working Group uses [GitHub issues](https://github.com/expressjs/
7777
The discussions are open to the public and anyone may participate. Also, the group uses the channel `#express-perf-wg`
7878
in the [OpenJS Foundation Slack](https://openjsf.org/collaboration) for realtime discussions.
7979

80+
## How to Run the Load Tests
81+
82+
### Requirements
83+
- Node.js v22 (Recommended)
84+
- Docker (for containerized runners)
85+
86+
### Quick Start
87+
88+
1. **Install dependencies**
89+
```bash
90+
node --run setup
91+
```
92+
93+
2. **Run basic test with vanilla Node.js**
94+
```bash
95+
node --run test:load
96+
```
97+
98+
### CLI Usage
99+
100+
#### CLI Options
101+
102+
The `load` command supports the following options:
103+
104+
| Option | Short | Default | Description |
105+
|--------|-------|---------|-------------|
106+
| `--test` | `-t` | `@expressjs/perf-load-example` | Test module to run |
107+
| `--runner` | `-u` | `@expressjs/perf-runner-vanilla` | Runner module to use |
108+
| `--duration` | `-d` | `60` | Test duration in seconds |
109+
| `--node` | `-n` | `lts_latest` | Node.js version to use |
110+
| `--overrides` | `-o` | - | JSON string with package overrides |
111+
| `--cwd` | - | Current directory | Working directory |
112+
| `--repo` | - | `https://github.com/expressjs/perf-wg.git` | Git repository URL |
113+
| `--repo-ref` | - | `master` | Git branch/tag to use |
114+
115+
#### Basic Commands
116+
117+
```bash
118+
# Run with default vanilla Node.js runner
119+
node --run load -- --test="@expressjs/perf-load-example"
120+
121+
# Run with specific runner
122+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid"
123+
124+
# Run with overrides
125+
node --run load -- --test="@expressjs/perf-load-example" --overrides='{"express":"5.0.0"}'
126+
127+
# Run with custom duration (default: 60 seconds)
128+
node --run load -- --test="@expressjs/perf-load-example" --duration=30
129+
130+
# Run with specific runner and overrides
131+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid" --overrides='{"express":"5.0.0"}'
132+
133+
# Run with specific runner, overrides, and custom duration
134+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid" --overrides='{"express":"5.0.0"}' --duration=120
135+
136+
# Run with specific runner and overrides and pass api key env
137+
NSOLID_SAAS="XXX" node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid" --overrides='{"express":"5.0.0"}'
138+
139+
# Compare results
140+
node --run compare -- result-A.json result-B.json
141+
```
142+
143+
#### Runner-Specific Examples
144+
145+
```bash
146+
# Vanilla Node.js (baseline performance)
147+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-vanilla"
148+
149+
# N|Solid with built-in monitoring
150+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid"
151+
152+
# Node.js with Datadog APM (when available)
153+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-datadog"
154+
155+
# Node.js with New Relic APM (when available)
156+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-newrelic"
157+
```
158+
### Programmatic Usage
159+
160+
#### Basic Import and Usage
161+
162+
```javascript
163+
// Import specific runner
164+
import vanillaRunner from '@expressjs/perf-runner-vanilla';
165+
import nsolidRunner from '@expressjs/perf-runner-nsolid';
166+
167+
// Run single test
168+
const results = await vanillaRunner({
169+
test: '@expressjs/perf-load-example',
170+
overrides: { express: '5.0.0' }
171+
});
172+
173+
console.log(`Requests/sec: ${results.clientResults.requests.mean}`);
174+
```
175+
176+
#### Performance Comparison Script
177+
178+
```javascript
179+
import vanillaRunner from '@expressjs/perf-runner-vanilla';
180+
import nsolidRunner from '@expressjs/perf-runner-nsolid';
181+
182+
async function compareRuntimes() {
183+
const testConfig = {
184+
test: '@expressjs/perf-load-example',
185+
overrides: { express: 'latest' }
186+
};
187+
188+
console.log('Running performance comparison...');
189+
190+
// Run tests in parallel
191+
const [vanillaResults, nsolidResults] = await Promise.all([
192+
vanillaRunner(testConfig),
193+
nsolidRunner(testConfig)
194+
]);
195+
196+
// Compare results
197+
const vanillaRPS = vanillaResults.clientResults.requests.mean;
198+
const nsolidRPS = nsolidResults.clientResults.requests.mean;
199+
const overhead = ((vanillaRPS - nsolidRPS) / vanillaRPS * 100).toFixed(2);
200+
201+
console.log('Performance Comparison:');
202+
console.log(`Vanilla Node.js: ${vanillaRPS.toFixed(0)} req/sec`);
203+
console.log(`N|Solid: ${nsolidRPS.toFixed(0)} req/sec`);
204+
console.log(`Overhead: ${overhead}%`);
205+
}
206+
207+
compareRuntimes().catch(console.error);
208+
```
209+
210+
#### Advanced Configuration
211+
212+
```javascript
213+
import { createRunner } from '@expressjs/perf-runner-shared';
214+
215+
// Create custom runner configuration
216+
const customRunner = createRunner({
217+
type: 'custom',
218+
runtime: 'node.js',
219+
apm: 'custom-monitoring',
220+
capabilities: ['profiling', 'tracing'],
221+
env: {
222+
RUNTIME_TYPE: 'custom',
223+
CUSTOM_CONFIG: 'enabled'
224+
}
225+
});
226+
227+
// Use with abort controller for cancellation
228+
const controller = new AbortController();
229+
const results = await customRunner({
230+
test: '@expressjs/perf-load-example',
231+
signal: controller.signal
232+
});
233+
```
234+
235+
### Available Runners
236+
237+
| Runner | Description | Use Case | Status |
238+
|--------|-------------|----------|--------|
239+
| `@expressjs/perf-runner-vanilla` | Standard Node.js runtime | Baseline performance measurements | Available |
240+
| `@expressjs/perf-runner-nsolid` | N|Solid runtime with built-in monitoring | Enterprise monitoring capabilities | Available |
241+
| `@expressjs/perf-runner-datadog` | Node.js + Datadog APM agent | Datadog APM overhead analysis | Coming soon |
242+
| `@expressjs/perf-runner-newrelic` | Node.js + New Relic APM agent | New Relic APM overhead analysis | Coming soon |
243+
244+
### Performance Analysis Examples
245+
246+
#### CLI-based Comparison
247+
248+
```bash
249+
# Step 1: Run baseline test
250+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-vanilla"
251+
# Output: results/vanilla-result-123456789.json
252+
253+
# Step 2: Run N|Solid test
254+
node --run load -- --test="@expressjs/perf-load-example" --runner="@expressjs/perf-runner-nsolid"
255+
# Output: results/nsolid-result-123456790.json
256+
257+
# Step 3: Compare results
258+
node --run compare -- results/vanilla-result-123456789.json results/nsolid-result-123456790.json
259+
```
260+
261+
#### Programmatic Analysis
262+
263+
```javascript
264+
import fs from 'node:fs/promises';
265+
266+
async function runPerformanceAnalysis() {
267+
// Test configuration
268+
const tests = [
269+
{ name: 'Express Hello World', test: '@expressjs/perf-load-example' },
270+
{ name: 'Express with Query', test: '@expressjs/perf-load-extended-query' }
271+
];
272+
273+
const runners = [
274+
{ name: 'Vanilla', runner: vanillaRunner },
275+
{ name: 'N|Solid', runner: nsolidRunner }
276+
];
277+
278+
const results = {};
279+
280+
// Run all combinations
281+
for (const test of tests) {
282+
results[test.name] = {};
283+
284+
for (const { name, runner } of runners) {
285+
console.log(`Running ${test.name} with ${name}...`);
286+
287+
const result = await runner({ test: test.test });
288+
results[test.name][name] = {
289+
requestsPerSec: result.clientResults.requests.mean,
290+
latencyP95: result.clientResults.latency.p95,
291+
memoryUsage: result.serverMetadata.memory
292+
};
293+
}
294+
}
295+
296+
// Save comprehensive report
297+
await fs.writeFile('performance-report.json', JSON.stringify(results, null, 2));
298+
console.log('Performance analysis complete. Results saved to performance-report.json');
299+
}
300+
301+
### Runner Development
302+
303+
To create a new runner, extend the shared runner infrastructure:
304+
305+
```javascript
306+
// packages/runner-custom/index.mjs
307+
import { createRunner } from '@expressjs/perf-runner-shared';
308+
309+
export default createRunner({
310+
type: 'custom',
311+
runtime: 'node.js',
312+
apm: 'custom-apm',
313+
capabilities: ['custom-profiling'],
314+
env: {
315+
RUNTIME_TYPE: 'custom',
316+
CUSTOM_SETTING: 'enabled'
317+
}
318+
});
319+
```
320+
80321
## Code of Conduct
81322

82323
The [Express Project's CoC](https://github.com/expressjs/.github/blob/master/CODE_OF_CONDUCT.md) applies to this repo.
83-

package-lock.json

Lines changed: 52 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/bin/expf.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ const { values, positionals } = parseArgs({
4747

4848
write: {
4949
type: 'boolean'
50+
},
51+
52+
duration: {
53+
type: 'string',
54+
short: 'd'
5055
}
5156
}
5257
});

0 commit comments

Comments
 (0)