mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
feat: update benchmarks
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# based on https://medium.com/@felipedutratine/intelligent-benchmark-with-wrk-163986c1587f
|
|
||||||
|
|
||||||
cd /tmp/
|
|
||||||
sudo apt-get install build-essential libssl-dev git -y
|
|
||||||
git clone --depth=1 https://github.com/wg/wrk.git wrk
|
|
||||||
cd wrk
|
|
||||||
sudo make
|
|
||||||
# move the executable to somewhere in your PATH, ex:
|
|
||||||
sudo cp wrk /usr/local/bin
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
-----------------------
|
|
||||||
express
|
|
||||||
-----------------------
|
|
||||||
Running 10s test @ http://localhost:3000
|
|
||||||
1024 connections
|
|
||||||
|
|
||||||
┌─────────┬───────┬───────┬───────┬────────┬──────────┬──────────┬────────┐
|
|
||||||
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
||||||
├─────────┼───────┼───────┼───────┼────────┼──────────┼──────────┼────────┤
|
|
||||||
│ Latency │ 55 ms │ 58 ms │ 91 ms │ 138 ms │ 61.88 ms │ 23.95 ms │ 747 ms │
|
|
||||||
└─────────┴───────┴───────┴───────┴────────┴──────────┴──────────┴────────┘
|
|
||||||
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
|
|
||||||
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
||||||
│ Req/Sec │ 8407 │ 8407 │ 17407 │ 17743 │ 16454.41 │ 2716.94 │ 8402 │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
|
|
||||||
│ Bytes/Sec │ 1.81 MB │ 1.81 MB │ 3.74 MB │ 3.81 MB │ 3.54 MB │ 584 kB │ 1.81 MB │
|
|
||||||
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
|
|
||||||
|
|
||||||
Req/Bytes counts sampled once per second.
|
|
||||||
|
|
||||||
165k requests in 10.17s, 35.4 MB read
|
|
||||||
-----------------------
|
|
||||||
nest (with "@nestjs/platform-express")
|
|
||||||
-----------------------
|
|
||||||
Running 10s test @ http://localhost:3000
|
|
||||||
1024 connections
|
|
||||||
|
|
||||||
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐
|
|
||||||
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
||||||
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤
|
|
||||||
│ Latency │ 61 ms │ 64 ms │ 71 ms │ 94 ms │ 65.44 ms │ 17.35 ms │ 325 ms │
|
|
||||||
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘
|
|
||||||
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
|
|
||||||
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
|
|
||||||
│ Req/Sec │ 14183 │ 14183 │ 15767 │ 15991 │ 15640 │ 501.13 │ 14182 │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
|
|
||||||
│ Bytes/Sec │ 3.06 MB │ 3.06 MB │ 3.41 MB │ 3.45 MB │ 3.38 MB │ 108 kB │ 3.06 MB │
|
|
||||||
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
|
|
||||||
|
|
||||||
Req/Bytes counts sampled once per second.
|
|
||||||
|
|
||||||
156k requests in 10.24s, 33.8 MB read
|
|
||||||
-----------------------
|
|
||||||
fastify
|
|
||||||
-----------------------
|
|
||||||
Running 10s test @ http://localhost:3000
|
|
||||||
1024 connections
|
|
||||||
|
|
||||||
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬─────────┐
|
|
||||||
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
||||||
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼─────────┤
|
|
||||||
│ Latency │ 27 ms │ 30 ms │ 39 ms │ 78 ms │ 31.62 ms │ 26.59 ms │ 1232 ms │
|
|
||||||
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴─────────┘
|
|
||||||
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
|
|
||||||
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
|
|
||||||
│ Req/Sec │ 19935 │ 19935 │ 33247 │ 34111 │ 32030.4 │ 4103.84 │ 19931 │
|
|
||||||
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
|
|
||||||
│ Bytes/Sec │ 3.03 MB │ 3.03 MB │ 5.05 MB │ 5.19 MB │ 4.87 MB │ 624 kB │ 3.03 MB │
|
|
||||||
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
|
|
||||||
|
|
||||||
Req/Bytes counts sampled once per second.
|
|
||||||
|
|
||||||
320k requests in 10.18s, 48.7 MB read
|
|
||||||
-----------------------
|
|
||||||
nest (with "@nestjs/platform-fastify")
|
|
||||||
-----------------------
|
|
||||||
Running 10s test @ http://localhost:3000
|
|
||||||
1024 connections
|
|
||||||
|
|
||||||
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐
|
|
||||||
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
|
|
||||||
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤
|
|
||||||
│ Latency │ 31 ms │ 33 ms │ 38 ms │ 52 ms │ 34.41 ms │ 11.73 ms │ 245 ms │
|
|
||||||
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘
|
|
||||||
┌───────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┬─────────┐
|
|
||||||
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
|
|
||||||
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
|
|
||||||
│ Req/Sec │ 24911 │ 24911 │ 30031 │ 30335 │ 29470.4 │ 1564.48 │ 24907 │
|
|
||||||
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
|
|
||||||
│ Bytes/Sec │ 3.81 MB │ 3.81 MB │ 4.6 MB │ 4.64 MB │ 4.51 MB │ 239 kB │ 3.81 MB │
|
|
||||||
└───────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┴─────────┘
|
|
||||||
|
|
||||||
Req/Bytes counts sampled once per second.
|
|
||||||
|
|
||||||
295k requests in 10.17s, 45.1 MB read
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const express = require('express');
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.get('/', async (req, res) => res.send('Hello world'));
|
|
||||||
app.listen(3000);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const fastify = require('fastify')();
|
|
||||||
fastify.get('/', async (req, reply) => reply.send('Hello world'));
|
|
||||||
fastify.listen({
|
|
||||||
port: 3000
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const core_1 = require('@nestjs/core');
|
|
||||||
const fastify_platform_1 = require('@nestjs/platform-fastify');
|
|
||||||
const app_module_1 = require('./nest/app.module');
|
|
||||||
core_1.NestFactory.create(
|
|
||||||
app_module_1.AppModule,
|
|
||||||
new fastify_platform_1.FastifyAdapter(),
|
|
||||||
{
|
|
||||||
logger: false,
|
|
||||||
bodyParser: false,
|
|
||||||
},
|
|
||||||
).then(app => app.listen(3000));
|
|
||||||
//# sourceMappingURL=main.js.map
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const core_1 = require('@nestjs/core');
|
|
||||||
const app_module_1 = require('./nest/app.module');
|
|
||||||
core_1.NestFactory.create(app_module_1.AppModule, {
|
|
||||||
logger: false,
|
|
||||||
bodyParser: false,
|
|
||||||
}).then(app => app.listen(3000));
|
|
||||||
//# sourceMappingURL=main.js.map
|
|
||||||
3
benchmarks/nest/app.controller.d.ts
vendored
3
benchmarks/nest/app.controller.d.ts
vendored
@@ -1,3 +0,0 @@
|
|||||||
export declare class AppController {
|
|
||||||
root(): string;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
var __decorate =
|
|
||||||
(this && this.__decorate) ||
|
|
||||||
function(decorators, target, key, desc) {
|
|
||||||
var c = arguments.length,
|
|
||||||
r =
|
|
||||||
c < 3
|
|
||||||
? target
|
|
||||||
: desc === null
|
|
||||||
? (desc = Object.getOwnPropertyDescriptor(target, key))
|
|
||||||
: desc,
|
|
||||||
d;
|
|
||||||
if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
|
|
||||||
r = Reflect.decorate(decorators, target, key, desc);
|
|
||||||
else
|
|
||||||
for (var i = decorators.length - 1; i >= 0; i--)
|
|
||||||
if ((d = decorators[i]))
|
|
||||||
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
||||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
||||||
};
|
|
||||||
var __metadata =
|
|
||||||
(this && this.__metadata) ||
|
|
||||||
function(k, v) {
|
|
||||||
if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function')
|
|
||||||
return Reflect.metadata(k, v);
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const common_1 = require('@nestjs/common');
|
|
||||||
let AppController = class AppController {
|
|
||||||
root() {
|
|
||||||
return 'Hello world!';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
__decorate(
|
|
||||||
[
|
|
||||||
common_1.Get(),
|
|
||||||
__metadata('design:type', Function),
|
|
||||||
__metadata('design:paramtypes', []),
|
|
||||||
__metadata('design:returntype', String),
|
|
||||||
],
|
|
||||||
AppController.prototype,
|
|
||||||
'root',
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
AppController = __decorate([common_1.Controller()], AppController);
|
|
||||||
exports.AppController = AppController;
|
|
||||||
//# sourceMappingURL=app.controller.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,2CAAiD;AAGjD,IAAa,aAAa,GAA1B,MAAa,aAAa;IAExB,IAAI;QACF,OAAO,cAAc,CAAA;IACvB,CAAC;CACF,CAAA;AAHC;IADC,YAAG,EAAE;;;;yCAGL;AAJU,aAAa;IADzB,mBAAU,EAAE;GACA,aAAa,CAKzB;AALY,sCAAa"}
|
|
||||||
1
benchmarks/nest/app.module.d.ts
vendored
1
benchmarks/nest/app.module.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export declare class AppModule {}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
||||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
||||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
||||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
||||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const common_1 = require("@nestjs/common");
|
|
||||||
const app_controller_1 = require("./app.controller");
|
|
||||||
let AppModule = class AppModule {
|
|
||||||
};
|
|
||||||
AppModule = __decorate([
|
|
||||||
common_1.Module({
|
|
||||||
imports: [],
|
|
||||||
controllers: [app_controller_1.AppController],
|
|
||||||
})
|
|
||||||
], AppModule);
|
|
||||||
exports.AppModule = AppModule;
|
|
||||||
//# sourceMappingURL=app.module.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AAMjD,IAAa,SAAS,GAAtB,MAAa,SAAS;CAAG,CAAA;AAAZ,SAAS;IAJrB,eAAM,CAAC;QACN,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;KAC7B,CAAC;GACW,SAAS,CAAG;AAAZ,8BAAS"}
|
|
||||||
1
benchmarks/nest/main.d.ts
vendored
1
benchmarks/nest/main.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export {};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AACD,SAAS,EAAE,CAAC"}
|
|
||||||
@@ -38,18 +38,18 @@ export default tseslint.config(
|
|||||||
'@typescript-eslint/no-require-imports': 'off',
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
|
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
|
||||||
"@typescript-eslint/no-misused-promises": [
|
'@typescript-eslint/no-misused-promises': [
|
||||||
"error",
|
'error',
|
||||||
{
|
{
|
||||||
"checksVoidReturn": false,
|
checksVoidReturn: false,
|
||||||
"checksConditionals": false
|
checksConditionals: false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"@typescript-eslint/require-await": "off",
|
'@typescript-eslint/require-await': 'off',
|
||||||
'@typescript-eslint/prefer-promise-reject-errors': 'off',
|
'@typescript-eslint/prefer-promise-reject-errors': 'off',
|
||||||
'@typescript-eslint/no-base-to-string': 'off',
|
'@typescript-eslint/no-base-to-string': 'off',
|
||||||
'@typescript-eslint/unbound-method': 'off',
|
'@typescript-eslint/unbound-method': 'off',
|
||||||
'@typescript-eslint/only-throw-error': 'off',
|
'@typescript-eslint/only-throw-error': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
import { codechecks, CodeChecksReport } from '@codechecks/client';
|
|
||||||
import * as bytes from 'bytes';
|
|
||||||
import { Benchmarks, getBenchmarks, LIBS } from './get-benchmarks';
|
|
||||||
|
|
||||||
const markdownTable = require('markdown-table');
|
|
||||||
const benchmarksKey = 'nest/performance-benchmark';
|
|
||||||
|
|
||||||
export default async function checkBenchmarks() {
|
|
||||||
const currentBenchmarks = await getBenchmarks();
|
|
||||||
await codechecks.saveValue(benchmarksKey, currentBenchmarks);
|
|
||||||
|
|
||||||
if (!codechecks.isPr()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const baselineBenchmarks =
|
|
||||||
await codechecks.getValue<Benchmarks>(benchmarksKey);
|
|
||||||
const report = getCodechecksReport(currentBenchmarks, baselineBenchmarks);
|
|
||||||
await codechecks.report(report);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCodechecksReport(
|
|
||||||
current: Benchmarks,
|
|
||||||
baseline: Benchmarks | undefined,
|
|
||||||
): CodeChecksReport {
|
|
||||||
const diff = getDiff(current, baseline);
|
|
||||||
|
|
||||||
const shortDescription = getShortDescription(baseline, diff);
|
|
||||||
const longDescription = getLongDescription(current, baseline, diff);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Benchmarks',
|
|
||||||
status: 'success',
|
|
||||||
shortDescription,
|
|
||||||
longDescription,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShortDescription(
|
|
||||||
baseline: Benchmarks | undefined,
|
|
||||||
diff: BenchmarksDiff,
|
|
||||||
): string {
|
|
||||||
if (!baseline) {
|
|
||||||
return 'New benchmarks generated';
|
|
||||||
}
|
|
||||||
|
|
||||||
const avgDiff = getAverageDiff(diff);
|
|
||||||
if (avgDiff > 0) {
|
|
||||||
return `Performance improved by ${avgDiff.toFixed(
|
|
||||||
2,
|
|
||||||
)}% on average, good job!`;
|
|
||||||
}
|
|
||||||
if (avgDiff === 0) {
|
|
||||||
return `No changes in performance detected`;
|
|
||||||
}
|
|
||||||
if (avgDiff < 0) {
|
|
||||||
return `Performance decreased by ${avgDiff.toFixed(
|
|
||||||
2,
|
|
||||||
)}% on average, be careful!`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLongDescription(
|
|
||||||
current: Benchmarks,
|
|
||||||
baseline: Benchmarks | undefined,
|
|
||||||
diff: BenchmarksDiff,
|
|
||||||
): string {
|
|
||||||
function printTableRow(id: string, label: string): string[] {
|
|
||||||
return [
|
|
||||||
label,
|
|
||||||
current[id].requestsPerSec.toFixed(0),
|
|
||||||
current[id].transferPerSec,
|
|
||||||
baseline ? formatPerc(diff[id].requestsPerSecDiff) : '-',
|
|
||||||
baseline ? formatPerc(diff[id].transferPerSecDiff) : '-',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = [
|
|
||||||
['', 'Req/sec', 'Trans/sec', 'Req/sec DIFF', 'Trans/sec DIFF'],
|
|
||||||
printTableRow('nest', 'Nest-Express'),
|
|
||||||
printTableRow('nest-fastify', 'Nest-Fastify'),
|
|
||||||
printTableRow('express', 'Express'),
|
|
||||||
printTableRow('fastify', 'Fastify'),
|
|
||||||
];
|
|
||||||
|
|
||||||
return markdownTable(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDiff(
|
|
||||||
current: Benchmarks,
|
|
||||||
baseline: Benchmarks | undefined,
|
|
||||||
): BenchmarksDiff {
|
|
||||||
const diff: BenchmarksDiff = {};
|
|
||||||
for (const l of LIBS) {
|
|
||||||
if (!baseline) {
|
|
||||||
diff[l] = undefined;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentValue = current[l];
|
|
||||||
const baselineValue = baseline[l];
|
|
||||||
|
|
||||||
diff[l] = {
|
|
||||||
requestsPerSecDiff: getRequestDiff(
|
|
||||||
currentValue.requestsPerSec,
|
|
||||||
baselineValue.requestsPerSec,
|
|
||||||
),
|
|
||||||
transferPerSecDiff: getTransferDiff(
|
|
||||||
currentValue.transferPerSec,
|
|
||||||
baselineValue.transferPerSec,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTransferDiff(
|
|
||||||
currentTransfer: string,
|
|
||||||
baselineTransfer: string,
|
|
||||||
): number {
|
|
||||||
return 1 - bytes.parse(currentTransfer) / bytes.parse(baselineTransfer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAverageDiff(diff: BenchmarksDiff) {
|
|
||||||
return (
|
|
||||||
(diff['nest'].transferPerSecDiff +
|
|
||||||
diff['nest'].requestsPerSecDiff +
|
|
||||||
diff['nest-fastify'].transferPerSecDiff +
|
|
||||||
diff['nest-fastify'].requestsPerSecDiff) /
|
|
||||||
4
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRequestDiff(currentRequest: number, baselineRequest: number) {
|
|
||||||
return 1 - currentRequest / baselineRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BenchmarkDiff {
|
|
||||||
transferPerSecDiff: number | undefined;
|
|
||||||
requestsPerSecDiff: number | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BenchmarksDiff {
|
|
||||||
[lib: string]: BenchmarkDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatPerc(n: number) {
|
|
||||||
return (n > 0 ? '+' : '') + (n * 100).toFixed(2) + '%';
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import wrkPkg = require('wrk');
|
|
||||||
import { spawn } from 'child_process';
|
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
export interface Benchmarks {
|
|
||||||
[lib: string]: WrkResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrk = (options: any) =>
|
|
||||||
new Promise<WrkResults>((resolve, reject) =>
|
|
||||||
wrkPkg(options, (err: any, result: any) =>
|
|
||||||
err ? reject(err) : resolve(result),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const sleep = (time: number) =>
|
|
||||||
new Promise(resolve => setTimeout(resolve, time));
|
|
||||||
|
|
||||||
const BENCHMARK_PATH = join(__dirname, '../../benchmarks');
|
|
||||||
export const LIBS = ['express', 'fastify', 'nest', 'nest-fastify'];
|
|
||||||
|
|
||||||
async function runBenchmarkOfLib(lib: string): Promise<WrkResults> {
|
|
||||||
const libPath = join(BENCHMARK_PATH, `${lib}.js`);
|
|
||||||
const process = spawn('node', [libPath], {
|
|
||||||
detached: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stdout!.on('data', data => {
|
|
||||||
console.log(`stdout: ${data}`);
|
|
||||||
});
|
|
||||||
process.stderr!.on('data', data => {
|
|
||||||
console.log(`stderr: ${data}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.unref();
|
|
||||||
|
|
||||||
await sleep(2000);
|
|
||||||
|
|
||||||
const result = await wrk({
|
|
||||||
threads: 8,
|
|
||||||
duration: '10s',
|
|
||||||
connections: 1024,
|
|
||||||
url: 'http://localhost:3000',
|
|
||||||
});
|
|
||||||
|
|
||||||
process.kill();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBenchmarks() {
|
|
||||||
const results: Benchmarks = {};
|
|
||||||
for await (const lib of LIBS) {
|
|
||||||
const result = await runBenchmarkOfLib(lib);
|
|
||||||
results[lib] = result;
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WrkResults {
|
|
||||||
transferPerSec: string;
|
|
||||||
requestsPerSec: number;
|
|
||||||
connectErrors: string;
|
|
||||||
readErrors: string;
|
|
||||||
writeErrors: string;
|
|
||||||
timeoutErrors: string;
|
|
||||||
requestsTotal: number;
|
|
||||||
durationActual: string;
|
|
||||||
transferTotal: string;
|
|
||||||
latencyAvg: string;
|
|
||||||
latencyStdev: string;
|
|
||||||
latencyMax: string;
|
|
||||||
latencyStdevPerc: number;
|
|
||||||
rpsAvg: string;
|
|
||||||
rpsStdev: string;
|
|
||||||
rpsMax: string;
|
|
||||||
rpsStdevPerc: number;
|
|
||||||
}
|
|
||||||
2632
tools/benchmarks/package-lock.json
generated
Normal file
2632
tools/benchmarks/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
tools/benchmarks/package.json
Normal file
18
tools/benchmarks/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@nestjs/benchmarks",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Nest - modern, fast, powerful node.js web framework (@benchmarks)",
|
||||||
|
"license": "ISC",
|
||||||
|
"scripts": {
|
||||||
|
"benchmarks": "tsc && node dist/main.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/platform-express": "^11.1.12",
|
||||||
|
"@nestjs/platform-fastify": "^11.1.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/autocannon": "^7.12.7",
|
||||||
|
"autocannon": "^8.0.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
Short description (shown on main PR screen): Performance improved 0.13% on average.
|
|
||||||
|
|
||||||
Long description (after clicking details):
|
|
||||||
|
|
||||||
| | Req/sec | Trans/sec | Req/sec DIFF | Trans/sec DIFF | Req vs Express | Trans vs Fastify |
|
|
||||||
| -------------- | ------- | --------- | ------------ | -------------- | -------------- | ---------------- |
|
|
||||||
| NestJS-Express | 3.37MB | 16375.58 | +0.15% | +0.14% | 80.62% | 80.37% |
|
|
||||||
| NestJS-Fastify | 4.78MB | 32728.51 | +0.12% | +0.12 | 64.76% | 64.25% |
|
|
||||||
| Express | 4.18MB | 20374.59 | 0% | 0% | - | - |
|
|
||||||
| Fastify | 7.38MB | 50938 | 0% | 0% | - | - |
|
|
||||||
|
|
||||||
## Explanations:
|
|
||||||
|
|
||||||
Short description: average of all diffs for NestJS-\* so: `(0.15 + 0.14 + 0.12 + 0.12) / 4`
|
|
||||||
|
|
||||||
Long description:
|
|
||||||
|
|
||||||
`req/sec DIFF` and `Trans/sec DIFF` is in comparison to the baseline on target branch (master).
|
|
||||||
|
|
||||||
Req vs express is calculated as perf compared to NOT using nestjs so: 80.62% = (3.37/4.18) \* 100%
|
|
||||||
32
tools/benchmarks/src/autocannon/run.ts
Normal file
32
tools/benchmarks/src/autocannon/run.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import autocannon from 'autocannon';
|
||||||
|
|
||||||
|
export type RunOptions = autocannon.Options & {
|
||||||
|
/**
|
||||||
|
* When true, prints live progress to stdout via `autocannon.track(instance)`.
|
||||||
|
* Default: false (quiet)
|
||||||
|
*/
|
||||||
|
verbose?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs an autocannon benchmark.
|
||||||
|
*
|
||||||
|
* By default this is quiet (no live table/progress). Pass `{ verbose: true }`
|
||||||
|
* to enable `autocannon.track(...)` output.
|
||||||
|
*/
|
||||||
|
export const run = (options: RunOptions) =>
|
||||||
|
new Promise<autocannon.Result>((resolve, reject) => {
|
||||||
|
const { verbose = false, ...autocannonOptions } = options;
|
||||||
|
|
||||||
|
const instance = autocannon(autocannonOptions, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
autocannon.track(instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
9
tools/benchmarks/src/frameworks/express.ts
Normal file
9
tools/benchmarks/src/frameworks/express.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.get('/', async (_, res) => {
|
||||||
|
res.send('Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000);
|
||||||
14
tools/benchmarks/src/frameworks/fastify.ts
Normal file
14
tools/benchmarks/src/frameworks/fastify.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import Fastify from 'fastify';
|
||||||
|
|
||||||
|
const fastify = Fastify({
|
||||||
|
logger: false,
|
||||||
|
});
|
||||||
|
fastify.get('/', async (_, reply) => reply.send('Hello world'));
|
||||||
|
fastify
|
||||||
|
.listen({
|
||||||
|
port: 3000,
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
13
tools/benchmarks/src/frameworks/nest-express.ts
Normal file
13
tools/benchmarks/src/frameworks/nest-express.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { ExpressAdapter } from '@nestjs/platform-express';
|
||||||
|
|
||||||
|
import { AppModule } from './nest/app.module';
|
||||||
|
|
||||||
|
NestFactory.create(AppModule, new ExpressAdapter(), {
|
||||||
|
logger: false,
|
||||||
|
bodyParser: false,
|
||||||
|
})
|
||||||
|
.then(app => app.listen(3000))
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error starting Nest.js application:', error);
|
||||||
|
});
|
||||||
13
tools/benchmarks/src/frameworks/nest-fastify.ts
Normal file
13
tools/benchmarks/src/frameworks/nest-fastify.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
||||||
|
|
||||||
|
import { AppModule } from './nest/app.module';
|
||||||
|
|
||||||
|
NestFactory.create(AppModule, new FastifyAdapter(), {
|
||||||
|
logger: false,
|
||||||
|
bodyParser: false,
|
||||||
|
})
|
||||||
|
.then(app => app.listen(3000))
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error starting Nest.js application:', error);
|
||||||
|
});
|
||||||
9
tools/benchmarks/src/frameworks/nest/app.controller.ts
Normal file
9
tools/benchmarks/src/frameworks/nest/app.controller.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('/')
|
||||||
|
export class AppController {
|
||||||
|
@Get()
|
||||||
|
root() {
|
||||||
|
return 'Hello world!';
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tools/benchmarks/src/frameworks/nest/app.module.ts
Normal file
8
tools/benchmarks/src/frameworks/nest/app.module.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
controllers: [AppController],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
349
tools/benchmarks/src/main.ts
Normal file
349
tools/benchmarks/src/main.ts
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
import { fork, ChildProcess } from 'node:child_process';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { run } from './autocannon/run';
|
||||||
|
|
||||||
|
type Framework = 'express' | 'fastify' | 'nest-express' | 'nest-fastify';
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
connections: number;
|
||||||
|
duration: number;
|
||||||
|
pipelining: number;
|
||||||
|
verbose: boolean;
|
||||||
|
port: number;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BenchmarkSummary = {
|
||||||
|
requestsPerSecAvg: number;
|
||||||
|
latencyAvgMs: number;
|
||||||
|
latencyP99Ms: number;
|
||||||
|
throughputBytesPerSecAvg: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULTS: Args = {
|
||||||
|
connections: 100,
|
||||||
|
duration: 10,
|
||||||
|
pipelining: 10,
|
||||||
|
verbose: false,
|
||||||
|
port: 3000,
|
||||||
|
path: '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseNumber(value: string | undefined, fallback: number): number {
|
||||||
|
if (!value) return fallback;
|
||||||
|
const n = Number(value);
|
||||||
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv: string[]): Args {
|
||||||
|
const args: Args = { ...DEFAULTS };
|
||||||
|
const only: Framework[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i++) {
|
||||||
|
const token = argv[i];
|
||||||
|
|
||||||
|
if (token === '--verbose' || token === '-v') {
|
||||||
|
args.verbose = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--connections' || token === '-c') {
|
||||||
|
args.connections = parseNumber(argv[++i], args.connections);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--duration' || token === '-d') {
|
||||||
|
args.duration = parseNumber(argv[++i], args.duration);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--pipelining' || token === '-p') {
|
||||||
|
args.pipelining = parseNumber(argv[++i], args.pipelining);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--port') {
|
||||||
|
args.port = parseNumber(argv[++i], args.port);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--path') {
|
||||||
|
args.path = argv[++i] ?? args.path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '--help' || token === '-h') {
|
||||||
|
printHelpAndExit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHelpAndExit(code: number): never {
|
||||||
|
// Keep this simple so `npm run benchmarks` works without extra args.
|
||||||
|
// You can optionally narrow execution with `--framework` or `--only`.
|
||||||
|
// Example: `npm run benchmarks -- --framework fastify`
|
||||||
|
// Example: `npm run benchmarks -- --only express,fastify`
|
||||||
|
// Tuning: `--connections`, `--duration`, `--pipelining`, `--port`, `--path`
|
||||||
|
// Notes: The benchmark spawns the framework server as a child process and
|
||||||
|
// runs autocannon against it.
|
||||||
|
//
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`
|
||||||
|
Usage:
|
||||||
|
npm run benchmarks [-- --verbose]
|
||||||
|
npm run benchmarks [-- --connections 100] [-- --duration 10] [-- --pipelining 10]
|
||||||
|
npm run benchmarks [-- --port 3000] [-- --path /]
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
verbose: ${DEFAULTS.verbose}
|
||||||
|
connections: ${DEFAULTS.connections}
|
||||||
|
duration: ${DEFAULTS.duration}
|
||||||
|
pipelining: ${DEFAULTS.pipelining}
|
||||||
|
port: ${DEFAULTS.port}
|
||||||
|
path: ${DEFAULTS.path}
|
||||||
|
`.trim(),
|
||||||
|
);
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function frameworkEntry(framework: Framework): string {
|
||||||
|
return join(__dirname, '.', 'frameworks', framework);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForServer(url: string, timeoutMs = 5_000): Promise<void> {
|
||||||
|
const start = Date.now();
|
||||||
|
// Use global fetch if available (Node 18+). If not, just wait a bit.
|
||||||
|
const hasFetch = typeof (globalThis as any).fetch === 'function';
|
||||||
|
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
try {
|
||||||
|
if (hasFetch) {
|
||||||
|
const res = await (globalThis as any).fetch(url, { method: 'GET' });
|
||||||
|
if (res && (res.status === 200 || res.status === 404)) return;
|
||||||
|
} else {
|
||||||
|
// best-effort fallback
|
||||||
|
await sleep(250);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// server not ready yet
|
||||||
|
}
|
||||||
|
await sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Server did not become ready in ${timeoutMs}ms: ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function killChild(child: ChildProcess): void {
|
||||||
|
if (!child || child.killed) return;
|
||||||
|
|
||||||
|
// Try graceful termination first.
|
||||||
|
try {
|
||||||
|
child.kill('SIGTERM');
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force kill if still alive shortly after.
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
if (!child.killed) child.kill('SIGKILL');
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, 1_000).unref?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value: unknown, fallback = 0): number {
|
||||||
|
const n = typeof value === 'number' ? value : Number(value);
|
||||||
|
return Number.isFinite(n) ? n : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeResult(result: any): BenchmarkSummary {
|
||||||
|
return {
|
||||||
|
requestsPerSecAvg: toNumber(result?.requests?.average),
|
||||||
|
latencyAvgMs: toNumber(result?.latency?.average),
|
||||||
|
latencyP99Ms: toNumber(result?.latency?.p99),
|
||||||
|
throughputBytesPerSecAvg: toNumber(result?.throughput?.average),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pctChange(newValue: number, oldValue: number): number | null {
|
||||||
|
if (
|
||||||
|
!Number.isFinite(newValue) ||
|
||||||
|
!Number.isFinite(oldValue) ||
|
||||||
|
oldValue === 0
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
return ((newValue - oldValue) / oldValue) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtPct(p: number | null, digits = 1): string {
|
||||||
|
if (p == null) return 'n/a';
|
||||||
|
const sign = p >= 0 ? '+' : '';
|
||||||
|
return `${sign}${p.toFixed(digits)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtNum(n: number, digits = 2): string {
|
||||||
|
if (!Number.isFinite(n)) return 'n/a';
|
||||||
|
return n.toFixed(digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtBytesPerSec(n: number): string {
|
||||||
|
if (!Number.isFinite(n)) return 'n/a';
|
||||||
|
const abs = Math.abs(n);
|
||||||
|
if (abs >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB/s`;
|
||||||
|
if (abs >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(2)} MB/s`;
|
||||||
|
if (abs >= 1024) return `${(n / 1024).toFixed(2)} KB/s`;
|
||||||
|
return `${n.toFixed(0)} B/s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printComparison(
|
||||||
|
baseName: string,
|
||||||
|
base: BenchmarkSummary | undefined,
|
||||||
|
nestName: string,
|
||||||
|
nest: BenchmarkSummary | undefined,
|
||||||
|
): void {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`\n--- Comparison: ${baseName} <-> ${nestName} ---`);
|
||||||
|
|
||||||
|
if (!base || !nest) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Missing results: ${baseName}=${base ? 'ok' : 'missing'} ${nestName}=${nest ? 'ok' : 'missing'}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpsPct = pctChange(nest.requestsPerSecAvg, base.requestsPerSecAvg);
|
||||||
|
const latAvgPct = pctChange(nest.latencyAvgMs, base.latencyAvgMs);
|
||||||
|
const latP99Pct = pctChange(nest.latencyP99Ms, base.latencyP99Ms);
|
||||||
|
const thrPct = pctChange(
|
||||||
|
nest.throughputBytesPerSecAvg,
|
||||||
|
base.throughputBytesPerSecAvg,
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Requests/sec avg: ${fmtNum(base.requestsPerSecAvg)} -> ${fmtNum(nest.requestsPerSecAvg)} (${fmtPct(rpsPct)})`,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Latency avg (ms): ${fmtNum(base.latencyAvgMs)} -> ${fmtNum(nest.latencyAvgMs)} (${fmtPct(latAvgPct)})`,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Latency p99 (ms): ${fmtNum(base.latencyP99Ms)} -> ${fmtNum(nest.latencyP99Ms)} (${fmtPct(latP99Pct)})`,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Throughput avg: ${fmtBytesPerSec(base.throughputBytesPerSecAvg)} -> ${fmtBytesPerSec(nest.throughputBytesPerSecAvg)} (${fmtPct(thrPct)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runOne(
|
||||||
|
framework: Framework,
|
||||||
|
args: Args,
|
||||||
|
): Promise<BenchmarkSummary> {
|
||||||
|
const child = fork(frameworkEntry(framework), {
|
||||||
|
stdio: 'ignore',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `http://localhost:${args.port}${args.path.startsWith('/') ? args.path : `/${args.path}`}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await waitForServer(url);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`\n=== ${framework.toUpperCase()} ===`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Target: ${url}`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`connections=${args.connections} duration=${args.duration} pipelining=${args.pipelining}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Warmup: ${framework.toUpperCase()}`);
|
||||||
|
await run({
|
||||||
|
url,
|
||||||
|
connections: args.connections,
|
||||||
|
duration: args.duration,
|
||||||
|
pipelining: args.pipelining,
|
||||||
|
verbose: args.verbose,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Warmup ended: ${framework.toUpperCase()}`);
|
||||||
|
|
||||||
|
const result = (await run({
|
||||||
|
url,
|
||||||
|
connections: args.connections,
|
||||||
|
duration: args.duration,
|
||||||
|
pipelining: args.pipelining,
|
||||||
|
verbose: args.verbose,
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
const summary = summarizeResult(result);
|
||||||
|
|
||||||
|
// Keep output readable and stable across autocannon versions.
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Requests/sec: ${summary.requestsPerSecAvg}`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Latency (ms): avg=${summary.latencyAvgMs} p99=${summary.latencyP99Ms}`,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Throughput (B/s): avg=${summary.throughputBytesPerSecAvg}`);
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
} finally {
|
||||||
|
killChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const args = parseArgs(process.argv.slice(2));
|
||||||
|
|
||||||
|
const targets: Framework[] = [
|
||||||
|
'express',
|
||||||
|
'nest-express',
|
||||||
|
'fastify',
|
||||||
|
'nest-fastify',
|
||||||
|
];
|
||||||
|
|
||||||
|
const results: Partial<Record<Framework, BenchmarkSummary>> = {};
|
||||||
|
|
||||||
|
// Run sequentially to avoid port conflicts (both frameworks listen on the same port).
|
||||||
|
for (const fw of targets) {
|
||||||
|
results[fw] = await runOne(fw, args);
|
||||||
|
// small cooldown between runs
|
||||||
|
await sleep(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare raw framework vs Nest adapter for same underlying HTTP server
|
||||||
|
printComparison(
|
||||||
|
'EXPRESS',
|
||||||
|
results['express'],
|
||||||
|
'NEST-EXPRESS',
|
||||||
|
results['nest-express'],
|
||||||
|
);
|
||||||
|
printComparison(
|
||||||
|
'FASTIFY',
|
||||||
|
results['fastify'],
|
||||||
|
'NEST-FASTIFY',
|
||||||
|
results['nest-fastify'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
21
tools/benchmarks/tsconfig.json
Normal file
21
tools/benchmarks/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/tsconfig",
|
||||||
|
"_version": "18.2.0",
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2023"],
|
||||||
|
"module": "node16",
|
||||||
|
"target": "es2022",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "node16",
|
||||||
|
|
||||||
|
"outDir": "dist",
|
||||||
|
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user