thunderbench 1.0.7 → 1.0.10
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 +104 -13
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +204 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/config-validation-BFeqCCo5.mjs +65 -0
- package/dist/config-validation-BFeqCCo5.mjs.map +1 -0
- package/dist/config-validation-sY5Ckxfp.mjs +3 -0
- package/dist/index.d.mts +875 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +827 -0
- package/dist/index.mjs.map +1 -0
- package/dist/wrk-test-engine-D_Ny_7mI.mjs +1321 -0
- package/dist/wrk-test-engine-D_Ny_7mI.mjs.map +1 -0
- 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 +79 -0
- package/examples/{programmatic-usage.js → usage/programmatic-usage.ts} +73 -62
- package/package.json +16 -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/core/config-validation.d.ts +0 -7
- package/dist/core/config-validation.d.ts.map +0 -1
- package/dist/core/json-report-generator.d.ts +0 -174
- package/dist/core/json-report-generator.d.ts.map +0 -1
- package/dist/core/markdown-report-generator.d.ts +0 -16
- package/dist/core/markdown-report-generator.d.ts.map +0 -1
- package/dist/core/report-storage.d.ts +0 -10
- package/dist/core/report-storage.d.ts.map +0 -1
- package/dist/core/stats-calculation.d.ts +0 -16
- package/dist/core/stats-calculation.d.ts.map +0 -1
- package/dist/core/weight-distribution.d.ts +0 -9
- package/dist/core/weight-distribution.d.ts.map +0 -1
- package/dist/core/wrk-test-engine.d.ts +0 -181
- package/dist/core/wrk-test-engine.d.ts.map +0 -1
- package/dist/index.d.ts +0 -39
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -10494
- package/dist/types/index.d.ts +0 -88
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/wrk-binary.d.ts +0 -41
- package/dist/utils/wrk-binary.d.ts.map +0 -1
- 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
|
|
|
@@ -38,7 +39,7 @@ npm install thunderbench
|
|
|
38
39
|
yarn add thunderbench
|
|
39
40
|
|
|
40
41
|
# 使用 bun
|
|
41
|
-
|
|
42
|
+
npm install thunderbench
|
|
42
43
|
|
|
43
44
|
# 使用 pnpm
|
|
44
45
|
pnpm add thunderbench
|
|
@@ -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
|
## 📊 报告格式
|
|
@@ -657,19 +748,19 @@ git clone https://github.com/thunderbench/thunderbench.git
|
|
|
657
748
|
cd thunderbench
|
|
658
749
|
|
|
659
750
|
# 安装依赖
|
|
660
|
-
|
|
751
|
+
npm install
|
|
661
752
|
|
|
662
753
|
# 开发模式
|
|
663
|
-
|
|
754
|
+
npm run dev
|
|
664
755
|
|
|
665
756
|
# 构建项目
|
|
666
|
-
|
|
757
|
+
npm run build
|
|
667
758
|
|
|
668
759
|
# 运行测试
|
|
669
|
-
|
|
760
|
+
npm run test
|
|
670
761
|
|
|
671
762
|
# 类型检查
|
|
672
|
-
|
|
763
|
+
npm run type-check
|
|
673
764
|
```
|
|
674
765
|
|
|
675
766
|
### 项目结构
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as TestEngine } from "./wrk-test-engine-D_Ny_7mI.mjs";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
|
|
9
|
+
//#region src/cli.ts
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program.name("thunderbench").description("高性能API性能测试工具,基于WRK引擎").version("1.0.0").option("-c, --config <path>", "配置文件路径", "./examples/test-config.ts").option("-v, --verbose", "详细输出模式").option("--no-report", "不生成报告文件").option("--no-progress", "不显示实时进度").option("-o, --output <dir>", "报告输出目录", "./reports").option("--timeout <ms>", "全局超时时间(毫秒)", "30000").option("--concurrent <number>", "全局并发数覆盖", "10").option("--dry-run", "仅验证配置,不执行测试").option("--list-examples", "列出示例配置文件").option("--create-example", "创建示例配置文件").option("--cleanup-wrk", "测试完成后清理 wrk 脚本文件");
|
|
12
|
+
program.parse(process.argv);
|
|
13
|
+
const options = program.opts();
|
|
14
|
+
async function main() {
|
|
15
|
+
try {
|
|
16
|
+
console.log(chalk.blue.bold("\nThunderBench - 高性能API性能测试工具"));
|
|
17
|
+
console.log(chalk.gray("版本 1.0.0 | 基于内置 WRK 引擎\n"));
|
|
18
|
+
if (options.listExamples) {
|
|
19
|
+
await listExamples();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (options.createExample) {
|
|
23
|
+
await createExampleConfig();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const configPath = path.resolve(options.config);
|
|
27
|
+
console.log(chalk.blue(`配置文件: ${configPath}`));
|
|
28
|
+
if (!await fileExists(configPath)) {
|
|
29
|
+
console.error(chalk.red(`配置文件不存在: ${configPath}`));
|
|
30
|
+
console.log(chalk.yellow("使用 --create-example 创建示例配置文件"));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const spinner = ora("加载配置文件...").start();
|
|
34
|
+
let config;
|
|
35
|
+
try {
|
|
36
|
+
const configModule = await import(configPath);
|
|
37
|
+
config = configModule.default || configModule.config;
|
|
38
|
+
spinner.succeed("配置文件加载成功");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail("配置文件加载失败");
|
|
41
|
+
console.error(chalk.red("错误详情:"), error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
if (options.timeout) {
|
|
45
|
+
const timeout = parseInt(options.timeout);
|
|
46
|
+
config.groups.forEach((group) => {
|
|
47
|
+
if (group.http) group.http.timeout = timeout;
|
|
48
|
+
});
|
|
49
|
+
console.log(chalk.yellow(`全局超时时间设置为: ${timeout}ms`));
|
|
50
|
+
}
|
|
51
|
+
if (options.concurrent && options.concurrent !== "10") {
|
|
52
|
+
const concurrent = parseInt(options.concurrent);
|
|
53
|
+
config.groups.forEach((group) => {
|
|
54
|
+
group.threads = Math.min(12, Math.ceil(concurrent / 10));
|
|
55
|
+
group.connections = concurrent;
|
|
56
|
+
});
|
|
57
|
+
console.log(chalk.yellow(`全局并发数设置为: ${concurrent}`));
|
|
58
|
+
}
|
|
59
|
+
if (options.dryRun) {
|
|
60
|
+
console.log(chalk.blue("\n干运行模式 - 仅验证配置"));
|
|
61
|
+
const { validateConfig } = await import("./config-validation-sY5Ckxfp.mjs");
|
|
62
|
+
validateConfig(config);
|
|
63
|
+
console.log(chalk.green("配置验证通过!"));
|
|
64
|
+
console.log(chalk.blue("使用 --no-dry-run 执行实际测试"));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.blue("使用内置 wrk 版本..."));
|
|
68
|
+
const engine = new TestEngine(config, {
|
|
69
|
+
outputDir: options.output,
|
|
70
|
+
cleanupScripts: options.cleanupWrk,
|
|
71
|
+
showProgress: options.progress !== false,
|
|
72
|
+
verbose: options.verbose
|
|
73
|
+
});
|
|
74
|
+
if (options.progress !== false) setupProgressMonitoring(engine, options.verbose);
|
|
75
|
+
console.log(chalk.blue("\n开始执行基准测试...\n"));
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
displayResultsSummary(await engine.runBenchmark(), (Date.now() - startTime) / 1e3);
|
|
78
|
+
engine.destroy();
|
|
79
|
+
console.log(chalk.green("\n测试完成!"));
|
|
80
|
+
if (options.report !== false) console.log(chalk.blue("详细报告已保存到 reports/ 目录"));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(chalk.red("\n测试执行失败:"));
|
|
83
|
+
console.error(error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 设置进度监控
|
|
89
|
+
*/
|
|
90
|
+
function setupProgressMonitoring(engine, verbose) {
|
|
91
|
+
engine.getProgressStream().subscribe((progress) => {
|
|
92
|
+
const percentage = progress.percentage;
|
|
93
|
+
const color = percentage === 100 ? chalk.green : chalk.blue;
|
|
94
|
+
if (verbose) {
|
|
95
|
+
const currentTest = progress.currentTest ? ` | ${progress.currentTest}` : "";
|
|
96
|
+
console.log(color(`${progress.groupName}: ${percentage}% (${progress.completed}/${progress.total})${currentTest}`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
engine.getStatsStream().subscribe((stats) => {
|
|
100
|
+
if (verbose) {
|
|
101
|
+
const successRate = stats.totalRequests > 0 ? (stats.successfulRequests / stats.totalRequests * 100).toFixed(1) : "0.0";
|
|
102
|
+
console.log(chalk.cyan(`实时: ${stats.totalRequests.toLocaleString()} 请求 | ${successRate}% 成功 | 平均 ${stats.averageResponseTime}ms | ${stats.requestsPerSecond.toFixed(1)} req/s`));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 显示结果摘要
|
|
108
|
+
*/
|
|
109
|
+
function displayResultsSummary(result, totalTime) {
|
|
110
|
+
const stats = result.overallStats;
|
|
111
|
+
const successRate = (stats.successfulRequests / stats.totalRequests * 100).toFixed(1);
|
|
112
|
+
console.log(chalk.blue("\n" + "=".repeat(60)));
|
|
113
|
+
console.log(chalk.blue.bold("测试结果摘要"));
|
|
114
|
+
console.log(chalk.blue("=".repeat(60)));
|
|
115
|
+
console.log(chalk.white(`总耗时: ${totalTime.toFixed(2)} 秒`));
|
|
116
|
+
console.log(chalk.white(`总请求数: ${stats.totalRequests.toLocaleString()}`));
|
|
117
|
+
console.log(chalk.green(`成功: ${stats.successfulRequests.toLocaleString()} (${successRate}%)`));
|
|
118
|
+
console.log(chalk.red(`失败: ${stats.failedRequests.toLocaleString()} (${(stats.errorRate * 100).toFixed(1)}%)`));
|
|
119
|
+
console.log(chalk.yellow(`平均响应时间: ${stats.averageResponseTime} ms`));
|
|
120
|
+
console.log(chalk.yellow(`P95响应时间: ${stats.p95ResponseTime} ms`));
|
|
121
|
+
console.log(chalk.cyan(`吞吐量: ${stats.requestsPerSecond.toFixed(1)} req/s`));
|
|
122
|
+
if (result.groups.length > 1) {
|
|
123
|
+
console.log(chalk.blue("\n测试组详情:"));
|
|
124
|
+
result.groups.forEach((group) => {
|
|
125
|
+
const groupStats = group.stats;
|
|
126
|
+
const groupSuccessRate = (groupStats.successfulRequests / groupStats.totalRequests * 100).toFixed(1);
|
|
127
|
+
const color = groupSuccessRate === "100.0" ? chalk.green : chalk.yellow;
|
|
128
|
+
console.log(color(` ${group.name}: ${groupStats.totalRequests} 请求, ${groupSuccessRate}% 成功, 平均 ${groupStats.averageResponseTime}ms`));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk.blue("=".repeat(60)));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 列出示例配置文件
|
|
135
|
+
*/
|
|
136
|
+
async function listExamples() {
|
|
137
|
+
console.log(chalk.blue("\n可用的示例配置文件:"));
|
|
138
|
+
console.log(chalk.blue("=".repeat(50)));
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
name: "simple-config.ts",
|
|
142
|
+
desc: "简单API测试配置"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "complex-config.ts",
|
|
146
|
+
desc: "复杂场景配置(多组、混合模式)"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "rest-api-config.ts",
|
|
150
|
+
desc: "REST API完整测试配置"
|
|
151
|
+
}
|
|
152
|
+
].forEach((example, index) => {
|
|
153
|
+
console.log(chalk.white(`${index + 1}. ${example.name}`));
|
|
154
|
+
console.log(chalk.gray(` ${example.desc}`));
|
|
155
|
+
console.log();
|
|
156
|
+
});
|
|
157
|
+
console.log(chalk.yellow("使用 --create-example 创建这些配置文件"));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 创建示例配置文件
|
|
161
|
+
*/
|
|
162
|
+
async function createExampleConfig() {
|
|
163
|
+
console.log(chalk.blue("\n创建示例配置文件..."));
|
|
164
|
+
const examplesDir = path.join(process.cwd(), "examples");
|
|
165
|
+
const targetDir = process.cwd();
|
|
166
|
+
try {
|
|
167
|
+
for (const file of [
|
|
168
|
+
"simple-config.ts",
|
|
169
|
+
"complex-config.ts",
|
|
170
|
+
"rest-api-config.ts"
|
|
171
|
+
]) {
|
|
172
|
+
const sourcePath = path.join(examplesDir, file);
|
|
173
|
+
const targetPath = path.join(targetDir, file);
|
|
174
|
+
if (await fileExists(sourcePath)) {
|
|
175
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
176
|
+
console.log(chalk.green(`创建: ${file}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
console.log(chalk.green("\n示例配置文件创建完成!"));
|
|
180
|
+
console.log(chalk.blue("现在你可以修改这些文件并运行测试"));
|
|
181
|
+
console.log(chalk.blue(" 例如: thunderbench --config simple-config.ts"));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error(chalk.red("创建示例配置文件失败:"), error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 检查文件是否存在
|
|
188
|
+
*/
|
|
189
|
+
async function fileExists(filePath) {
|
|
190
|
+
try {
|
|
191
|
+
await fs.access(filePath);
|
|
192
|
+
return true;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
main().catch((error) => {
|
|
198
|
+
console.error(chalk.red("程序执行失败:"), error);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
export { };
|
|
204
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { TestEngine } from \"./core/wrk-test-engine\";\nimport { BenchmarkConfig } from \"./types\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\n\nconst program = new Command();\n\nprogram\n .name(\"thunderbench\")\n .description(\"高性能API性能测试工具,基于WRK引擎\")\n .version(\"1.0.0\")\n .option(\"-c, --config <path>\", \"配置文件路径\", \"./examples/test-config.ts\")\n .option(\"-v, --verbose\", \"详细输出模式\")\n .option(\"--no-report\", \"不生成报告文件\")\n .option(\"--no-progress\", \"不显示实时进度\")\n .option(\"-o, --output <dir>\", \"报告输出目录\", \"./reports\")\n .option(\"--timeout <ms>\", \"全局超时时间(毫秒)\", \"30000\")\n .option(\"--concurrent <number>\", \"全局并发数覆盖\", \"10\")\n .option(\"--dry-run\", \"仅验证配置,不执行测试\")\n .option(\"--list-examples\", \"列出示例配置文件\")\n .option(\"--create-example\", \"创建示例配置文件\")\n\n .option(\"--cleanup-wrk\", \"测试完成后清理 wrk 脚本文件\");\n\nprogram.parse(process.argv);\n\nconst options = program.opts();\n\nasync function main() {\n try {\n // 显示欢迎信息\n console.log(chalk.blue.bold(\"\\nThunderBench - 高性能API性能测试工具\"));\n console.log(chalk.gray(\"版本 1.0.0 | 基于内置 WRK 引擎\\n\"));\n\n // 处理特殊选项\n if (options.listExamples) {\n await listExamples();\n return;\n }\n\n if (options.createExample) {\n await createExampleConfig();\n return;\n }\n\n // 验证配置文件\n const configPath = path.resolve(options.config);\n console.log(chalk.blue(`配置文件: ${configPath}`));\n\n if (!(await fileExists(configPath))) {\n console.error(chalk.red(`配置文件不存在: ${configPath}`));\n console.log(chalk.yellow(\"使用 --create-example 创建示例配置文件\"));\n process.exit(1);\n }\n\n // 加载配置\n const spinner = ora(\"加载配置文件...\").start();\n let config: BenchmarkConfig;\n\n try {\n const configModule = await import(configPath);\n config = configModule.default || configModule.config;\n spinner.succeed(\"配置文件加载成功\");\n } catch (error) {\n spinner.fail(\"配置文件加载失败\");\n console.error(chalk.red(\"错误详情:\"), error);\n process.exit(1);\n }\n\n // 应用全局选项覆盖\n if (options.timeout) {\n const timeout = parseInt(options.timeout);\n config.groups.forEach((group) => {\n if (group.http) {\n group.http.timeout = timeout;\n }\n });\n console.log(chalk.yellow(`全局超时时间设置为: ${timeout}ms`));\n }\n\n if (options.concurrent && options.concurrent !== \"10\") {\n const concurrent = parseInt(options.concurrent);\n config.groups.forEach((group) => {\n // 将全局并发数转换为线程数和连接数\n group.threads = Math.min(12, Math.ceil(concurrent / 10)); // 每线程最多10个连接\n group.connections = concurrent;\n });\n console.log(chalk.yellow(`全局并发数设置为: ${concurrent}`));\n }\n\n // 干运行模式\n if (options.dryRun) {\n console.log(chalk.blue(\"\\n干运行模式 - 仅验证配置\"));\n const { validateConfig } = await import(\"./core/config-validation\");\n validateConfig(config);\n console.log(chalk.green(\"配置验证通过!\"));\n console.log(chalk.blue(\"使用 --no-dry-run 执行实际测试\"));\n return;\n }\n\n // 创建测试引擎\n console.log(chalk.blue(\"使用内置 wrk 版本...\"));\n\n const engine = new TestEngine(config, {\n outputDir: options.output, // 使用全局输出目录\n cleanupScripts: options.cleanupWrk,\n showProgress: options.progress !== false,\n verbose: options.verbose,\n });\n\n // 设置进度监控\n if (options.progress !== false) {\n setupProgressMonitoring(engine, options.verbose);\n }\n\n // 执行测试\n console.log(chalk.blue(\"\\n开始执行基准测试...\\n\"));\n\n const startTime = Date.now();\n const result = await engine.runBenchmark();\n const totalTime = (Date.now() - startTime) / 1000;\n\n // 显示结果摘要\n displayResultsSummary(result, totalTime);\n\n // 清理资源\n engine.destroy();\n\n console.log(chalk.green(\"\\n测试完成!\"));\n\n if (options.report !== false) {\n console.log(chalk.blue(\"详细报告已保存到 reports/ 目录\"));\n }\n } catch (error) {\n console.error(chalk.red(\"\\n测试执行失败:\"));\n console.error(error);\n process.exit(1);\n }\n}\n\n/**\n * 设置进度监控\n */\nfunction setupProgressMonitoring(engine: TestEngine, verbose: boolean) {\n let lastProgressUpdate = Date.now();\n\n // 进度流\n engine.getProgressStream().subscribe((progress) => {\n const percentage = progress.percentage;\n const color = percentage === 100 ? chalk.green : chalk.blue;\n\n if (verbose) {\n const currentTest = progress.currentTest ? ` | ${progress.currentTest}` : \"\";\n console.log(\n color(\n `${progress.groupName}: ${percentage}% (${progress.completed}/${progress.total})${currentTest}`\n )\n );\n } else {\n // 禁用进度条输出,使用TestEngine的单行显示\n }\n });\n\n // 统计流\n engine.getStatsStream().subscribe((stats) => {\n const now = Date.now();\n\n if (verbose) {\n const successRate =\n stats.totalRequests > 0\n ? ((stats.successfulRequests / stats.totalRequests) * 100).toFixed(1)\n : \"0.0\";\n console.log(\n chalk.cyan(\n `实时: ${stats.totalRequests.toLocaleString()} 请求 | ${successRate}% 成功 | 平均 ${\n stats.averageResponseTime\n }ms | ${stats.requestsPerSecond.toFixed(1)} req/s`\n )\n );\n } else {\n // 禁用统计输出,使用TestEngine的单行显示\n }\n });\n}\n\n/**\n * 显示结果摘要\n */\nfunction displayResultsSummary(result: any, totalTime: number) {\n const stats = result.overallStats;\n const successRate = ((stats.successfulRequests / stats.totalRequests) * 100).toFixed(1);\n\n console.log(chalk.blue(\"\\n\" + \"=\".repeat(60)));\n console.log(chalk.blue.bold(\"测试结果摘要\"));\n console.log(chalk.blue(\"=\".repeat(60)));\n\n console.log(chalk.white(`总耗时: ${totalTime.toFixed(2)} 秒`));\n console.log(chalk.white(`总请求数: ${stats.totalRequests.toLocaleString()}`));\n console.log(chalk.green(`成功: ${stats.successfulRequests.toLocaleString()} (${successRate}%)`));\n console.log(\n chalk.red(\n `失败: ${stats.failedRequests.toLocaleString()} (${(stats.errorRate * 100).toFixed(1)}%)`\n )\n );\n console.log(chalk.yellow(`平均响应时间: ${stats.averageResponseTime} ms`));\n console.log(chalk.yellow(`P95响应时间: ${stats.p95ResponseTime} ms`));\n console.log(chalk.cyan(`吞吐量: ${stats.requestsPerSecond.toFixed(1)} req/s`));\n\n // 测试组详情\n if (result.groups.length > 1) {\n console.log(chalk.blue(\"\\n测试组详情:\"));\n result.groups.forEach((group: any) => {\n const groupStats = group.stats;\n const groupSuccessRate = (\n (groupStats.successfulRequests / groupStats.totalRequests) *\n 100\n ).toFixed(1);\n const color = groupSuccessRate === \"100.0\" ? chalk.green : chalk.yellow;\n\n console.log(\n color(\n ` ${group.name}: ${groupStats.totalRequests} 请求, ${groupSuccessRate}% 成功, 平均 ${groupStats.averageResponseTime}ms`\n )\n );\n });\n }\n\n console.log(chalk.blue(\"=\".repeat(60)));\n}\n\n/**\n * 列出示例配置文件\n */\nasync function listExamples() {\n console.log(chalk.blue(\"\\n可用的示例配置文件:\"));\n console.log(chalk.blue(\"=\".repeat(50)));\n\n const examples = [\n { name: \"simple-config.ts\", desc: \"简单API测试配置\" },\n { name: \"complex-config.ts\", desc: \"复杂场景配置(多组、混合模式)\" },\n { name: \"rest-api-config.ts\", desc: \"REST API完整测试配置\" },\n ];\n\n examples.forEach((example, index) => {\n console.log(chalk.white(`${index + 1}. ${example.name}`));\n console.log(chalk.gray(` ${example.desc}`));\n console.log();\n });\n\n console.log(chalk.yellow(\"使用 --create-example 创建这些配置文件\"));\n}\n\n/**\n * 创建示例配置文件\n */\nasync function createExampleConfig() {\n console.log(chalk.blue(\"\\n创建示例配置文件...\"));\n\n const examplesDir = path.join(process.cwd(), \"examples\");\n const targetDir = process.cwd();\n\n try {\n // 复制示例文件\n const files = [\"simple-config.ts\", \"complex-config.ts\", \"rest-api-config.ts\"];\n\n for (const file of files) {\n const sourcePath = path.join(examplesDir, file);\n const targetPath = path.join(targetDir, file);\n\n if (await fileExists(sourcePath)) {\n await fs.copyFile(sourcePath, targetPath);\n console.log(chalk.green(`创建: ${file}`));\n }\n }\n\n console.log(chalk.green(\"\\n示例配置文件创建完成!\"));\n console.log(chalk.blue(\"现在你可以修改这些文件并运行测试\"));\n console.log(chalk.blue(\" 例如: thunderbench --config simple-config.ts\"));\n } catch (error) {\n console.error(chalk.red(\"创建示例配置文件失败:\"), error);\n }\n}\n\n/**\n * 检查文件是否存在\n */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n// 运行主程序\nmain().catch((error) => {\n console.error(chalk.red(\"程序执行失败:\"), error);\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/core/config-validation.ts
|
|
2
|
+
/**
|
|
3
|
+
* 验证测试配置
|
|
4
|
+
* @param config 测试配置
|
|
5
|
+
*/
|
|
6
|
+
const validateConfig = (config) => {
|
|
7
|
+
if (!config.groups || config.groups.length === 0) throw new Error("至少需要一个测试组");
|
|
8
|
+
config.groups.forEach((group) => {
|
|
9
|
+
validateGroup(group);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* 验证测试组配置
|
|
14
|
+
* @param group 测试组配置
|
|
15
|
+
*/
|
|
16
|
+
const validateGroup = (group) => {
|
|
17
|
+
if (!group.name || group.name.trim() === "") throw new Error("测试组名称不能为空");
|
|
18
|
+
if (group.executionMode !== "parallel" && group.executionMode !== "sequential") throw new Error("执行模式必须是 \"parallel\" 或 \"sequential\"");
|
|
19
|
+
if (group.delay !== void 0 && group.delay < 0) throw new Error("延迟时间不能为负数");
|
|
20
|
+
if (group.http) validateHttpConfig(group.http);
|
|
21
|
+
if (!group.tests || group.tests.length === 0) throw new Error(`测试组 "${group.name}" 至少需要一个测试接口`);
|
|
22
|
+
group.tests.forEach((test) => {
|
|
23
|
+
validateTest(test, group.name);
|
|
24
|
+
});
|
|
25
|
+
if (group.threads <= 0) throw new Error("线程数必须大于0");
|
|
26
|
+
if (group.connections <= 0) throw new Error("连接数必须大于0");
|
|
27
|
+
if (group.duration === void 0 || group.duration === null) throw new Error(`测试组 "${group.name}" 必须指定 duration`);
|
|
28
|
+
if (group.duration <= 0) throw new Error("测试时长必须大于0");
|
|
29
|
+
validateWeights(group.tests, group.name);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 验证测试接口配置
|
|
33
|
+
* @param test 测试接口配置
|
|
34
|
+
* @param groupName 组名称
|
|
35
|
+
*/
|
|
36
|
+
const validateTest = (test, groupName) => {
|
|
37
|
+
if (!test.name || test.name.trim() === "") throw new Error("测试接口名称不能为空");
|
|
38
|
+
if (!test.request) throw new Error(`测试接口 "${test.name}" 缺少请求配置`);
|
|
39
|
+
if (!test.request.method) throw new Error(`测试接口 "${test.name}" 缺少请求方法`);
|
|
40
|
+
if (!test.request.url) throw new Error(`测试接口 "${test.name}" 缺少请求URL`);
|
|
41
|
+
if (test.weight < 0 || test.weight > 100) throw new Error(`测试接口 "${test.name}" 权重必须在0-100之间,当前: ${test.weight}`);
|
|
42
|
+
if (test.errorHandling) {
|
|
43
|
+
if (test.errorHandling.expectMaxResponseTime <= 0) throw new Error("期望最大响应时间必须大于0");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* 验证HTTP配置
|
|
48
|
+
* @param httpConfig HTTP配置
|
|
49
|
+
*/
|
|
50
|
+
const validateHttpConfig = (httpConfig) => {
|
|
51
|
+
if (httpConfig.timeout !== void 0 && httpConfig.timeout < 0) throw new Error("HTTP超时时间不能为负数");
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* 验证权重总和
|
|
55
|
+
* @param tests 测试接口数组
|
|
56
|
+
* @param groupName 组名称
|
|
57
|
+
*/
|
|
58
|
+
const validateWeights = (tests, groupName) => {
|
|
59
|
+
const totalWeight = tests.reduce((sum, test) => sum + test.weight, 0);
|
|
60
|
+
if (Math.abs(totalWeight - 100) > .01) throw new Error(`测试组 "${groupName}" 权重总和必须为100,当前为${totalWeight}`);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { validateConfig as t };
|
|
65
|
+
//# sourceMappingURL=config-validation-BFeqCCo5.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-validation-BFeqCCo5.mjs","names":[],"sources":["../src/core/config-validation.ts"],"sourcesContent":["import { BenchmarkConfig, TestGroupConfig, ApiTestConfig } from \"../types\";\n\n/**\n * 验证测试配置\n * @param config 测试配置\n */\nexport const validateConfig = (config: BenchmarkConfig): void => {\n // 验证测试组\n if (!config.groups || config.groups.length === 0) {\n throw new Error(\"至少需要一个测试组\");\n }\n\n // 验证每个测试组\n config.groups.forEach((group) => {\n validateGroup(group);\n });\n};\n\n/**\n * 验证测试组配置\n * @param group 测试组配置\n */\nconst validateGroup = (group: TestGroupConfig): void => {\n // 验证组名称\n if (!group.name || group.name.trim() === \"\") {\n throw new Error(\"测试组名称不能为空\");\n }\n\n // 验证执行模式\n if (group.executionMode !== \"parallel\" && group.executionMode !== \"sequential\") {\n throw new Error('执行模式必须是 \"parallel\" 或 \"sequential\"');\n }\n\n // 验证延迟配置\n if (group.delay !== undefined && group.delay < 0) {\n throw new Error(\"延迟时间不能为负数\");\n }\n\n // 验证HTTP配置\n if (group.http) {\n validateHttpConfig(group.http);\n }\n\n // 验证测试接口\n if (!group.tests || group.tests.length === 0) {\n throw new Error(`测试组 \"${group.name}\" 至少需要一个测试接口`);\n }\n\n // 验证每个测试接口\n group.tests.forEach((test) => {\n validateTest(test, group.name);\n });\n\n // 验证 wrk 参数\n if (group.threads <= 0) {\n throw new Error(\"线程数必须大于0\");\n }\n\n if (group.connections <= 0) {\n throw new Error(\"连接数必须大于0\");\n }\n\n // 验证测试策略(duration)\n if (group.duration === undefined || group.duration === null) {\n throw new Error(`测试组 \"${group.name}\" 必须指定 duration`);\n }\n\n if (group.duration <= 0) {\n throw new Error(\"测试时长必须大于0\");\n }\n\n // 验证权重总和\n validateWeights(group.tests, group.name);\n};\n\n/**\n * 验证测试接口配置\n * @param test 测试接口配置\n * @param groupName 组名称\n */\nconst validateTest = (test: ApiTestConfig, groupName: string): void => {\n // 验证接口名称\n if (!test.name || test.name.trim() === \"\") {\n throw new Error(\"测试接口名称不能为空\");\n }\n\n // 验证请求配置\n if (!test.request) {\n throw new Error(`测试接口 \"${test.name}\" 缺少请求配置`);\n }\n\n if (!test.request.method) {\n throw new Error(`测试接口 \"${test.name}\" 缺少请求方法`);\n }\n\n if (!test.request.url) {\n throw new Error(`测试接口 \"${test.name}\" 缺少请求URL`);\n }\n\n // 验证权重\n if (test.weight < 0 || test.weight > 100) {\n throw new Error(`测试接口 \"${test.name}\" 权重必须在0-100之间,当前: ${test.weight}`);\n }\n\n // 验证错误处理配置\n if (test.errorHandling) {\n if (test.errorHandling.expectMaxResponseTime <= 0) {\n throw new Error(\"期望最大响应时间必须大于0\");\n }\n }\n};\n\n/**\n * 验证HTTP配置\n * @param httpConfig HTTP配置\n */\nconst validateHttpConfig = (httpConfig: any): void => {\n if (httpConfig.timeout !== undefined && httpConfig.timeout < 0) {\n throw new Error(\"HTTP超时时间不能为负数\");\n }\n};\n\n/**\n * 验证权重总和\n * @param tests 测试接口数组\n * @param groupName 组名称\n */\nconst validateWeights = (tests: ApiTestConfig[], groupName: string): void => {\n const totalWeight = tests.reduce((sum, test) => sum + test.weight, 0);\n\n if (Math.abs(totalWeight - 100) > 0.01) {\n // 允许0.01的误差\n throw new Error(`测试组 \"${groupName}\" 权重总和必须为100,当前为${totalWeight}`);\n }\n};\n"],"mappings":";;;;;AAMA,MAAa,kBAAkB,WAAkC;AAE/D,KAAI,CAAC,OAAO,UAAU,OAAO,OAAO,WAAW,EAC7C,OAAM,IAAI,MAAM,YAAY;AAI9B,QAAO,OAAO,SAAS,UAAU;AAC/B,gBAAc,MAAM;GACpB;;;;;;AAOJ,MAAM,iBAAiB,UAAiC;AAEtD,KAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,GACvC,OAAM,IAAI,MAAM,YAAY;AAI9B,KAAI,MAAM,kBAAkB,cAAc,MAAM,kBAAkB,aAChE,OAAM,IAAI,MAAM,wCAAoC;AAItD,KAAI,MAAM,UAAU,UAAa,MAAM,QAAQ,EAC7C,OAAM,IAAI,MAAM,YAAY;AAI9B,KAAI,MAAM,KACR,oBAAmB,MAAM,KAAK;AAIhC,KAAI,CAAC,MAAM,SAAS,MAAM,MAAM,WAAW,EACzC,OAAM,IAAI,MAAM,QAAQ,MAAM,KAAK,cAAc;AAInD,OAAM,MAAM,SAAS,SAAS;AAC5B,eAAa,MAAM,MAAM,KAAK;GAC9B;AAGF,KAAI,MAAM,WAAW,EACnB,OAAM,IAAI,MAAM,WAAW;AAG7B,KAAI,MAAM,eAAe,EACvB,OAAM,IAAI,MAAM,WAAW;AAI7B,KAAI,MAAM,aAAa,UAAa,MAAM,aAAa,KACrD,OAAM,IAAI,MAAM,QAAQ,MAAM,KAAK,iBAAiB;AAGtD,KAAI,MAAM,YAAY,EACpB,OAAM,IAAI,MAAM,YAAY;AAI9B,iBAAgB,MAAM,OAAO,MAAM,KAAK;;;;;;;AAQ1C,MAAM,gBAAgB,MAAqB,cAA4B;AAErE,KAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,MAAM,KAAK,GACrC,OAAM,IAAI,MAAM,aAAa;AAI/B,KAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,SAAS,KAAK,KAAK,UAAU;AAG/C,KAAI,CAAC,KAAK,QAAQ,OAChB,OAAM,IAAI,MAAM,SAAS,KAAK,KAAK,UAAU;AAG/C,KAAI,CAAC,KAAK,QAAQ,IAChB,OAAM,IAAI,MAAM,SAAS,KAAK,KAAK,WAAW;AAIhD,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,IACnC,OAAM,IAAI,MAAM,SAAS,KAAK,KAAK,qBAAqB,KAAK,SAAS;AAIxE,KAAI,KAAK,eACP;MAAI,KAAK,cAAc,yBAAyB,EAC9C,OAAM,IAAI,MAAM,gBAAgB;;;;;;;AAStC,MAAM,sBAAsB,eAA0B;AACpD,KAAI,WAAW,YAAY,UAAa,WAAW,UAAU,EAC3D,OAAM,IAAI,MAAM,gBAAgB;;;;;;;AASpC,MAAM,mBAAmB,OAAwB,cAA4B;CAC3E,MAAM,cAAc,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,QAAQ,EAAE;AAErE,KAAI,KAAK,IAAI,cAAc,IAAI,GAAG,IAEhC,OAAM,IAAI,MAAM,QAAQ,UAAU,kBAAkB,cAAc"}
|