@arikajs/benchmark 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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +37 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +42 -0
- package/dist/reporter.js +178 -0
- package/dist/runner.js +77 -0
- package/dist/servers/arika.d.ts +2 -0
- package/dist/servers/arika.js +43 -0
- package/dist/servers/express.d.ts +2 -0
- package/dist/servers/express.js +35 -0
- package/dist/servers/fastify.d.ts +1 -0
- package/dist/servers/fastify.js +35 -0
- package/dist/servers/node.js +27 -0
- package/dist/types.js +2 -0
- package/package.json +26 -0
- package/src/index.ts +48 -0
- package/src/servers/arika.ts +47 -0
- package/src/servers/express.ts +35 -0
- package/src/servers/fastify.ts +35 -0
- package/tsconfig.json +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @arikajs/benchmark
|
|
2
|
+
|
|
3
|
+
## 0.0.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- arikajs@0.0.8
|
|
9
|
+
- @arikajs/http@0.0.8
|
|
10
|
+
- @arikajs/router@0.0.8
|
|
11
|
+
|
|
12
|
+
## 0.0.7
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- arikajs@0.0.7
|
|
18
|
+
- @arikajs/http@0.0.7
|
|
19
|
+
- @arikajs/router@0.0.7
|
|
20
|
+
|
|
21
|
+
## 0.0.6
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- arikajs@0.0.6
|
|
27
|
+
- @arikajs/http@0.0.6
|
|
28
|
+
- @arikajs/router@0.0.6
|
|
29
|
+
|
|
30
|
+
## 0.0.5
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies
|
|
35
|
+
- arikajs@0.0.5
|
|
36
|
+
- @arikajs/http@0.0.5
|
|
37
|
+
- @arikajs/router@0.0.5
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runBenchmark = runBenchmark;
|
|
4
|
+
const arika_1 = require("./servers/arika");
|
|
5
|
+
const fastify_1 = require("./servers/fastify");
|
|
6
|
+
const express_1 = require("./servers/express");
|
|
7
|
+
const autocannon = require('autocannon');
|
|
8
|
+
async function runBenchmark({ duration = 10, connections = 200, warmup = 2 }) {
|
|
9
|
+
console.log('\n 🚀 ArikaJS Benchmark Suite');
|
|
10
|
+
console.log(' Comparing ArikaJS vs Fastify vs Express\n');
|
|
11
|
+
const run = (serverFactory, name, path) => {
|
|
12
|
+
return new Promise(async (resolve) => {
|
|
13
|
+
const server = await serverFactory();
|
|
14
|
+
await new Promise(res => server.listen(3000, () => res()));
|
|
15
|
+
// Warmup
|
|
16
|
+
await autocannon({ url: `http://localhost:3000${path}`, connections: 10, duration: warmup });
|
|
17
|
+
// Benchmark
|
|
18
|
+
const result = await autocannon({
|
|
19
|
+
url: `http://localhost:3000${path}`,
|
|
20
|
+
connections,
|
|
21
|
+
duration,
|
|
22
|
+
pipelining: 1
|
|
23
|
+
});
|
|
24
|
+
server.close();
|
|
25
|
+
console.log(` ✔ ${name.padEnd(15)} ${(result.requests.average || 0).toFixed(2)} req/s latency avg ${result.latency.average}ms`);
|
|
26
|
+
resolve(result);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
console.log(' 📌 Scenario: GET /hello (simple JSON)\n');
|
|
30
|
+
await run(express_1.createExpressServer, 'Express', '/hello');
|
|
31
|
+
await run(fastify_1.createFastifyServer, 'Fastify', '/hello');
|
|
32
|
+
await run(arika_1.createArikaServer, 'ArikaJS', '/hello');
|
|
33
|
+
console.log('\n 📌 Scenario: GET /users/:id (route params)\n');
|
|
34
|
+
await run(express_1.createExpressServer, 'Express', '/users/123');
|
|
35
|
+
await run(fastify_1.createFastifyServer, 'Fastify', '/users/123');
|
|
36
|
+
await run(arika_1.createArikaServer, 'ArikaJS', '/users/123');
|
|
37
|
+
console.log('\n 📌 Scenario: GET /posts/:id/comments (Middleware + Params)\n');
|
|
38
|
+
await run(express_1.createExpressServer, 'Express', '/posts/1/comments');
|
|
39
|
+
await run(fastify_1.createFastifyServer, 'Fastify', '/posts/1/comments');
|
|
40
|
+
await run(arika_1.createArikaServer, 'ArikaJS', '/posts/1/comments');
|
|
41
|
+
console.log('\n');
|
|
42
|
+
}
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printReport = printReport;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
|
+
// ─── Colour palette ────────────────────────────────────────────────────────
|
|
10
|
+
const FRAMEWORK_COLORS = {
|
|
11
|
+
'ArikaJS': chalk_1.default.hex('#7C3AED'), // violet
|
|
12
|
+
'Fastify': chalk_1.default.hex('#00B7A3'), // teal
|
|
13
|
+
'Express': chalk_1.default.hex('#E09000'), // amber
|
|
14
|
+
};
|
|
15
|
+
function colorFor(name) {
|
|
16
|
+
return FRAMEWORK_COLORS[name] ?? chalk_1.default.white;
|
|
17
|
+
}
|
|
18
|
+
function medal(rank) {
|
|
19
|
+
if (rank === 1)
|
|
20
|
+
return '🥇';
|
|
21
|
+
if (rank === 2)
|
|
22
|
+
return '🥈';
|
|
23
|
+
if (rank === 3)
|
|
24
|
+
return '🥉';
|
|
25
|
+
return ` ${rank}.`;
|
|
26
|
+
}
|
|
27
|
+
function fmtRps(n) {
|
|
28
|
+
return n >= 1000 ? `${(n / 1000).toFixed(2)}k` : n.toFixed(0);
|
|
29
|
+
}
|
|
30
|
+
function fmtBytes(n) {
|
|
31
|
+
if (n >= 1024 * 1024)
|
|
32
|
+
return `${(n / (1024 * 1024)).toFixed(2)} MB/s`;
|
|
33
|
+
if (n >= 1024)
|
|
34
|
+
return `${(n / 1024).toFixed(2)} KB/s`;
|
|
35
|
+
return `${n.toFixed(0)} B/s`;
|
|
36
|
+
}
|
|
37
|
+
function fmtMs(n) {
|
|
38
|
+
return `${n.toFixed(2)} ms`;
|
|
39
|
+
}
|
|
40
|
+
// ─── Header ────────────────────────────────────────────────────────────────
|
|
41
|
+
function printHeader(suite) {
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk_1.default.hex('#7C3AED').bold('╔══════════════════════════════════════════════════════════════╗'));
|
|
44
|
+
console.log(chalk_1.default.hex('#7C3AED').bold('║') + chalk_1.default.white.bold(' 🚀 ArikaJS Performance Benchmark Report ') + chalk_1.default.hex('#7C3AED').bold('║'));
|
|
45
|
+
console.log(chalk_1.default.hex('#7C3AED').bold('╚══════════════════════════════════════════════════════════════╝'));
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk_1.default.gray(` 📅 Timestamp : ${suite.timestamp}`));
|
|
48
|
+
console.log(chalk_1.default.gray(` ⬡ Node.js : ${suite.nodeVersion}`));
|
|
49
|
+
console.log(chalk_1.default.gray(` 💻 Platform : ${suite.platform}`));
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
// ─── Per-scenario table ─────────────────────────────────────────────────────
|
|
53
|
+
function printScenarioTable(scenario, rows) {
|
|
54
|
+
// Sort by req/s descending
|
|
55
|
+
const sorted = [...rows].sort((a, b) => b.requestsPerSecond - a.requestsPerSecond);
|
|
56
|
+
const topRps = sorted[0]?.requestsPerSecond ?? 1;
|
|
57
|
+
console.log(chalk_1.default.white.bold(` 📌 Scenario: `) + chalk_1.default.cyan.bold(scenario));
|
|
58
|
+
console.log(chalk_1.default.gray(` Connections: ${sorted[0]?.connections} Duration: ${sorted[0]?.duration}s`));
|
|
59
|
+
console.log('');
|
|
60
|
+
const table = new cli_table3_1.default({
|
|
61
|
+
head: [
|
|
62
|
+
chalk_1.default.white.bold('Rank'),
|
|
63
|
+
chalk_1.default.white.bold('Framework'),
|
|
64
|
+
chalk_1.default.white.bold('Req/s'),
|
|
65
|
+
chalk_1.default.white.bold('Latency avg'),
|
|
66
|
+
chalk_1.default.white.bold('Latency p99'),
|
|
67
|
+
chalk_1.default.white.bold('Throughput'),
|
|
68
|
+
chalk_1.default.white.bold('Errors'),
|
|
69
|
+
chalk_1.default.white.bold('vs Best'),
|
|
70
|
+
],
|
|
71
|
+
colAligns: ['center', 'left', 'right', 'right', 'right', 'right', 'right', 'right'],
|
|
72
|
+
style: {
|
|
73
|
+
head: [],
|
|
74
|
+
border: ['gray'],
|
|
75
|
+
},
|
|
76
|
+
chars: {
|
|
77
|
+
top: '─', 'top-mid': '┬', 'top-left': '╭', 'top-right': '╮',
|
|
78
|
+
bottom: '─', 'bottom-mid': '┴', 'bottom-left': '╰', 'bottom-right': '╯',
|
|
79
|
+
left: '│', 'left-mid': '├', mid: '─', 'mid-mid': '┼',
|
|
80
|
+
right: '│', 'right-mid': '┤', middle: '│',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
sorted.forEach((r, idx) => {
|
|
84
|
+
const rank = idx + 1;
|
|
85
|
+
const color = colorFor(r.framework);
|
|
86
|
+
const ratio = (r.requestsPerSecond / topRps);
|
|
87
|
+
const vsLabel = rank === 1
|
|
88
|
+
? chalk_1.default.green.bold(' ★ Best')
|
|
89
|
+
: chalk_1.default.red(` −${((1 - ratio) * 100).toFixed(1)}%`);
|
|
90
|
+
const errText = r.errors > 0
|
|
91
|
+
? chalk_1.default.red(r.errors.toLocaleString())
|
|
92
|
+
: chalk_1.default.green('0');
|
|
93
|
+
table.push([
|
|
94
|
+
medal(rank),
|
|
95
|
+
color.bold(r.framework),
|
|
96
|
+
color(fmtRps(r.requestsPerSecond)),
|
|
97
|
+
chalk_1.default.white(fmtMs(r.latencyAvg)),
|
|
98
|
+
chalk_1.default.white(fmtMs(r.latencyP99)),
|
|
99
|
+
chalk_1.default.gray(fmtBytes(r.throughput)),
|
|
100
|
+
errText,
|
|
101
|
+
vsLabel,
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
console.log(table.toString());
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
// ─── Summary comparison table ───────────────────────────────────────────────
|
|
108
|
+
function printSummary(results) {
|
|
109
|
+
const frameworks = [...new Set(results.map(r => r.framework))];
|
|
110
|
+
const scenarios = [...new Set(results.map(r => r.scenario))];
|
|
111
|
+
// Compute average req/s per framework across all scenarios
|
|
112
|
+
const avgMap = {};
|
|
113
|
+
for (const fw of frameworks) {
|
|
114
|
+
const fwResults = results.filter(r => r.framework === fw);
|
|
115
|
+
avgMap[fw] = fwResults.reduce((s, r) => s + r.requestsPerSecond, 0) / fwResults.length;
|
|
116
|
+
}
|
|
117
|
+
const sorted = frameworks.sort((a, b) => avgMap[b] - avgMap[a]);
|
|
118
|
+
const bestRps = avgMap[sorted[0]];
|
|
119
|
+
console.log(chalk_1.default.white.bold(' 📊 Overall Summary ') + chalk_1.default.gray('(avg req/s across all scenarios)'));
|
|
120
|
+
console.log('');
|
|
121
|
+
const table = new cli_table3_1.default({
|
|
122
|
+
head: [
|
|
123
|
+
chalk_1.default.white.bold('Rank'),
|
|
124
|
+
chalk_1.default.white.bold('Framework'),
|
|
125
|
+
chalk_1.default.white.bold('Avg Req/s'),
|
|
126
|
+
chalk_1.default.white.bold('vs Best'),
|
|
127
|
+
...scenarios.map(s => chalk_1.default.white.bold(s)),
|
|
128
|
+
],
|
|
129
|
+
colAligns: ['center', 'left', 'right', 'right', ...scenarios.map(() => 'right')],
|
|
130
|
+
style: { head: [], border: ['gray'] },
|
|
131
|
+
chars: {
|
|
132
|
+
top: '─', 'top-mid': '┬', 'top-left': '╭', 'top-right': '╮',
|
|
133
|
+
bottom: '─', 'bottom-mid': '┴', 'bottom-left': '╰', 'bottom-right': '╯',
|
|
134
|
+
left: '│', 'left-mid': '├', mid: '─', 'mid-mid': '┼',
|
|
135
|
+
right: '│', 'right-mid': '┤', middle: '│',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
sorted.forEach((fw, idx) => {
|
|
139
|
+
const rank = idx + 1;
|
|
140
|
+
const color = colorFor(fw);
|
|
141
|
+
const ratio = avgMap[fw] / bestRps;
|
|
142
|
+
const vsLabel = rank === 1
|
|
143
|
+
? chalk_1.default.green.bold(' ★ Best')
|
|
144
|
+
: chalk_1.default.red(` −${((1 - ratio) * 100).toFixed(1)}%`);
|
|
145
|
+
const perScenario = scenarios.map(s => {
|
|
146
|
+
const r = results.find(x => x.framework === fw && x.scenario === s);
|
|
147
|
+
return r ? color(fmtRps(r.requestsPerSecond)) : chalk_1.default.gray('N/A');
|
|
148
|
+
});
|
|
149
|
+
table.push([
|
|
150
|
+
medal(rank),
|
|
151
|
+
color.bold(fw),
|
|
152
|
+
color.bold(fmtRps(avgMap[fw])),
|
|
153
|
+
vsLabel,
|
|
154
|
+
...perScenario,
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
console.log(table.toString());
|
|
158
|
+
console.log('');
|
|
159
|
+
}
|
|
160
|
+
// ─── Footer ─────────────────────────────────────────────────────────────────
|
|
161
|
+
function printFooter() {
|
|
162
|
+
console.log(chalk_1.default.hex('#7C3AED').bold('─────────────────────────────────────────────────────────────────'));
|
|
163
|
+
console.log(chalk_1.default.gray(' ℹ Measured with autocannon. Results may vary between runs.'));
|
|
164
|
+
console.log(chalk_1.default.gray(' ℹ ArikaJS includes full framework boot (routing, middleware, DI).'));
|
|
165
|
+
console.log(chalk_1.default.hex('#7C3AED').bold('─────────────────────────────────────────────────────────────────'));
|
|
166
|
+
console.log('');
|
|
167
|
+
}
|
|
168
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
169
|
+
function printReport(suite) {
|
|
170
|
+
printHeader(suite);
|
|
171
|
+
const scenarios = [...new Set(suite.results.map(r => r.scenario))];
|
|
172
|
+
for (const scenario of scenarios) {
|
|
173
|
+
const rows = suite.results.filter(r => r.scenario === scenario);
|
|
174
|
+
printScenarioTable(scenario, rows);
|
|
175
|
+
}
|
|
176
|
+
printSummary(suite.results);
|
|
177
|
+
printFooter();
|
|
178
|
+
}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startServer = startServer;
|
|
7
|
+
exports.stopServer = stopServer;
|
|
8
|
+
exports.runScenario = runScenario;
|
|
9
|
+
const autocannon_1 = __importDefault(require("autocannon"));
|
|
10
|
+
/**
|
|
11
|
+
* Start an HTTP server on a random free port and return the address.
|
|
12
|
+
*/
|
|
13
|
+
function startServer(server) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
server.on('error', reject);
|
|
16
|
+
server.listen(0, '127.0.0.1', () => {
|
|
17
|
+
const addr = server.address();
|
|
18
|
+
resolve(addr);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Close an HTTP server gracefully.
|
|
24
|
+
*/
|
|
25
|
+
function stopServer(server) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
server.close(() => resolve());
|
|
28
|
+
// Force-close any keep-alive connections
|
|
29
|
+
if (typeof server.closeAllConnections === 'function') {
|
|
30
|
+
server.closeAllConnections();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run autocannon against a server for a single scenario and return structured results.
|
|
36
|
+
*/
|
|
37
|
+
async function runScenario(framework, server, scenario, opts) {
|
|
38
|
+
const { port } = await startServer(server);
|
|
39
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
40
|
+
// -- Warmup run (shorter, results discarded) --
|
|
41
|
+
if (opts.warmupDuration && opts.warmupDuration > 0) {
|
|
42
|
+
await (0, autocannon_1.default)({
|
|
43
|
+
url: `${baseUrl}${scenario.url}`,
|
|
44
|
+
connections: opts.connections,
|
|
45
|
+
duration: opts.warmupDuration,
|
|
46
|
+
silent: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// -- Actual benchmark run --
|
|
50
|
+
const result = await (0, autocannon_1.default)({
|
|
51
|
+
url: `${baseUrl}${scenario.url}`,
|
|
52
|
+
method: scenario.method,
|
|
53
|
+
connections: opts.connections,
|
|
54
|
+
duration: opts.duration,
|
|
55
|
+
pipelining: opts.pipelining ?? 1,
|
|
56
|
+
silent: true,
|
|
57
|
+
...(scenario.body
|
|
58
|
+
? {
|
|
59
|
+
headers: { 'content-type': 'application/json' },
|
|
60
|
+
body: JSON.stringify(scenario.body),
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
63
|
+
});
|
|
64
|
+
await stopServer(server);
|
|
65
|
+
return {
|
|
66
|
+
framework,
|
|
67
|
+
scenario: scenario.name,
|
|
68
|
+
requestsPerSecond: result.requests.average,
|
|
69
|
+
latencyAvg: result.latency.average,
|
|
70
|
+
latencyP99: result.latency.p99,
|
|
71
|
+
throughput: result.throughput.average,
|
|
72
|
+
errors: result.errors,
|
|
73
|
+
timeouts: result.timeouts,
|
|
74
|
+
duration: opts.duration,
|
|
75
|
+
connections: opts.connections,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createArikaServer = createArikaServer;
|
|
7
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
async function createArikaServer() {
|
|
10
|
+
const origConsoleLog = console.log;
|
|
11
|
+
console.log = () => { };
|
|
12
|
+
console.info = () => { };
|
|
13
|
+
console.warn = () => { };
|
|
14
|
+
console.error = () => { };
|
|
15
|
+
const { Application } = require('arikajs');
|
|
16
|
+
const { Route, RouteRegistry } = require('@arikajs/router');
|
|
17
|
+
RouteRegistry.getInstance().clear();
|
|
18
|
+
const app = new Application(node_path_1.default.resolve(__dirname, '../../../../'));
|
|
19
|
+
Route.get('/hello', () => ({ message: 'Hello from ArikaJS!' }));
|
|
20
|
+
// Test parameters
|
|
21
|
+
Route.get('/users/:id', (req) => ({
|
|
22
|
+
id: req.param('id'),
|
|
23
|
+
name: `User ${req.param('id')}`
|
|
24
|
+
}));
|
|
25
|
+
// Test Middleware + Nested Params
|
|
26
|
+
Route.get('/posts/:id/comments', (req) => {
|
|
27
|
+
return {
|
|
28
|
+
id: req.param('id'),
|
|
29
|
+
comments: [
|
|
30
|
+
{ id: 1, text: 'First comment' },
|
|
31
|
+
{ id: 2, text: 'Second comment' }
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}).withMiddleware((req, res, next) => {
|
|
35
|
+
// Simple auth simulation
|
|
36
|
+
req.user = { id: 1, name: 'Tester' };
|
|
37
|
+
return next();
|
|
38
|
+
});
|
|
39
|
+
await app.boot();
|
|
40
|
+
const server = node_http_1.default.createServer(app.getCallback());
|
|
41
|
+
console.log = origConsoleLog;
|
|
42
|
+
return server;
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createExpressServer = createExpressServer;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
9
|
+
async function createExpressServer() {
|
|
10
|
+
const origConsoleLog = console.log;
|
|
11
|
+
console.log = () => { };
|
|
12
|
+
const app = (0, express_1.default)();
|
|
13
|
+
app.get('/hello', (req, res) => {
|
|
14
|
+
res.json({ message: 'Hello from ArikaJS!' });
|
|
15
|
+
});
|
|
16
|
+
app.get('/users/:id', (req, res) => {
|
|
17
|
+
res.json({ id: req.params.id, name: `User ${req.params.id}` });
|
|
18
|
+
});
|
|
19
|
+
app.get('/posts/:id/comments', (req, res, next) => {
|
|
20
|
+
// Simple auth simulation
|
|
21
|
+
req.user = { id: 1, name: 'Tester' };
|
|
22
|
+
next();
|
|
23
|
+
}, (req, res) => {
|
|
24
|
+
res.json({
|
|
25
|
+
id: req.params.id,
|
|
26
|
+
comments: [
|
|
27
|
+
{ id: 1, text: 'First comment' },
|
|
28
|
+
{ id: 2, text: 'Second comment' }
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
const server = node_http_1.default.createServer(app);
|
|
33
|
+
console.log = origConsoleLog;
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createFastifyServer(): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createFastifyServer = createFastifyServer;
|
|
7
|
+
const fastify_1 = __importDefault(require("fastify"));
|
|
8
|
+
async function createFastifyServer() {
|
|
9
|
+
const origConsoleLog = console.log;
|
|
10
|
+
console.log = () => { };
|
|
11
|
+
const app = (0, fastify_1.default)();
|
|
12
|
+
app.get('/hello', async (request, reply) => {
|
|
13
|
+
return { message: 'Hello from ArikaJS!' }; // Keep same text
|
|
14
|
+
});
|
|
15
|
+
app.get('/users/:id', async (request, reply) => {
|
|
16
|
+
return { id: request.params.id, name: `User ${request.params.id}` };
|
|
17
|
+
});
|
|
18
|
+
app.get('/posts/:id/comments', {
|
|
19
|
+
onRequest: async (request, reply) => {
|
|
20
|
+
// Simple auth simulation
|
|
21
|
+
request.user = { id: 1, name: 'Tester' };
|
|
22
|
+
}
|
|
23
|
+
}, async (request, reply) => {
|
|
24
|
+
return {
|
|
25
|
+
id: request.params.id,
|
|
26
|
+
comments: [
|
|
27
|
+
{ id: 1, text: 'First comment' },
|
|
28
|
+
{ id: 2, text: 'Second comment' }
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
await app.ready();
|
|
33
|
+
console.log = origConsoleLog;
|
|
34
|
+
return app.server;
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createNodeServer = createNodeServer;
|
|
7
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
8
|
+
/**
|
|
9
|
+
* Minimal pure Node.js HTTP server (no framework overhead) – baseline reference.
|
|
10
|
+
*/
|
|
11
|
+
function createNodeServer() {
|
|
12
|
+
return node_http_1.default.createServer((req, res) => {
|
|
13
|
+
if (req.method === 'GET' && req.url === '/hello') {
|
|
14
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
15
|
+
res.end(JSON.stringify({ message: 'Hello from Node.js!' }));
|
|
16
|
+
}
|
|
17
|
+
else if (req.method === 'GET' && req.url?.startsWith('/users/')) {
|
|
18
|
+
const id = req.url.split('/')[2];
|
|
19
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
20
|
+
res.end(JSON.stringify({ id, name: `User ${id}` }));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
24
|
+
res.end(JSON.stringify({ error: 'Not Found' }));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arikajs/benchmark",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Benchmark suite for ArikaJS applications.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"autocannon": "^8.0.0",
|
|
13
|
+
"express": "^4.22.1",
|
|
14
|
+
"@arikajs/router": "0.1.0",
|
|
15
|
+
"arikajs": "0.1.0",
|
|
16
|
+
"@arikajs/http": "0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/express": "^5.0.6",
|
|
20
|
+
"cli-table3": "^0.6.5",
|
|
21
|
+
"fastify": "^5.8.1"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createArikaServer } from './servers/arika';
|
|
2
|
+
import { createFastifyServer } from './servers/fastify';
|
|
3
|
+
import { createExpressServer } from './servers/express';
|
|
4
|
+
const autocannon = require('autocannon');
|
|
5
|
+
|
|
6
|
+
export async function runBenchmark({ duration = 10, connections = 200, warmup = 2 }) {
|
|
7
|
+
console.log('\n 🚀 ArikaJS Benchmark Suite');
|
|
8
|
+
console.log(' Comparing ArikaJS vs Fastify vs Express\n');
|
|
9
|
+
|
|
10
|
+
const run = (serverFactory: any, name: string, path: string) => {
|
|
11
|
+
return new Promise<any>(async (resolve) => {
|
|
12
|
+
const server = await serverFactory();
|
|
13
|
+
await new Promise<void>(res => server.listen(3000, () => res()));
|
|
14
|
+
|
|
15
|
+
// Warmup
|
|
16
|
+
await autocannon({ url: `http://localhost:3000${path}`, connections: 10, duration: warmup });
|
|
17
|
+
|
|
18
|
+
// Benchmark
|
|
19
|
+
const result = await autocannon({
|
|
20
|
+
url: `http://localhost:3000${path}`,
|
|
21
|
+
connections,
|
|
22
|
+
duration,
|
|
23
|
+
pipelining: 1
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
server.close();
|
|
27
|
+
console.log(` ✔ ${name.padEnd(15)} ${(result.requests.average || 0).toFixed(2)} req/s latency avg ${result.latency.average}ms`);
|
|
28
|
+
resolve(result);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
console.log(' 📌 Scenario: GET /hello (simple JSON)\n');
|
|
33
|
+
await run(createExpressServer, 'Express', '/hello');
|
|
34
|
+
await run(createFastifyServer, 'Fastify', '/hello');
|
|
35
|
+
await run(createArikaServer, 'ArikaJS', '/hello');
|
|
36
|
+
|
|
37
|
+
console.log('\n 📌 Scenario: GET /users/:id (route params)\n');
|
|
38
|
+
await run(createExpressServer, 'Express', '/users/123');
|
|
39
|
+
await run(createFastifyServer, 'Fastify', '/users/123');
|
|
40
|
+
await run(createArikaServer, 'ArikaJS', '/users/123');
|
|
41
|
+
|
|
42
|
+
console.log('\n 📌 Scenario: GET /posts/:id/comments (Middleware + Params)\n');
|
|
43
|
+
await run(createExpressServer, 'Express', '/posts/1/comments');
|
|
44
|
+
await run(createFastifyServer, 'Fastify', '/posts/1/comments');
|
|
45
|
+
await run(createArikaServer, 'ArikaJS', '/posts/1/comments');
|
|
46
|
+
|
|
47
|
+
console.log('\n');
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export async function createArikaServer(): Promise<http.Server> {
|
|
5
|
+
const origConsoleLog = console.log;
|
|
6
|
+
console.log = () => { };
|
|
7
|
+
console.info = () => { };
|
|
8
|
+
console.warn = () => { };
|
|
9
|
+
console.error = () => { };
|
|
10
|
+
|
|
11
|
+
const { Application } = require('arikajs');
|
|
12
|
+
const { Route, RouteRegistry } = require('@arikajs/router');
|
|
13
|
+
|
|
14
|
+
RouteRegistry.getInstance().clear();
|
|
15
|
+
|
|
16
|
+
const app = new Application(path.resolve(__dirname, '../../../../'));
|
|
17
|
+
|
|
18
|
+
Route.get('/hello', () => ({ message: 'Hello from ArikaJS!' }));
|
|
19
|
+
|
|
20
|
+
// Test parameters
|
|
21
|
+
Route.get('/users/:id', (req: any) => ({
|
|
22
|
+
id: req.param('id'),
|
|
23
|
+
name: `User ${req.param('id')}`
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Test Middleware + Nested Params
|
|
27
|
+
Route.get('/posts/:id/comments', (req: any) => {
|
|
28
|
+
return {
|
|
29
|
+
id: req.param('id'),
|
|
30
|
+
comments: [
|
|
31
|
+
{ id: 1, text: 'First comment' },
|
|
32
|
+
{ id: 2, text: 'Second comment' }
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}).withMiddleware((req: any, res: any, next: any) => {
|
|
36
|
+
// Simple auth simulation
|
|
37
|
+
req.user = { id: 1, name: 'Tester' };
|
|
38
|
+
return next();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await app.boot();
|
|
42
|
+
|
|
43
|
+
const server = http.createServer(app.getCallback());
|
|
44
|
+
|
|
45
|
+
console.log = origConsoleLog;
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
|
|
4
|
+
export async function createExpressServer(): Promise<http.Server> {
|
|
5
|
+
const origConsoleLog = console.log;
|
|
6
|
+
console.log = () => { };
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
|
|
10
|
+
app.get('/hello', (req: any, res: any) => {
|
|
11
|
+
res.json({ message: 'Hello from ArikaJS!' });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
app.get('/users/:id', (req: any, res: any) => {
|
|
15
|
+
res.json({ id: req.params.id, name: `User ${req.params.id}` });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
app.get('/posts/:id/comments', (req: any, res: any, next: any) => {
|
|
19
|
+
// Simple auth simulation
|
|
20
|
+
(req as any).user = { id: 1, name: 'Tester' };
|
|
21
|
+
next();
|
|
22
|
+
}, (req: any, res: any) => {
|
|
23
|
+
res.json({
|
|
24
|
+
id: req.params.id,
|
|
25
|
+
comments: [
|
|
26
|
+
{ id: 1, text: 'First comment' },
|
|
27
|
+
{ id: 2, text: 'Second comment' }
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const server = http.createServer(app);
|
|
33
|
+
console.log = origConsoleLog;
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fastify from 'fastify';
|
|
2
|
+
|
|
3
|
+
export async function createFastifyServer() {
|
|
4
|
+
const origConsoleLog = console.log;
|
|
5
|
+
console.log = () => { };
|
|
6
|
+
|
|
7
|
+
const app = fastify();
|
|
8
|
+
|
|
9
|
+
app.get('/hello', async (request: any, reply: any) => {
|
|
10
|
+
return { message: 'Hello from ArikaJS!' }; // Keep same text
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
app.get('/users/:id', async (request: any, reply: any) => {
|
|
14
|
+
return { id: request.params.id, name: `User ${request.params.id}` };
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.get('/posts/:id/comments', {
|
|
18
|
+
onRequest: async (request: any, reply: any) => {
|
|
19
|
+
// Simple auth simulation
|
|
20
|
+
(request as any).user = { id: 1, name: 'Tester' };
|
|
21
|
+
}
|
|
22
|
+
}, async (request: any, reply: any) => {
|
|
23
|
+
return {
|
|
24
|
+
id: request.params.id,
|
|
25
|
+
comments: [
|
|
26
|
+
{ id: 1, text: 'First comment' },
|
|
27
|
+
{ id: 2, text: 'Second comment' }
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await app.ready();
|
|
33
|
+
console.log = origConsoleLog;
|
|
34
|
+
return app.server;
|
|
35
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*"
|
|
16
|
+
]
|
|
17
|
+
}
|