thunderbench 1.0.7 → 1.0.9
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/README.md +98 -7
- package/dist/chunk-CWMRHHAH.mjs +114 -0
- package/dist/chunk-SQP24Z4Q.mjs +1607 -0
- package/dist/cli.mjs +1022 -0
- package/dist/config-validation-Y5UUDC5O.mjs +6 -0
- package/dist/core/comparison-report.d.ts +67 -0
- package/dist/core/comparison-report.d.ts.map +1 -0
- package/dist/core/comparison-runner.d.ts +176 -0
- package/dist/core/comparison-runner.d.ts.map +1 -0
- package/dist/core/server-manager.d.ts +166 -0
- package/dist/core/server-manager.d.ts.map +1 -0
- package/dist/core/wrk-test-engine.d.ts +1 -0
- package/dist/core/wrk-test-engine.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +869 -0
- package/dist/utils/wrk-binary.d.ts +1 -0
- package/dist/utils/wrk-binary.d.ts.map +1 -1
- package/examples/README.md +97 -0
- package/examples/comparison/framework-comparison.ts +141 -0
- package/examples/{complex-config.ts → configs/complex-config.ts} +1 -1
- package/examples/{complex-wrk-demo.ts → configs/complex-wrk-demo.ts} +1 -1
- package/examples/{simple-wrk-config.js → configs/simple-config.ts} +15 -7
- package/examples/servers/elysia-server.ts +53 -0
- package/examples/servers/express-server.ts +65 -0
- package/examples/servers/hono-server.ts +63 -0
- package/examples/servers/vafast-server.ts +82 -0
- package/examples/{programmatic-usage.js → usage/programmatic-usage.ts} +73 -62
- package/package.json +15 -8
- package/scripts/{setup-wrk.js → setup-wrk.ts} +52 -46
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/index.js +0 -10494
- package/examples/programmatic-usage-cjs.js +0 -156
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
- **🌐 跨平台**:内置跨平台 WRK 二进制文件,开箱即用
|
|
26
26
|
- **📈 丰富报告**:支持 JSON、Markdown 等多种报告格式,提供详细的性能分析
|
|
27
27
|
- **🔄 流式处理**:基于 RxJS 的响应式数据流处理
|
|
28
|
+
- **🏆 框架对比**:内置框架横向对比测试,自动启停服务器、生成排名报告
|
|
28
29
|
|
|
29
30
|
## 🚀 快速开始
|
|
30
31
|
|
|
@@ -259,23 +260,113 @@ try {
|
|
|
259
260
|
}
|
|
260
261
|
```
|
|
261
262
|
|
|
262
|
-
### 4.
|
|
263
|
+
### 4. 框架对比测试 🆕
|
|
264
|
+
|
|
265
|
+
ThunderBench 支持自动化框架性能对比测试,自动管理服务器生命周期:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import {
|
|
269
|
+
runComparison,
|
|
270
|
+
generateComparisonReport,
|
|
271
|
+
ServerConfig,
|
|
272
|
+
ComparisonTestConfig,
|
|
273
|
+
} from 'thunderbench';
|
|
274
|
+
|
|
275
|
+
// 定义要对比的框架
|
|
276
|
+
const servers: ServerConfig[] = [
|
|
277
|
+
{
|
|
278
|
+
name: "Vafast",
|
|
279
|
+
command: "bun",
|
|
280
|
+
args: ["run", "servers/vafast-server.ts"],
|
|
281
|
+
port: 3001,
|
|
282
|
+
healthCheckPath: "/health",
|
|
283
|
+
warmupRequests: 100,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "Express",
|
|
287
|
+
command: "node",
|
|
288
|
+
args: ["servers/express-server.js"],
|
|
289
|
+
port: 3002,
|
|
290
|
+
healthCheckPath: "/health",
|
|
291
|
+
warmupRequests: 100,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "Hono",
|
|
295
|
+
command: "bun",
|
|
296
|
+
args: ["run", "servers/hono-server.ts"],
|
|
297
|
+
port: 3003,
|
|
298
|
+
healthCheckPath: "/health",
|
|
299
|
+
warmupRequests: 100,
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
// 定义测试配置
|
|
304
|
+
const testConfig: ComparisonTestConfig = {
|
|
305
|
+
name: "Web 框架性能对比",
|
|
306
|
+
threads: 4,
|
|
307
|
+
connections: 100,
|
|
308
|
+
duration: 30,
|
|
309
|
+
scenarios: [
|
|
310
|
+
{ name: "Hello World", method: "GET", path: "/", weight: 40 },
|
|
311
|
+
{ name: "JSON API", method: "GET", path: "/api/users", weight: 30 },
|
|
312
|
+
{ name: "动态路由", method: "GET", path: "/api/users/123", weight: 20 },
|
|
313
|
+
{ name: "POST JSON", method: "POST", path: "/api/users",
|
|
314
|
+
headers: { "Content-Type": "application/json" },
|
|
315
|
+
body: { name: "Test" }, weight: 10 },
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// 运行对比测试(自动启停服务器)
|
|
320
|
+
const result = await runComparison(servers, testConfig);
|
|
321
|
+
|
|
322
|
+
// 生成对比报告
|
|
323
|
+
await generateComparisonReport(result, {
|
|
324
|
+
formats: ["markdown", "json"],
|
|
325
|
+
outputDir: "./comparison-reports",
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### 对比报告示例
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
🏆 框架性能对比结果
|
|
333
|
+
═══════════════════════════════════════════════════════
|
|
334
|
+
|
|
335
|
+
排名 | 框架 | RPS | 延迟(P99) | 相对性能
|
|
336
|
+
─────────────────────────────────────────────────────────
|
|
337
|
+
🥇 1 | Vafast | 120,000 | 2.5ms | 100%
|
|
338
|
+
🥈 2 | Hono | 115,000 | 2.8ms | 96%
|
|
339
|
+
🥉 3 | Elysia | 98,000 | 3.2ms | 82%
|
|
340
|
+
4 | Express | 45,000 | 8.2ms | 38%
|
|
341
|
+
─────────────────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
📈 Vafast 比 Express 快 2.67x
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 5. 命令行使用
|
|
263
347
|
|
|
264
348
|
```bash
|
|
265
349
|
# 安装 CLI 工具
|
|
266
|
-
npm install -g thunderbench
|
|
350
|
+
npm install -g thunderbench-cli
|
|
267
351
|
|
|
268
|
-
#
|
|
269
|
-
thunderbench --config
|
|
352
|
+
# 运行单个测试
|
|
353
|
+
thunderbench run --config test-config.ts
|
|
354
|
+
|
|
355
|
+
# 运行框架对比测试
|
|
356
|
+
thunderbench compare --config comparison-config.ts
|
|
270
357
|
|
|
271
358
|
# 详细输出模式
|
|
272
|
-
thunderbench --config
|
|
359
|
+
thunderbench run --config test-config.ts --verbose
|
|
273
360
|
|
|
274
361
|
# 自定义输出目录
|
|
275
|
-
thunderbench --config
|
|
362
|
+
thunderbench run --config test-config.ts --output ./my-reports
|
|
276
363
|
|
|
277
364
|
# 配置验证(不执行测试)
|
|
278
|
-
thunderbench --config
|
|
365
|
+
thunderbench run --config test-config.ts --dry-run
|
|
366
|
+
|
|
367
|
+
# 创建示例配置
|
|
368
|
+
thunderbench create-config --type single
|
|
369
|
+
thunderbench create-config --type comparison
|
|
279
370
|
```
|
|
280
371
|
|
|
281
372
|
## 📊 报告格式
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// src/core/config-validation.ts
|
|
34
|
+
var validateConfig = (config) => {
|
|
35
|
+
if (!config.groups || config.groups.length === 0) {
|
|
36
|
+
throw new Error("\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6D4B\u8BD5\u7EC4");
|
|
37
|
+
}
|
|
38
|
+
config.groups.forEach((group) => {
|
|
39
|
+
validateGroup(group);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
var validateGroup = (group) => {
|
|
43
|
+
if (!group.name || group.name.trim() === "") {
|
|
44
|
+
throw new Error("\u6D4B\u8BD5\u7EC4\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
45
|
+
}
|
|
46
|
+
if (group.executionMode !== "parallel" && group.executionMode !== "sequential") {
|
|
47
|
+
throw new Error('\u6267\u884C\u6A21\u5F0F\u5FC5\u987B\u662F "parallel" \u6216 "sequential"');
|
|
48
|
+
}
|
|
49
|
+
if (group.delay !== void 0 && group.delay < 0) {
|
|
50
|
+
throw new Error("\u5EF6\u8FDF\u65F6\u95F4\u4E0D\u80FD\u4E3A\u8D1F\u6570");
|
|
51
|
+
}
|
|
52
|
+
if (group.http) {
|
|
53
|
+
validateHttpConfig(group.http);
|
|
54
|
+
}
|
|
55
|
+
if (!group.tests || group.tests.length === 0) {
|
|
56
|
+
throw new Error(`\u6D4B\u8BD5\u7EC4 "${group.name}" \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6D4B\u8BD5\u63A5\u53E3`);
|
|
57
|
+
}
|
|
58
|
+
group.tests.forEach((test) => {
|
|
59
|
+
validateTest(test, group.name);
|
|
60
|
+
});
|
|
61
|
+
if (group.threads <= 0) {
|
|
62
|
+
throw new Error("\u7EBF\u7A0B\u6570\u5FC5\u987B\u5927\u4E8E0");
|
|
63
|
+
}
|
|
64
|
+
if (group.connections <= 0) {
|
|
65
|
+
throw new Error("\u8FDE\u63A5\u6570\u5FC5\u987B\u5927\u4E8E0");
|
|
66
|
+
}
|
|
67
|
+
if (group.duration === void 0 || group.duration === null) {
|
|
68
|
+
throw new Error(`\u6D4B\u8BD5\u7EC4 "${group.name}" \u5FC5\u987B\u6307\u5B9A duration`);
|
|
69
|
+
}
|
|
70
|
+
if (group.duration <= 0) {
|
|
71
|
+
throw new Error("\u6D4B\u8BD5\u65F6\u957F\u5FC5\u987B\u5927\u4E8E0");
|
|
72
|
+
}
|
|
73
|
+
validateWeights(group.tests, group.name);
|
|
74
|
+
};
|
|
75
|
+
var validateTest = (test, groupName) => {
|
|
76
|
+
if (!test.name || test.name.trim() === "") {
|
|
77
|
+
throw new Error("\u6D4B\u8BD5\u63A5\u53E3\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
78
|
+
}
|
|
79
|
+
if (!test.request) {
|
|
80
|
+
throw new Error(`\u6D4B\u8BD5\u63A5\u53E3 "${test.name}" \u7F3A\u5C11\u8BF7\u6C42\u914D\u7F6E`);
|
|
81
|
+
}
|
|
82
|
+
if (!test.request.method) {
|
|
83
|
+
throw new Error(`\u6D4B\u8BD5\u63A5\u53E3 "${test.name}" \u7F3A\u5C11\u8BF7\u6C42\u65B9\u6CD5`);
|
|
84
|
+
}
|
|
85
|
+
if (!test.request.url) {
|
|
86
|
+
throw new Error(`\u6D4B\u8BD5\u63A5\u53E3 "${test.name}" \u7F3A\u5C11\u8BF7\u6C42URL`);
|
|
87
|
+
}
|
|
88
|
+
if (test.weight < 0 || test.weight > 100) {
|
|
89
|
+
throw new Error(`\u6D4B\u8BD5\u63A5\u53E3 "${test.name}" \u6743\u91CD\u5FC5\u987B\u57280-100\u4E4B\u95F4\uFF0C\u5F53\u524D: ${test.weight}`);
|
|
90
|
+
}
|
|
91
|
+
if (test.errorHandling) {
|
|
92
|
+
if (test.errorHandling.expectMaxResponseTime <= 0) {
|
|
93
|
+
throw new Error("\u671F\u671B\u6700\u5927\u54CD\u5E94\u65F6\u95F4\u5FC5\u987B\u5927\u4E8E0");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var validateHttpConfig = (httpConfig) => {
|
|
98
|
+
if (httpConfig.timeout !== void 0 && httpConfig.timeout < 0) {
|
|
99
|
+
throw new Error("HTTP\u8D85\u65F6\u65F6\u95F4\u4E0D\u80FD\u4E3A\u8D1F\u6570");
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var validateWeights = (tests, groupName) => {
|
|
103
|
+
const totalWeight = tests.reduce((sum, test) => sum + test.weight, 0);
|
|
104
|
+
if (Math.abs(totalWeight - 100) > 0.01) {
|
|
105
|
+
throw new Error(`\u6D4B\u8BD5\u7EC4 "${groupName}" \u6743\u91CD\u603B\u548C\u5FC5\u987B\u4E3A100\uFF0C\u5F53\u524D\u4E3A${totalWeight}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
__require,
|
|
111
|
+
__commonJS,
|
|
112
|
+
__toESM,
|
|
113
|
+
validateConfig
|
|
114
|
+
};
|