ferrings 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +428 -0
- package/benchmark/compare.js +178 -0
- package/benchmark/first-slice.js +232 -0
- package/benchmark/high-concurrency.js +119 -0
- package/benchmark/syscalls.js +514 -0
- package/benchmark/tcp-echo.js +442 -0
- package/bin/ferrings.js +503 -0
- package/examples/http-fixed.js +24 -0
- package/examples/tcp-echo.js +29 -0
- package/ferrings.linux-x64-gnu.node +0 -0
- package/index.d.ts +70 -0
- package/index.js +11 -0
- package/native.d.ts +213 -0
- package/native.js +594 -0
- package/package.json +95 -0
- package/tcp-transport.js +340 -0
- package/zcrx-smoke.js +476 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const http = require('node:http');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { performance } = require('node:perf_hooks');
|
|
7
|
+
const { UringHttpServer } = require('../');
|
|
8
|
+
|
|
9
|
+
const BODY = 'ok\n';
|
|
10
|
+
const DURATION_MS = Number(process.env.DURATION_MS || 5000);
|
|
11
|
+
const CONCURRENCY = Number(process.env.CONCURRENCY || 128);
|
|
12
|
+
const QUEUE_DEPTH = Number(process.env.QUEUE_DEPTH || 256);
|
|
13
|
+
const REPORT_PATH = process.env.REPORT_PATH;
|
|
14
|
+
|
|
15
|
+
function requestOnce(port) {
|
|
16
|
+
const startedAt = performance.now();
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const req = http.get({ host: '127.0.0.1', port, path: '/', agent: false }, (res) => {
|
|
19
|
+
res.resume();
|
|
20
|
+
res.on('end', () => resolve(performance.now() - startedAt));
|
|
21
|
+
});
|
|
22
|
+
req.on('error', reject);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function runLoad(port) {
|
|
27
|
+
let completed = 0;
|
|
28
|
+
let stopped = false;
|
|
29
|
+
const latencies = [];
|
|
30
|
+
const endAt = performance.now() + DURATION_MS;
|
|
31
|
+
|
|
32
|
+
async function worker() {
|
|
33
|
+
while (!stopped) {
|
|
34
|
+
latencies.push(await requestOnce(port));
|
|
35
|
+
completed += 1;
|
|
36
|
+
if (performance.now() >= endAt) stopped = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
|
|
41
|
+
latencies.sort((a, b) => a - b);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
requests: completed,
|
|
45
|
+
rps: Math.round((completed * 1000) / DURATION_MS),
|
|
46
|
+
p50Ms: percentile(latencies, 0.5),
|
|
47
|
+
p95Ms: percentile(latencies, 0.95),
|
|
48
|
+
p99Ms: percentile(latencies, 0.99)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function percentile(values, quantile) {
|
|
53
|
+
if (values.length === 0) return 0;
|
|
54
|
+
const index = Math.min(values.length - 1, Math.floor(values.length * quantile));
|
|
55
|
+
return Number(values[index].toFixed(3));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function withNodeServer() {
|
|
59
|
+
const server = http.createServer((_, res) => {
|
|
60
|
+
res.setHeader('connection', 'close');
|
|
61
|
+
res.end(BODY);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
65
|
+
const port = server.address().port;
|
|
66
|
+
try {
|
|
67
|
+
return await runLoad(port);
|
|
68
|
+
} finally {
|
|
69
|
+
await new Promise((resolve) => server.close(resolve));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function withUringServer() {
|
|
74
|
+
const server = new UringHttpServer({
|
|
75
|
+
host: '127.0.0.1',
|
|
76
|
+
port: 0,
|
|
77
|
+
queueDepth: QUEUE_DEPTH,
|
|
78
|
+
responseBody: BODY,
|
|
79
|
+
bufferCount: 4096,
|
|
80
|
+
bufferSize: 2048
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const info = server.start();
|
|
84
|
+
try {
|
|
85
|
+
return {
|
|
86
|
+
...(await runLoad(info.port)),
|
|
87
|
+
serverInfo: summarizeServerInfo(server.info())
|
|
88
|
+
};
|
|
89
|
+
} finally {
|
|
90
|
+
server.stop();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function summarizeServerInfo(info) {
|
|
95
|
+
if (!info) return null;
|
|
96
|
+
return {
|
|
97
|
+
backend: info.backend,
|
|
98
|
+
backlog: info.backlog,
|
|
99
|
+
queueDepth: info.queueDepth,
|
|
100
|
+
bufferCount: info.bufferCount,
|
|
101
|
+
bufferSize: info.bufferSize,
|
|
102
|
+
tcpNoDelay: info.tcpNoDelay,
|
|
103
|
+
reusePort: info.reusePort,
|
|
104
|
+
tcpDeferAcceptSeconds: info.tcpDeferAcceptSeconds,
|
|
105
|
+
socketRecvBufferSize: info.socketRecvBufferSize,
|
|
106
|
+
socketSendBufferSize: info.socketSendBufferSize,
|
|
107
|
+
multishotAccept: info.multishotAccept,
|
|
108
|
+
multishotRecv: info.multishotRecv,
|
|
109
|
+
providedBufferRing: info.providedBufferRing,
|
|
110
|
+
recvBundle: info.recvBundle,
|
|
111
|
+
recvCopyEvents: info.recvCopyEvents,
|
|
112
|
+
recvCopyBytes: info.recvCopyBytes,
|
|
113
|
+
eventBatchSize: info.eventBatchSize,
|
|
114
|
+
sendBufferCount: info.sendBufferCount,
|
|
115
|
+
sendBufferSize: info.sendBufferSize,
|
|
116
|
+
registeredSendBuffer: info.registeredSendBuffer,
|
|
117
|
+
fixedSendBufferMisses: info.fixedSendBufferMisses,
|
|
118
|
+
fixedSendBufferMissBytes: info.fixedSendBufferMissBytes,
|
|
119
|
+
zeroCopySend: info.zeroCopySend,
|
|
120
|
+
zeroCopyReceive: info.zeroCopyReceive,
|
|
121
|
+
zcrxReady: info.zcrxReady
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function baseReport() {
|
|
126
|
+
return {
|
|
127
|
+
mode: 'http-fixed-response',
|
|
128
|
+
status: 'running',
|
|
129
|
+
startedAt: new Date().toISOString(),
|
|
130
|
+
finishedAt: null,
|
|
131
|
+
config: {
|
|
132
|
+
durationMs: DURATION_MS,
|
|
133
|
+
concurrency: CONCURRENCY,
|
|
134
|
+
queueDepth: QUEUE_DEPTH
|
|
135
|
+
},
|
|
136
|
+
results: [],
|
|
137
|
+
error: null
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function writeReport(report) {
|
|
142
|
+
if (!REPORT_PATH) return;
|
|
143
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
144
|
+
fs.writeFileSync(REPORT_PATH, `${JSON.stringify(report, null, 2)}\n`);
|
|
145
|
+
console.log(`HTTP benchmark report written: ${REPORT_PATH}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function errorForReport(error) {
|
|
149
|
+
return {
|
|
150
|
+
name: error && error.name ? error.name : 'Error',
|
|
151
|
+
message: error && error.message ? error.message : String(error),
|
|
152
|
+
stack: error && error.stack ? error.stack : undefined
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
(async () => {
|
|
157
|
+
const report = baseReport();
|
|
158
|
+
try {
|
|
159
|
+
console.log(report.config);
|
|
160
|
+
const nodeResult = await withNodeServer();
|
|
161
|
+
report.results.push({ caseName: 'node:http', result: nodeResult });
|
|
162
|
+
console.log('node:http', nodeResult);
|
|
163
|
+
const ferringsResult = await withUringServer();
|
|
164
|
+
report.results.push({ caseName: 'ferrings', result: ferringsResult });
|
|
165
|
+
console.log('ferrings', ferringsResult);
|
|
166
|
+
report.status = 'passed';
|
|
167
|
+
} catch (error) {
|
|
168
|
+
report.status = 'failed';
|
|
169
|
+
report.error = errorForReport(error);
|
|
170
|
+
throw error;
|
|
171
|
+
} finally {
|
|
172
|
+
report.finishedAt = new Date().toISOString();
|
|
173
|
+
writeReport(report);
|
|
174
|
+
}
|
|
175
|
+
})().catch((error) => {
|
|
176
|
+
console.error(error);
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { capabilities } = require('../');
|
|
8
|
+
|
|
9
|
+
const DURATION_MS = String(process.env.DURATION_MS || 1000);
|
|
10
|
+
const CONCURRENCY = String(process.env.CONCURRENCY || 128);
|
|
11
|
+
const QUEUE_DEPTH = String(process.env.QUEUE_DEPTH || 256);
|
|
12
|
+
const SYSCALL_REQUESTS = String(process.env.SYSCALL_REQUESTS || 200);
|
|
13
|
+
const SYSCALL_CONCURRENCY = String(process.env.SYSCALL_CONCURRENCY || 32);
|
|
14
|
+
const SYSCALL_CASES =
|
|
15
|
+
process.env.SYSCALL_CASES ||
|
|
16
|
+
'node-http,ferrings-http,node-tcp,ferrings-tcp-facade,ferrings-tcp-facade-batch,ferrings-native-tcp';
|
|
17
|
+
const REPORT_PATH = process.env.REPORT_PATH;
|
|
18
|
+
|
|
19
|
+
const report = {
|
|
20
|
+
mode: 'first-slice',
|
|
21
|
+
status: 'running',
|
|
22
|
+
startedAt: new Date().toISOString(),
|
|
23
|
+
finishedAt: null,
|
|
24
|
+
config: {
|
|
25
|
+
durationMs: Number(DURATION_MS),
|
|
26
|
+
concurrency: Number(CONCURRENCY),
|
|
27
|
+
queueDepth: Number(QUEUE_DEPTH),
|
|
28
|
+
syscallRequests: Number(SYSCALL_REQUESTS),
|
|
29
|
+
syscallConcurrency: Number(SYSCALL_CONCURRENCY),
|
|
30
|
+
syscallCases: SYSCALL_CASES.split(',').map((name) => name.trim()).filter(Boolean)
|
|
31
|
+
},
|
|
32
|
+
capabilities: capabilities(),
|
|
33
|
+
results: [],
|
|
34
|
+
summary: {},
|
|
35
|
+
error: null
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
console.log({ mode: report.mode, ...report.config });
|
|
40
|
+
report.results.push(
|
|
41
|
+
runBenchmark('HTTP fixed response latency', 'compare.js', {
|
|
42
|
+
DURATION_MS,
|
|
43
|
+
CONCURRENCY,
|
|
44
|
+
QUEUE_DEPTH
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
report.results.push(
|
|
48
|
+
runBenchmark('TCP echo latency matrix', 'tcp-echo.js', {
|
|
49
|
+
DURATION_MS,
|
|
50
|
+
CONCURRENCY,
|
|
51
|
+
QUEUE_DEPTH
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
report.results.push(runSyscallBenchmark());
|
|
55
|
+
report.summary = buildSummary(report);
|
|
56
|
+
report.status = 'passed';
|
|
57
|
+
} catch (error) {
|
|
58
|
+
report.status = 'failed';
|
|
59
|
+
report.error = errorForReport(error);
|
|
60
|
+
if (error && error.childResult) {
|
|
61
|
+
report.results.push(error.childResult);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
} finally {
|
|
65
|
+
report.finishedAt = new Date().toISOString();
|
|
66
|
+
writeReport(report);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function runSyscallBenchmark() {
|
|
70
|
+
const strace = spawnSync('strace', ['-V'], { encoding: 'utf8' });
|
|
71
|
+
if (strace.error || strace.status !== 0) {
|
|
72
|
+
return {
|
|
73
|
+
label: 'Syscalls per connection',
|
|
74
|
+
script: 'syscalls.js',
|
|
75
|
+
status: 'skipped',
|
|
76
|
+
skippedReason: 'strace is not available',
|
|
77
|
+
report: null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return runBenchmark('Syscalls per connection', 'syscalls.js', {
|
|
81
|
+
REQUESTS: SYSCALL_REQUESTS,
|
|
82
|
+
CONCURRENCY: SYSCALL_CONCURRENCY,
|
|
83
|
+
QUEUE_DEPTH,
|
|
84
|
+
CASES: SYSCALL_CASES
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runBenchmark(label, script, extraEnv) {
|
|
89
|
+
console.log(`\n== ${label} ==`);
|
|
90
|
+
const childReportPath = path.join(
|
|
91
|
+
os.tmpdir(),
|
|
92
|
+
`ferrings-first-slice-${process.pid}-${path.basename(script, '.js')}-${Date.now()}.json`
|
|
93
|
+
);
|
|
94
|
+
const childResult = {
|
|
95
|
+
label,
|
|
96
|
+
script,
|
|
97
|
+
status: 'running',
|
|
98
|
+
report: null
|
|
99
|
+
};
|
|
100
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, script)], {
|
|
101
|
+
cwd: path.join(__dirname, '..'),
|
|
102
|
+
env: {
|
|
103
|
+
...process.env,
|
|
104
|
+
...extraEnv,
|
|
105
|
+
REPORT_PATH: childReportPath
|
|
106
|
+
},
|
|
107
|
+
stdio: 'inherit'
|
|
108
|
+
});
|
|
109
|
+
childResult.report = readChildReport(childReportPath);
|
|
110
|
+
childResult.status = result.status === 0 ? 'passed' : 'failed';
|
|
111
|
+
if (result.error) {
|
|
112
|
+
result.error.childResult = childResult;
|
|
113
|
+
throw result.error;
|
|
114
|
+
}
|
|
115
|
+
if (result.status !== 0) {
|
|
116
|
+
const error = new Error(`${script} exited with status ${result.status ?? 1}`);
|
|
117
|
+
error.childResult = childResult;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return childResult;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readChildReport(childReportPath) {
|
|
124
|
+
try {
|
|
125
|
+
if (!fs.existsSync(childReportPath)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return JSON.parse(fs.readFileSync(childReportPath, 'utf8'));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
status: 'unreadable',
|
|
132
|
+
error: errorForReport(error)
|
|
133
|
+
};
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(childReportPath, { force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildSummary(output) {
|
|
140
|
+
const httpReport = childReport(output, 'compare.js');
|
|
141
|
+
const tcpReport = childReport(output, 'tcp-echo.js');
|
|
142
|
+
const syscallsReport = childReport(output, 'syscalls.js');
|
|
143
|
+
return {
|
|
144
|
+
httpLatency: compareResults(
|
|
145
|
+
resultByCase(httpReport, 'node:http'),
|
|
146
|
+
resultByCase(httpReport, 'ferrings')
|
|
147
|
+
),
|
|
148
|
+
tcpNativeLatency: compareResults(
|
|
149
|
+
resultByCase(tcpReport, 'node:net echo'),
|
|
150
|
+
resultByCase(tcpReport, 'ferrings native tcp echo')
|
|
151
|
+
),
|
|
152
|
+
tcpFacadeLatency: compareResults(
|
|
153
|
+
resultByCase(tcpReport, 'node:net echo'),
|
|
154
|
+
resultByCase(tcpReport, 'ferrings tcp facade echo')
|
|
155
|
+
),
|
|
156
|
+
tcpFacadeBatchLatency: compareResults(
|
|
157
|
+
resultByCase(tcpReport, 'node:net echo'),
|
|
158
|
+
resultByCase(tcpReport, 'ferrings tcp facade batch echo')
|
|
159
|
+
),
|
|
160
|
+
httpSyscalls: compareResults(
|
|
161
|
+
resultByCase(syscallsReport, 'node-http'),
|
|
162
|
+
resultByCase(syscallsReport, 'ferrings-http'),
|
|
163
|
+
'syscallsPerConnection'
|
|
164
|
+
),
|
|
165
|
+
tcpNativeSyscalls: compareResults(
|
|
166
|
+
resultByCase(syscallsReport, 'node-tcp'),
|
|
167
|
+
resultByCase(syscallsReport, 'ferrings-native-tcp'),
|
|
168
|
+
'syscallsPerConnection'
|
|
169
|
+
),
|
|
170
|
+
tcpFacadeSyscalls: compareResults(
|
|
171
|
+
resultByCase(syscallsReport, 'node-tcp'),
|
|
172
|
+
resultByCase(syscallsReport, 'ferrings-tcp-facade'),
|
|
173
|
+
'syscallsPerConnection'
|
|
174
|
+
),
|
|
175
|
+
tcpFacadeBatchSyscalls: compareResults(
|
|
176
|
+
resultByCase(syscallsReport, 'node-tcp'),
|
|
177
|
+
resultByCase(syscallsReport, 'ferrings-tcp-facade-batch'),
|
|
178
|
+
'syscallsPerConnection'
|
|
179
|
+
)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function childReport(output, script) {
|
|
184
|
+
const entry = output.results.find((result) => result.script === script);
|
|
185
|
+
return entry && entry.report && entry.report.status === 'passed' ? entry.report : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resultByCase(child, caseName) {
|
|
189
|
+
if (!child || !Array.isArray(child.results)) return null;
|
|
190
|
+
const entry = child.results.find((result) => result.caseName === caseName);
|
|
191
|
+
return entry ? entry.result : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function compareResults(baseline, candidate, metric = 'p99Ms') {
|
|
195
|
+
if (!baseline || !candidate) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const baselineMetric = Number(baseline[metric]);
|
|
199
|
+
const candidateMetric = Number(candidate[metric]);
|
|
200
|
+
return {
|
|
201
|
+
baseline,
|
|
202
|
+
candidate,
|
|
203
|
+
metric,
|
|
204
|
+
baselineValue: baselineMetric,
|
|
205
|
+
candidateValue: candidateMetric,
|
|
206
|
+
delta: finiteNumber(candidateMetric - baselineMetric),
|
|
207
|
+
ratio: finiteNumber(candidateMetric / baselineMetric),
|
|
208
|
+
rpsRatio: finiteNumber(Number(candidate.rps) / Number(baseline.rps))
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function finiteNumber(value) {
|
|
213
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function writeReport(output) {
|
|
217
|
+
if (REPORT_PATH) {
|
|
218
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
219
|
+
fs.writeFileSync(REPORT_PATH, `${JSON.stringify(output, null, 2)}\n`);
|
|
220
|
+
console.log(`first-slice benchmark report written: ${REPORT_PATH}`);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(JSON.stringify(output.summary, null, 2));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function errorForReport(error) {
|
|
227
|
+
return {
|
|
228
|
+
name: error && error.name ? error.name : 'Error',
|
|
229
|
+
message: error && error.message ? error.message : String(error),
|
|
230
|
+
stack: error && error.stack ? error.stack : undefined
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const { spawnSync } = require('node:child_process');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_DURATION_MS = '10000';
|
|
9
|
+
const DEFAULT_CONCURRENCY = '512';
|
|
10
|
+
const DEFAULT_QUEUE_DEPTH = '1024';
|
|
11
|
+
const REPORT_PATH = process.env.REPORT_PATH;
|
|
12
|
+
|
|
13
|
+
const env = {
|
|
14
|
+
...process.env,
|
|
15
|
+
DURATION_MS: process.env.DURATION_MS || DEFAULT_DURATION_MS,
|
|
16
|
+
CONCURRENCY: process.env.CONCURRENCY || DEFAULT_CONCURRENCY,
|
|
17
|
+
QUEUE_DEPTH: process.env.QUEUE_DEPTH || DEFAULT_QUEUE_DEPTH
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const report = {
|
|
21
|
+
mode: 'high-concurrency',
|
|
22
|
+
status: 'running',
|
|
23
|
+
startedAt: new Date().toISOString(),
|
|
24
|
+
finishedAt: null,
|
|
25
|
+
config: {
|
|
26
|
+
durationMs: Number(env.DURATION_MS),
|
|
27
|
+
concurrency: Number(env.CONCURRENCY),
|
|
28
|
+
queueDepth: Number(env.QUEUE_DEPTH)
|
|
29
|
+
},
|
|
30
|
+
results: [],
|
|
31
|
+
error: null
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
console.log({ mode: report.mode, ...report.config });
|
|
36
|
+
report.results.push(run('HTTP fixed response', 'compare.js'));
|
|
37
|
+
report.results.push(run('TCP echo matrix', 'tcp-echo.js'));
|
|
38
|
+
report.status = 'passed';
|
|
39
|
+
} catch (error) {
|
|
40
|
+
report.status = 'failed';
|
|
41
|
+
report.error = errorForReport(error);
|
|
42
|
+
if (error && error.childResult) {
|
|
43
|
+
report.results.push(error.childResult);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
} finally {
|
|
47
|
+
report.finishedAt = new Date().toISOString();
|
|
48
|
+
writeReport(report);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function run(label, script) {
|
|
52
|
+
console.log(`\n== ${label} ==`);
|
|
53
|
+
const childResult = {
|
|
54
|
+
label,
|
|
55
|
+
script,
|
|
56
|
+
report: null
|
|
57
|
+
};
|
|
58
|
+
const childEnv = { ...env };
|
|
59
|
+
const childReportPath = REPORT_PATH
|
|
60
|
+
? path.join(
|
|
61
|
+
os.tmpdir(),
|
|
62
|
+
`ferrings-high-${process.pid}-${path.basename(script, '.js')}-${Date.now()}.json`
|
|
63
|
+
)
|
|
64
|
+
: null;
|
|
65
|
+
if (childReportPath) {
|
|
66
|
+
childEnv.REPORT_PATH = childReportPath;
|
|
67
|
+
}
|
|
68
|
+
const result = spawnSync(
|
|
69
|
+
process.execPath,
|
|
70
|
+
[path.join(__dirname, script)],
|
|
71
|
+
{
|
|
72
|
+
cwd: path.join(__dirname, '..'),
|
|
73
|
+
env: childEnv,
|
|
74
|
+
stdio: 'inherit'
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
if (result.error) {
|
|
78
|
+
childResult.report = readChildReport(childReportPath);
|
|
79
|
+
result.error.childResult = childResult;
|
|
80
|
+
throw result.error;
|
|
81
|
+
}
|
|
82
|
+
childResult.report = readChildReport(childReportPath);
|
|
83
|
+
if (result.status !== 0) {
|
|
84
|
+
const error = new Error(`${script} exited with status ${result.status ?? 1}`);
|
|
85
|
+
error.childResult = childResult;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
return childResult;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readChildReport(childReportPath) {
|
|
92
|
+
if (!childReportPath) return null;
|
|
93
|
+
if (!fs.existsSync(childReportPath)) return null;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(fs.readFileSync(childReportPath, 'utf8'));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
status: 'unreadable',
|
|
99
|
+
error: errorForReport(error)
|
|
100
|
+
};
|
|
101
|
+
} finally {
|
|
102
|
+
fs.rmSync(childReportPath, { force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function writeReport(output) {
|
|
107
|
+
if (!REPORT_PATH) return;
|
|
108
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
109
|
+
fs.writeFileSync(REPORT_PATH, `${JSON.stringify(output, null, 2)}\n`);
|
|
110
|
+
console.log(`high-concurrency benchmark report written: ${REPORT_PATH}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function errorForReport(error) {
|
|
114
|
+
return {
|
|
115
|
+
name: error && error.name ? error.name : 'Error',
|
|
116
|
+
message: error && error.message ? error.message : String(error),
|
|
117
|
+
stack: error && error.stack ? error.stack : undefined
|
|
118
|
+
};
|
|
119
|
+
}
|