token-speed-tester 1.5.0 → 1.6.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/README.en.md +50 -78
- package/README.md +3 -0
- package/dist/index.js +184 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A CLI tool to measure and analyze LLM API token streaming performance
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
[Chinese README](README.md)
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/token-speed-tester)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -51,7 +51,8 @@ npm install token-speed-tester
|
|
|
51
51
|
```bash
|
|
52
52
|
# Test Anthropic API (default)
|
|
53
53
|
token-speed-test --api-key sk-ant-xxx
|
|
54
|
-
|
|
54
|
+
# English output
|
|
55
|
+
token-speed-test --api-key sk-ant-xxx --lang en
|
|
55
56
|
# Test OpenAI API
|
|
56
57
|
token-speed-test --api-key sk-xxx --provider openai
|
|
57
58
|
```
|
|
@@ -102,8 +103,11 @@ node dist/index.js --api-key sk-ant-xxx
|
|
|
102
103
|
| `--model` | `-m` | Model name | Auto-selected by provider |
|
|
103
104
|
| `--url` | `-u` | Custom API endpoint | Official endpoint |
|
|
104
105
|
| `--runs` | `-r` | Number of test runs | `3` |
|
|
105
|
-
| `--prompt` | | Test prompt |
|
|
106
|
+
| `--prompt` | | Test prompt | Language-specific |
|
|
106
107
|
| `--max-tokens` | | Maximum output tokens | `1024` |
|
|
108
|
+
| `--lang` | | Output language: `zh` or `en` | `zh` |
|
|
109
|
+
|
|
110
|
+
Note: The default prompt follows the selected language. Use `--lang en` for the English default prompt.
|
|
107
111
|
|
|
108
112
|
### Default Models
|
|
109
113
|
|
|
@@ -113,89 +117,57 @@ node dist/index.js --api-key sk-ant-xxx
|
|
|
113
117
|
## Output Example
|
|
114
118
|
|
|
115
119
|
```
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
Token Speed Test
|
|
121
|
+
--------------------------------------------------
|
|
118
122
|
Provider: anthropic
|
|
119
123
|
Model: claude-opus-4-5-20251101
|
|
120
124
|
Max Tokens: 1024
|
|
121
125
|
Runs: 3
|
|
122
|
-
Prompt:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
模型输出 (流式):
|
|
128
|
-
|
|
129
|
-
[运行 1]
|
|
126
|
+
Prompt: Write a short essay about AI
|
|
127
|
+
--------------------------------------------------
|
|
128
|
+
Running tests...
|
|
129
|
+
Model output (streaming):
|
|
130
|
+
[Run 1]
|
|
130
131
|
TTFT: 523ms
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
[运行 2]
|
|
132
|
+
Total Time: 3245ms
|
|
133
|
+
Total Tokens: 412
|
|
134
|
+
Avg Speed: 126.96 tokens/s
|
|
135
|
+
Peak Speed: 156.32 tokens/s
|
|
136
|
+
Peak TPS: 168.00 tokens/s
|
|
137
|
+
[Run 2]
|
|
138
138
|
TTFT: 487ms
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
[运行 3]
|
|
139
|
+
Total Time: 3189ms
|
|
140
|
+
Total Tokens: 398
|
|
141
|
+
Avg Speed: 124.84 tokens/s
|
|
142
|
+
Peak Speed: 158.41 tokens/s
|
|
143
|
+
Peak TPS: 171.00 tokens/s
|
|
144
|
+
[Run 3]
|
|
146
145
|
TTFT: 501ms
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
Total Time: 3312ms
|
|
147
|
+
Total Tokens: 405
|
|
148
|
+
Avg Speed: 122.28 tokens/s
|
|
149
|
+
Peak Speed: 154.23 tokens/s
|
|
150
|
+
Peak TPS: 166.00 tokens/s
|
|
153
151
|
======================================================================
|
|
154
|
-
Token
|
|
152
|
+
Token Speed Test Report
|
|
155
153
|
======================================================================
|
|
154
|
+
Summary (N=3)
|
|
155
|
+
+-----------------+--------+-------+-------+---------+
|
|
156
|
+
| Metric | Mean | Min | Max | Std Dev |
|
|
157
|
+
+-----------------+--------+-------+-------+---------+
|
|
158
|
+
| TTFT (ms) | 503.67 | 487.00| 523.00| 14.57 |
|
|
159
|
+
| Total Time (ms) | 3248.67| 3189.00|3312.00|51.92 |
|
|
160
|
+
| Total Tokens | 405.00 | 398.00| 412.00| 5.35 |
|
|
161
|
+
| Avg Speed | 124.69 | 122.28| 126.96| 1.88 |
|
|
162
|
+
| Peak Speed | 156.32 | 154.23| 158.41| 1.82 |
|
|
163
|
+
| Peak TPS | 168.33 | 166.00| 171.00| 2.05 |
|
|
164
|
+
+-----------------+--------+-------+-------+---------+
|
|
165
|
+
Token Speed Trend (TPS)
|
|
166
|
+
[chart omitted]
|
|
167
|
+
TPS Distribution
|
|
168
|
+
[histogram omitted]
|
|
169
|
+
Tests complete!
|
|
156
170
|
|
|
157
|
-
统计汇总 (N=3)
|
|
158
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
159
|
-
│ 指标 │ 均值 │ 最小值 │ 最大值 │ 标准差 │
|
|
160
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
161
|
-
│ TTFT (ms) │ 503.67 │ 487.00 │ 523.00 │ 14.57 │
|
|
162
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
163
|
-
│ 总耗时 (ms) │ 3248.67 │ 3189.00 │ 3312.00 │ 51.92 │
|
|
164
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
165
|
-
│ 总 Token 数 │ 405.00 │ 398.00 │ 412.00 │ 5.35 │
|
|
166
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
167
|
-
│ 平均速度 │ 124.69 │ 122.28 │ 126.96 │ 1.88 │
|
|
168
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
169
|
-
│ 峰值速度 │ 156.32 │ 154.23 │ 158.41 │ 1.82 │
|
|
170
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
171
|
-
│ 峰值 TPS │ 168.33 │ 166.00 │ 171.00 │ 2.05 │
|
|
172
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
173
|
-
|
|
174
|
-
Token 速度趋势图 (TPS)
|
|
175
|
-
┌────────────────────────────────────────┐
|
|
176
|
-
│ 120 ┤ █ │
|
|
177
|
-
│ 100 ┤ █ █ █ █ │
|
|
178
|
-
│ 80 ┤ █ █ █ █ █ █ █ │
|
|
179
|
-
│ 60 ┤ █ █ █ █ █ █ █ █ █ █ │
|
|
180
|
-
│ 40 ┤ █ █ █ █ █ █ █ █ █ █ █ █ │
|
|
181
|
-
│ 20 ┤ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │
|
|
182
|
-
│ 0 └────────────────────────────────── │
|
|
183
|
-
│ 0s 1s 2s 3s 4s 5s 6s │
|
|
184
|
-
└────────────────────────────────────────┘
|
|
185
|
-
|
|
186
|
-
TPS 分布
|
|
187
|
-
0.0-12.0 │██████████████████████████████████████████████████ 45
|
|
188
|
-
12.0-24.0 │██ 3
|
|
189
|
-
24.0-36.0 │ 0
|
|
190
|
-
36.0-48.0 │ 0
|
|
191
|
-
48.0-60.0 │ 0
|
|
192
|
-
60.0-72.0 │ 0
|
|
193
|
-
72.0-84.0 │ 0
|
|
194
|
-
84.0-96.0 │ 0
|
|
195
|
-
96.0-108.0 │ 0
|
|
196
|
-
108.0-120.0 │ 0
|
|
197
|
-
|
|
198
|
-
✅ 测试完成!
|
|
199
171
|
```
|
|
200
172
|
|
|
201
173
|
## Metrics Explained
|
|
@@ -205,7 +177,7 @@ TPS 分布
|
|
|
205
177
|
| **TTFT** | Time to First Token - latency from request to first token arrival |
|
|
206
178
|
| **Total Time** | Complete duration from request to stream completion |
|
|
207
179
|
| **Total Tokens** | Number of output tokens received |
|
|
208
|
-
| **Average Speed** | Mean tokens per second (totalTokens / totalTime
|
|
180
|
+
| **Average Speed** | Mean tokens per second (totalTokens / totalTime x 1000) |
|
|
209
181
|
| **Peak Speed** | Fastest speed measured over a sliding 10-token window |
|
|
210
182
|
| **Peak TPS** | Highest tokens received within a single second |
|
|
211
183
|
| **TPS Curve** | Tokens received per second throughout the streaming response |
|
|
@@ -270,7 +242,7 @@ This project maintains high code coverage:
|
|
|
270
242
|
|
|
271
243
|
## License
|
|
272
244
|
|
|
273
|
-
MIT
|
|
245
|
+
MIT (c) [Cansiny0320](https://github.com/Cansiny0320)
|
|
274
246
|
|
|
275
247
|
## Contributing
|
|
276
248
|
|
package/README.md
CHANGED
|
@@ -51,6 +51,8 @@ npm install token-speed-tester
|
|
|
51
51
|
```bash
|
|
52
52
|
# 测试 Anthropic API(默认)
|
|
53
53
|
token-speed-test --api-key sk-ant-xxx
|
|
54
|
+
# 输出英文结果
|
|
55
|
+
token-speed-test --api-key sk-ant-xxx --lang en
|
|
54
56
|
|
|
55
57
|
# 测试 OpenAI API
|
|
56
58
|
token-speed-test --api-key sk-xxx --provider openai
|
|
@@ -104,6 +106,7 @@ node dist/index.js --api-key sk-ant-xxx
|
|
|
104
106
|
| `--runs` | `-r` | 测试次数 | `3` |
|
|
105
107
|
| `--prompt` | | 测试提示词 | "写一篇关于 AI 的短文" |
|
|
106
108
|
| `--max-tokens` | | 最大输出 Token 数 | `1024` |
|
|
109
|
+
| `--lang` | | 输出语言: `zh` 或 `en` | `zh` |
|
|
107
110
|
|
|
108
111
|
### 默认模型
|
|
109
112
|
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,120 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
|
|
10
|
+
// src/i18n.ts
|
|
11
|
+
var SUPPORTED_LANGS = ["zh", "en"];
|
|
12
|
+
var DEFAULT_LANG = "zh";
|
|
13
|
+
var zhMessages = {
|
|
14
|
+
defaultPrompt: "\u5199\u4E00\u7BC7\u5173\u4E8E AI \u7684\u77ED\u6587",
|
|
15
|
+
appTitle: "\u{1F680} Token \u901F\u5EA6\u6D4B\u8BD5\u5DE5\u5177",
|
|
16
|
+
runningTests: "\u23F3 \u6B63\u5728\u8FD0\u884C\u6D4B\u8BD5...",
|
|
17
|
+
streamingOutput: "\u6A21\u578B\u8F93\u51FA (\u6D41\u5F0F):",
|
|
18
|
+
testComplete: "\u2705 \u6D4B\u8BD5\u5B8C\u6210!",
|
|
19
|
+
errorPrefix: "\u274C \u9519\u8BEF",
|
|
20
|
+
unknownError: "\u274C \u53D1\u751F\u672A\u77E5\u9519\u8BEF",
|
|
21
|
+
configLabels: {
|
|
22
|
+
provider: "Provider",
|
|
23
|
+
model: "Model",
|
|
24
|
+
maxTokens: "Max Tokens",
|
|
25
|
+
runs: "Runs",
|
|
26
|
+
prompt: "Prompt"
|
|
27
|
+
},
|
|
28
|
+
runLabel: (index) => `[\u8FD0\u884C ${index}]`,
|
|
29
|
+
runProgressLabel: (current, total) => `[\u8FD0\u884C ${current}/${total}]`,
|
|
30
|
+
reportTitle: "Token \u901F\u5EA6\u6D4B\u8BD5\u62A5\u544A",
|
|
31
|
+
speedChartTitle: "Token \u901F\u5EA6\u8D8B\u52BF\u56FE (TPS)",
|
|
32
|
+
tpsHistogramTitle: "TPS \u5206\u5E03",
|
|
33
|
+
noChartData: "\u6CA1\u6709\u53EF\u7528\u4E8E\u56FE\u8868\u7684\u6570\u636E",
|
|
34
|
+
noTpsData: "\u6CA1\u6709 TPS \u6570\u636E\u53EF\u7528",
|
|
35
|
+
statsSummaryTitle: (sampleSize) => `\u7EDF\u8BA1\u6C47\u603B (N=${sampleSize})`,
|
|
36
|
+
statsHeaders: {
|
|
37
|
+
metric: "\u6307\u6807",
|
|
38
|
+
mean: "\u5747\u503C",
|
|
39
|
+
min: "\u6700\u5C0F\u503C",
|
|
40
|
+
max: "\u6700\u5927\u503C",
|
|
41
|
+
stdDev: "\u6807\u51C6\u5DEE"
|
|
42
|
+
},
|
|
43
|
+
statsLabels: {
|
|
44
|
+
ttft: "TTFT (ms)",
|
|
45
|
+
totalTime: "\u603B\u8017\u65F6 (ms)",
|
|
46
|
+
totalTokens: "\u603B Token \u6570",
|
|
47
|
+
averageSpeed: "\u5E73\u5747\u901F\u5EA6",
|
|
48
|
+
peakSpeed: "\u5CF0\u503C\u901F\u5EA6",
|
|
49
|
+
peakTps: "\u5CF0\u503C TPS"
|
|
50
|
+
},
|
|
51
|
+
resultLabels: {
|
|
52
|
+
ttft: "TTFT",
|
|
53
|
+
totalTime: "\u603B\u8017\u65F6",
|
|
54
|
+
totalTokens: "\u603B Token \u6570",
|
|
55
|
+
averageSpeed: "\u5E73\u5747\u901F\u5EA6",
|
|
56
|
+
peakSpeed: "\u5CF0\u503C\u901F\u5EA6",
|
|
57
|
+
peakTps: "\u5CF0\u503C TPS"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var enMessages = {
|
|
61
|
+
defaultPrompt: "Write a short essay about AI",
|
|
62
|
+
appTitle: "\u{1F680} Token Speed Test",
|
|
63
|
+
runningTests: "\u23F3 Running tests...",
|
|
64
|
+
streamingOutput: "Model output (streaming):",
|
|
65
|
+
testComplete: "\u2705 Tests complete!",
|
|
66
|
+
errorPrefix: "\u274C Error",
|
|
67
|
+
unknownError: "\u274C An unknown error occurred",
|
|
68
|
+
configLabels: {
|
|
69
|
+
provider: "Provider",
|
|
70
|
+
model: "Model",
|
|
71
|
+
maxTokens: "Max Tokens",
|
|
72
|
+
runs: "Runs",
|
|
73
|
+
prompt: "Prompt"
|
|
74
|
+
},
|
|
75
|
+
runLabel: (index) => `[Run ${index}]`,
|
|
76
|
+
runProgressLabel: (current, total) => `[Run ${current}/${total}]`,
|
|
77
|
+
reportTitle: "Token Speed Test Report",
|
|
78
|
+
speedChartTitle: "Token Speed Trend (TPS)",
|
|
79
|
+
tpsHistogramTitle: "TPS Distribution",
|
|
80
|
+
noChartData: "No data available for chart",
|
|
81
|
+
noTpsData: "No TPS data available",
|
|
82
|
+
statsSummaryTitle: (sampleSize) => `Summary (N=${sampleSize})`,
|
|
83
|
+
statsHeaders: {
|
|
84
|
+
metric: "Metric",
|
|
85
|
+
mean: "Mean",
|
|
86
|
+
min: "Min",
|
|
87
|
+
max: "Max",
|
|
88
|
+
stdDev: "Std Dev"
|
|
89
|
+
},
|
|
90
|
+
statsLabels: {
|
|
91
|
+
ttft: "TTFT (ms)",
|
|
92
|
+
totalTime: "Total Time (ms)",
|
|
93
|
+
totalTokens: "Total Tokens",
|
|
94
|
+
averageSpeed: "Avg Speed",
|
|
95
|
+
peakSpeed: "Peak Speed",
|
|
96
|
+
peakTps: "Peak TPS"
|
|
97
|
+
},
|
|
98
|
+
resultLabels: {
|
|
99
|
+
ttft: "TTFT",
|
|
100
|
+
totalTime: "Total Time",
|
|
101
|
+
totalTokens: "Total Tokens",
|
|
102
|
+
averageSpeed: "Avg Speed",
|
|
103
|
+
peakSpeed: "Peak Speed",
|
|
104
|
+
peakTps: "Peak TPS"
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function isSupportedLang(value) {
|
|
108
|
+
return SUPPORTED_LANGS.includes(value);
|
|
109
|
+
}
|
|
110
|
+
function resolveLang(value) {
|
|
111
|
+
if (!value) {
|
|
112
|
+
return DEFAULT_LANG;
|
|
113
|
+
}
|
|
114
|
+
const normalized = value.toLowerCase();
|
|
115
|
+
if (!isSupportedLang(normalized)) {
|
|
116
|
+
throw new Error(`Invalid lang: ${value}. Must be 'zh' or 'en'.`);
|
|
117
|
+
}
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
function getMessages(lang) {
|
|
121
|
+
return lang === "en" ? enMessages : zhMessages;
|
|
122
|
+
}
|
|
123
|
+
|
|
10
124
|
// src/config.ts
|
|
11
125
|
var DEFAULT_MODELS = {
|
|
12
126
|
anthropic: "claude-opus-4-5-20251101",
|
|
@@ -14,7 +128,6 @@ var DEFAULT_MODELS = {
|
|
|
14
128
|
};
|
|
15
129
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
16
130
|
var DEFAULT_RUNS = 3;
|
|
17
|
-
var DEFAULT_PROMPT = "\u5199\u4E00\u7BC7\u5173\u4E8E AI \u7684\u77ED\u6587";
|
|
18
131
|
function parseConfig(args) {
|
|
19
132
|
const {
|
|
20
133
|
apiKey,
|
|
@@ -23,8 +136,12 @@ function parseConfig(args) {
|
|
|
23
136
|
model,
|
|
24
137
|
maxTokens = DEFAULT_MAX_TOKENS,
|
|
25
138
|
runs = DEFAULT_RUNS,
|
|
26
|
-
prompt
|
|
139
|
+
prompt,
|
|
140
|
+
lang: langInput
|
|
27
141
|
} = args;
|
|
142
|
+
const lang = resolveLang(langInput);
|
|
143
|
+
const messages = getMessages(lang);
|
|
144
|
+
const finalPrompt = prompt ?? messages.defaultPrompt;
|
|
28
145
|
if (!apiKey || apiKey.trim() === "") {
|
|
29
146
|
throw new Error("API Key is required. Use --api-key or -k to provide it.");
|
|
30
147
|
}
|
|
@@ -45,7 +162,8 @@ function parseConfig(args) {
|
|
|
45
162
|
model: finalModel,
|
|
46
163
|
maxTokens,
|
|
47
164
|
runCount: runs,
|
|
48
|
-
prompt:
|
|
165
|
+
prompt: finalPrompt.trim(),
|
|
166
|
+
lang
|
|
49
167
|
};
|
|
50
168
|
}
|
|
51
169
|
|
|
@@ -202,10 +320,11 @@ async function streamTest(config) {
|
|
|
202
320
|
}
|
|
203
321
|
async function runMultipleTests(config) {
|
|
204
322
|
const results = [];
|
|
323
|
+
const messages = getMessages(config.lang);
|
|
205
324
|
for (let i = 0; i < config.runCount; i++) {
|
|
206
325
|
if (config.runCount > 1) {
|
|
207
326
|
const label = `
|
|
208
|
-
|
|
327
|
+
${messages.runProgressLabel(i + 1, config.runCount)}`;
|
|
209
328
|
console.log(label);
|
|
210
329
|
console.log("-".repeat(label.length - 1));
|
|
211
330
|
}
|
|
@@ -366,9 +485,10 @@ function padStartWidth(text, width) {
|
|
|
366
485
|
}
|
|
367
486
|
return " ".repeat(width - currentWidth) + text;
|
|
368
487
|
}
|
|
369
|
-
function renderSpeedChart(tps, maxSpeed) {
|
|
488
|
+
function renderSpeedChart(tps, maxSpeed, lang = DEFAULT_LANG) {
|
|
489
|
+
const messages = getMessages(lang);
|
|
370
490
|
if (tps.length === 0) {
|
|
371
|
-
return
|
|
491
|
+
return messages.noChartData;
|
|
372
492
|
}
|
|
373
493
|
const actualMax = maxSpeed ?? Math.max(...tps, 1);
|
|
374
494
|
const maxVal = Math.max(actualMax, 1);
|
|
@@ -377,7 +497,7 @@ function renderSpeedChart(tps, maxSpeed) {
|
|
|
377
497
|
const chartWidth = stringWidth(emptyRow) - 2;
|
|
378
498
|
const axisPrefix = `\u2502 ${padStartWidth("", Y_LABEL_WIDTH)} \u253C`;
|
|
379
499
|
const lines = [];
|
|
380
|
-
lines.push(
|
|
500
|
+
lines.push(messages.speedChartTitle);
|
|
381
501
|
lines.push("\u250C" + "\u2500".repeat(chartWidth) + "\u2510");
|
|
382
502
|
for (let row = CHART_HEIGHT - 1; row >= 0; row--) {
|
|
383
503
|
const value = row / (CHART_HEIGHT - 1) * maxVal;
|
|
@@ -423,12 +543,13 @@ function generateXLabels(dataPoints, maxLabels) {
|
|
|
423
543
|
}
|
|
424
544
|
return labels;
|
|
425
545
|
}
|
|
426
|
-
function renderTPSHistogram(tps) {
|
|
546
|
+
function renderTPSHistogram(tps, lang = DEFAULT_LANG) {
|
|
547
|
+
const messages = getMessages(lang);
|
|
427
548
|
if (tps.length === 0) {
|
|
428
|
-
return
|
|
549
|
+
return messages.noTpsData;
|
|
429
550
|
}
|
|
430
551
|
const lines = [];
|
|
431
|
-
lines.push(
|
|
552
|
+
lines.push(messages.tpsHistogramTitle);
|
|
432
553
|
const maxTps = Math.max(...tps, 1);
|
|
433
554
|
const buckets = 10;
|
|
434
555
|
const bucketSize = maxTps / buckets;
|
|
@@ -454,18 +575,19 @@ function renderTPSHistogram(tps) {
|
|
|
454
575
|
}
|
|
455
576
|
return lines.join("\n");
|
|
456
577
|
}
|
|
457
|
-
function renderStatsTable(stats) {
|
|
578
|
+
function renderStatsTable(stats, lang = DEFAULT_LANG) {
|
|
579
|
+
const messages = getMessages(lang);
|
|
458
580
|
const lines = [];
|
|
459
581
|
lines.push("");
|
|
460
|
-
lines.push(
|
|
461
|
-
const headerRow = "\u2502 " + padEndWidth(
|
|
582
|
+
lines.push(messages.statsSummaryTitle(stats.sampleSize));
|
|
583
|
+
const headerRow = "\u2502 " + padEndWidth(messages.statsHeaders.metric, STAT_LABEL_WIDTH) + " \u2502 " + padStartWidth(messages.statsHeaders.mean, STAT_VALUE_WIDTH) + " \u2502 " + padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) + " \u2502 " + padStartWidth(messages.statsHeaders.max, STAT_VALUE_WIDTH) + " \u2502 " + padStartWidth(messages.statsHeaders.stdDev, STAT_VALUE_WIDTH) + " \u2502";
|
|
462
584
|
const tableWidth = stringWidth(headerRow) - 2;
|
|
463
585
|
lines.push("\u250C" + "\u2500".repeat(tableWidth) + "\u2510");
|
|
464
586
|
lines.push(headerRow);
|
|
465
587
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
466
588
|
lines.push(
|
|
467
589
|
formatStatRow(
|
|
468
|
-
|
|
590
|
+
messages.statsLabels.ttft,
|
|
469
591
|
stats.mean.ttft,
|
|
470
592
|
stats.min.ttft,
|
|
471
593
|
stats.max.ttft,
|
|
@@ -476,7 +598,7 @@ function renderStatsTable(stats) {
|
|
|
476
598
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
477
599
|
lines.push(
|
|
478
600
|
formatStatRow(
|
|
479
|
-
|
|
601
|
+
messages.statsLabels.totalTime,
|
|
480
602
|
stats.mean.totalTime,
|
|
481
603
|
stats.min.totalTime,
|
|
482
604
|
stats.max.totalTime,
|
|
@@ -487,7 +609,7 @@ function renderStatsTable(stats) {
|
|
|
487
609
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
488
610
|
lines.push(
|
|
489
611
|
formatStatRow(
|
|
490
|
-
|
|
612
|
+
messages.statsLabels.totalTokens,
|
|
491
613
|
stats.mean.totalTokens,
|
|
492
614
|
stats.min.totalTokens,
|
|
493
615
|
stats.max.totalTokens,
|
|
@@ -498,7 +620,7 @@ function renderStatsTable(stats) {
|
|
|
498
620
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
499
621
|
lines.push(
|
|
500
622
|
formatStatRow(
|
|
501
|
-
|
|
623
|
+
messages.statsLabels.averageSpeed,
|
|
502
624
|
stats.mean.averageSpeed,
|
|
503
625
|
stats.min.averageSpeed,
|
|
504
626
|
stats.max.averageSpeed,
|
|
@@ -509,7 +631,7 @@ function renderStatsTable(stats) {
|
|
|
509
631
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
510
632
|
lines.push(
|
|
511
633
|
formatStatRow(
|
|
512
|
-
|
|
634
|
+
messages.statsLabels.peakSpeed,
|
|
513
635
|
stats.mean.peakSpeed,
|
|
514
636
|
stats.min.peakSpeed,
|
|
515
637
|
stats.max.peakSpeed,
|
|
@@ -520,7 +642,7 @@ function renderStatsTable(stats) {
|
|
|
520
642
|
lines.push("\u251C" + "\u2500".repeat(tableWidth) + "\u2524");
|
|
521
643
|
lines.push(
|
|
522
644
|
formatStatRow(
|
|
523
|
-
|
|
645
|
+
messages.statsLabels.peakTps,
|
|
524
646
|
stats.mean.peakTps,
|
|
525
647
|
stats.min.peakTps,
|
|
526
648
|
stats.max.peakTps,
|
|
@@ -541,29 +663,33 @@ function formatTimeWithDecimals(ms) {
|
|
|
541
663
|
}
|
|
542
664
|
return `${ms.toFixed(2)}ms`;
|
|
543
665
|
}
|
|
544
|
-
function renderSingleResult(metrics, runIndex) {
|
|
666
|
+
function renderSingleResult(metrics, runIndex, lang = DEFAULT_LANG) {
|
|
667
|
+
const messages = getMessages(lang);
|
|
545
668
|
const lines = [];
|
|
546
669
|
lines.push(`
|
|
547
|
-
|
|
548
|
-
lines.push(`
|
|
549
|
-
lines.push(`
|
|
550
|
-
lines.push(`
|
|
551
|
-
lines.push(
|
|
552
|
-
|
|
553
|
-
|
|
670
|
+
${messages.runLabel(runIndex + 1)}`);
|
|
671
|
+
lines.push(` ${messages.resultLabels.ttft}: ${formatTimeWithDecimals(metrics.ttft)}`);
|
|
672
|
+
lines.push(` ${messages.resultLabels.totalTime}: ${formatTimeWithDecimals(metrics.totalTime)}`);
|
|
673
|
+
lines.push(` ${messages.resultLabels.totalTokens}: ${metrics.totalTokens}`);
|
|
674
|
+
lines.push(
|
|
675
|
+
` ${messages.resultLabels.averageSpeed}: ${metrics.averageSpeed.toFixed(2)} tokens/s`
|
|
676
|
+
);
|
|
677
|
+
lines.push(` ${messages.resultLabels.peakSpeed}: ${metrics.peakSpeed.toFixed(2)} tokens/s`);
|
|
678
|
+
lines.push(` ${messages.resultLabels.peakTps}: ${metrics.peakTps.toFixed(2)} tokens/s`);
|
|
554
679
|
return lines.join("\n");
|
|
555
680
|
}
|
|
556
|
-
function renderReport(stats) {
|
|
681
|
+
function renderReport(stats, lang = DEFAULT_LANG) {
|
|
682
|
+
const messages = getMessages(lang);
|
|
557
683
|
const lines = [];
|
|
558
684
|
lines.push("\n" + "\u2550".repeat(72));
|
|
559
|
-
lines.push(
|
|
685
|
+
lines.push(messages.reportTitle);
|
|
560
686
|
lines.push("\u2550".repeat(72));
|
|
561
|
-
lines.push(renderStatsTable(stats));
|
|
687
|
+
lines.push(renderStatsTable(stats, lang));
|
|
562
688
|
if (stats.mean.tps.length > 0) {
|
|
563
|
-
lines.push("\n" + renderSpeedChart(stats.mean.tps));
|
|
689
|
+
lines.push("\n" + renderSpeedChart(stats.mean.tps, void 0, lang));
|
|
564
690
|
}
|
|
565
691
|
if (stats.mean.tps.length > 0) {
|
|
566
|
-
lines.push("\n" + renderTPSHistogram(stats.mean.tps));
|
|
692
|
+
lines.push("\n" + renderTPSHistogram(stats.mean.tps, lang));
|
|
567
693
|
}
|
|
568
694
|
return lines.join("\n");
|
|
569
695
|
}
|
|
@@ -581,9 +707,10 @@ function getCliVersion() {
|
|
|
581
707
|
}
|
|
582
708
|
var program = new Command();
|
|
583
709
|
program.name("token-speed-test").description("A CLI tool to test LLM API token output speed").version(getCliVersion());
|
|
584
|
-
program.option("-k, --api-key <key>", "API Key (required)", "").option("-p, --provider <provider>", "API provider: anthropic or openai", "anthropic").option("-u, --url <url>", "Custom API endpoint URL").option("-m, --model <model>", "Model name").option("--max-tokens <number>", "Maximum output tokens", "1024").option("-r, --runs <number>", "Number of test runs", "3").option("--prompt <text>", "Test prompt", "
|
|
710
|
+
program.option("-k, --api-key <key>", "API Key (required)", "").option("-p, --provider <provider>", "API provider: anthropic or openai", "anthropic").option("-u, --url <url>", "Custom API endpoint URL").option("-m, --model <model>", "Model name").option("--max-tokens <number>", "Maximum output tokens", "1024").option("-r, --runs <number>", "Number of test runs", "3").option("--prompt <text>", "Test prompt").option("--lang <lang>", "Output language: zh or en", "zh").parse(process.argv);
|
|
585
711
|
var options = program.opts();
|
|
586
712
|
async function main() {
|
|
713
|
+
let messages = getMessages(DEFAULT_LANG);
|
|
587
714
|
try {
|
|
588
715
|
const config = parseConfig({
|
|
589
716
|
apiKey: options.apiKey,
|
|
@@ -592,37 +719,47 @@ async function main() {
|
|
|
592
719
|
model: options.model,
|
|
593
720
|
maxTokens: parseInt(options.maxTokens, 10),
|
|
594
721
|
runs: parseInt(options.runs, 10),
|
|
595
|
-
prompt: options.prompt
|
|
722
|
+
prompt: options.prompt,
|
|
723
|
+
lang: options.lang
|
|
596
724
|
});
|
|
597
|
-
|
|
725
|
+
messages = getMessages(config.lang);
|
|
726
|
+
console.log(chalk.cyan(`
|
|
727
|
+
${messages.appTitle}`));
|
|
598
728
|
console.log(chalk.gray("\u2500".repeat(50)));
|
|
599
|
-
console.log(chalk.gray(
|
|
600
|
-
console.log(chalk.gray(
|
|
601
|
-
console.log(chalk.gray(
|
|
602
|
-
console.log(chalk.gray(
|
|
729
|
+
console.log(chalk.gray(`${messages.configLabels.provider}: ${chalk.white(config.provider)}`));
|
|
730
|
+
console.log(chalk.gray(`${messages.configLabels.model}: ${chalk.white(config.model)}`));
|
|
731
|
+
console.log(chalk.gray(`${messages.configLabels.maxTokens}: ${chalk.white(config.maxTokens)}`));
|
|
732
|
+
console.log(chalk.gray(`${messages.configLabels.runs}: ${chalk.white(config.runCount)}`));
|
|
603
733
|
console.log(
|
|
604
734
|
chalk.gray(
|
|
605
|
-
|
|
735
|
+
`${messages.configLabels.prompt}: ${chalk.white(config.prompt.substring(0, 50))}${config.prompt.length > 50 ? "..." : ""}`
|
|
606
736
|
)
|
|
607
737
|
);
|
|
608
738
|
console.log(chalk.gray("\u2500".repeat(50)));
|
|
609
|
-
console.log(chalk.yellow(
|
|
610
|
-
|
|
739
|
+
console.log(chalk.yellow(`
|
|
740
|
+
${messages.runningTests}
|
|
741
|
+
`));
|
|
742
|
+
console.log(chalk.gray(`${messages.streamingOutput}
|
|
743
|
+
`));
|
|
611
744
|
const results = await runMultipleTests(config);
|
|
612
745
|
const allMetrics = results.map((r) => calculateMetrics(r));
|
|
613
746
|
for (let i = 0; i < allMetrics.length; i++) {
|
|
614
|
-
console.log(chalk.gray(renderSingleResult(allMetrics[i], i)));
|
|
747
|
+
console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));
|
|
615
748
|
}
|
|
616
749
|
const stats = calculateStats(allMetrics);
|
|
617
|
-
console.log(chalk.cyan("\n" + renderReport(stats)));
|
|
618
|
-
console.log(chalk.green(
|
|
750
|
+
console.log(chalk.cyan("\n" + renderReport(stats, config.lang)));
|
|
751
|
+
console.log(chalk.green(`
|
|
752
|
+
${messages.testComplete}
|
|
753
|
+
`));
|
|
619
754
|
} catch (error) {
|
|
620
755
|
if (error instanceof Error) {
|
|
621
756
|
console.error(chalk.red(`
|
|
622
|
-
|
|
757
|
+
${messages.errorPrefix}: ${error.message}
|
|
623
758
|
`));
|
|
624
759
|
} else {
|
|
625
|
-
console.error(chalk.red(
|
|
760
|
+
console.error(chalk.red(`
|
|
761
|
+
${messages.unknownError}
|
|
762
|
+
`));
|
|
626
763
|
}
|
|
627
764
|
process.exit(1);
|
|
628
765
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/client.ts","../src/tokenizer.ts","../src/metrics.ts","../src/chart.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport type { Provider } from \"./config.js\";\nimport { parseConfig } from \"./config.js\";\nimport { runMultipleTests } from \"./client.js\";\nimport { calculateMetrics, calculateStats } from \"./metrics.js\";\nimport { renderReport, renderSingleResult } from \"./chart.js\";\n\nfunction getCliVersion(): string {\n try {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packagePath = join(currentDir, \"..\", \"package.json\");\n const packageJson = JSON.parse(readFileSync(packagePath, \"utf-8\")) as { version?: string };\n return packageJson.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"token-speed-test\")\n .description(\"A CLI tool to test LLM API token output speed\")\n .version(getCliVersion());\n\nprogram\n .option(\"-k, --api-key <key>\", \"API Key (required)\", \"\")\n .option(\"-p, --provider <provider>\", \"API provider: anthropic or openai\", \"anthropic\")\n .option(\"-u, --url <url>\", \"Custom API endpoint URL\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"--max-tokens <number>\", \"Maximum output tokens\", \"1024\")\n .option(\"-r, --runs <number>\", \"Number of test runs\", \"3\")\n .option(\"--prompt <text>\", \"Test prompt\", \"写一篇关于 AI 的短文\")\n .parse(process.argv);\n\nconst options = program.opts();\n\nasync function main() {\n try {\n // 解析配置\n const config = parseConfig({\n apiKey: options.apiKey,\n provider: options.provider as Provider,\n url: options.url,\n model: options.model,\n maxTokens: parseInt(options.maxTokens, 10),\n runs: parseInt(options.runs, 10),\n prompt: options.prompt,\n });\n\n // 显示配置信息\n console.log(chalk.cyan(\"\\n🚀 Token 速度测试工具\"));\n console.log(chalk.gray(\"─\".repeat(50)));\n console.log(chalk.gray(`Provider: ${chalk.white(config.provider)}`));\n console.log(chalk.gray(`Model: ${chalk.white(config.model)}`));\n console.log(chalk.gray(`Max Tokens: ${chalk.white(config.maxTokens)}`));\n console.log(chalk.gray(`Runs: ${chalk.white(config.runCount)}`));\n console.log(\n chalk.gray(\n `Prompt: ${chalk.white(config.prompt.substring(0, 50))}${config.prompt.length > 50 ? \"...\" : \"\"}`\n )\n );\n console.log(chalk.gray(\"─\".repeat(50)));\n\n // 执行测试\n console.log(chalk.yellow(\"\\n⏳ 正在运行测试...\\n\"));\n console.log(chalk.gray(\"模型输出 (流式):\\n\"));\n\n const results = await runMultipleTests(config);\n\n // 计算指标\n const allMetrics = results.map((r) => calculateMetrics(r));\n\n // 显示每次运行的结果\n for (let i = 0; i < allMetrics.length; i++) {\n console.log(chalk.gray(renderSingleResult(allMetrics[i], i)));\n }\n\n // 计算统计\n const stats = calculateStats(allMetrics);\n\n // 显示报告\n console.log(chalk.cyan(\"\\n\" + renderReport(stats)));\n\n console.log(chalk.green(\"\\n✅ 测试完成!\\n\"));\n } catch (error) {\n if (error instanceof Error) {\n console.error(chalk.red(`\\n❌ 错误: ${error.message}\\n`));\n } else {\n console.error(chalk.red(\"\\n❌ 发生未知错误\\n\"));\n }\n process.exit(1);\n }\n}\n\nvoid main();\n","export type Provider = \"anthropic\" | \"openai\";\n\nexport interface Config {\n provider: Provider;\n apiKey: string;\n baseURL?: string;\n model: string;\n maxTokens: number;\n runCount: number;\n prompt: string;\n}\n\nexport interface ParsedArgs {\n apiKey: string;\n provider?: Provider;\n url?: string;\n model?: string;\n maxTokens?: number;\n runs?: number;\n prompt?: string;\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n anthropic: \"claude-opus-4-5-20251101\",\n openai: \"gpt-5.2\",\n};\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_RUNS = 3;\nconst DEFAULT_PROMPT = \"写一篇关于 AI 的短文\";\n\n/**\n * 解析命令行参数并生成配置\n */\nexport function parseConfig(args: ParsedArgs): Config {\n const {\n apiKey,\n provider = \"anthropic\",\n url,\n model,\n maxTokens = DEFAULT_MAX_TOKENS,\n runs = DEFAULT_RUNS,\n prompt = DEFAULT_PROMPT,\n } = args;\n\n // 验证必填参数\n if (!apiKey || apiKey.trim() === \"\") {\n throw new Error(\"API Key is required. Use --api-key or -k to provide it.\");\n }\n\n // 验证 provider\n if (provider !== \"anthropic\" && provider !== \"openai\") {\n throw new Error(`Invalid provider: ${provider}. Must be 'anthropic' or 'openai'.`);\n }\n\n // 验证数值参数\n if (!Number.isFinite(maxTokens) || !Number.isInteger(maxTokens) || maxTokens <= 0) {\n throw new Error(`Invalid max-tokens: ${maxTokens}. Must be a positive integer.`);\n }\n\n if (!Number.isFinite(runs) || !Number.isInteger(runs) || runs <= 0) {\n throw new Error(`Invalid runs: ${runs}. Must be a positive integer.`);\n }\n\n // 使用默认模型或用户指定的模型\n const finalModel = model || DEFAULT_MODELS[provider];\n\n return {\n provider,\n apiKey: apiKey.trim(),\n baseURL: url?.trim(),\n model: finalModel,\n maxTokens,\n runCount: runs,\n prompt: prompt.trim(),\n };\n}\n\n/**\n * 获取默认模型名称\n */\nexport function getDefaultModel(provider: Provider): string {\n return DEFAULT_MODELS[provider];\n}\n\n/**\n * 验证配置有效性\n */\nexport function validateConfig(config: Config): { valid: boolean; error?: string } {\n if (!config.apiKey) {\n return { valid: false, error: \"API Key is required\" };\n }\n\n if (config.provider !== \"anthropic\" && config.provider !== \"openai\") {\n return { valid: false, error: `Invalid provider: ${config.provider}` };\n }\n\n if (\n !Number.isFinite(config.maxTokens) ||\n !Number.isInteger(config.maxTokens) ||\n config.maxTokens <= 0\n ) {\n return { valid: false, error: \"maxTokens must be a positive integer\" };\n }\n\n if (\n !Number.isFinite(config.runCount) ||\n !Number.isInteger(config.runCount) ||\n config.runCount <= 0\n ) {\n return { valid: false, error: \"runCount must be a positive integer\" };\n }\n\n if (!config.prompt || config.prompt.trim() === \"\") {\n return { valid: false, error: \"prompt cannot be empty\" };\n }\n\n return { valid: true };\n}\n// test\n","import { performance } from \"node:perf_hooks\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport OpenAI from \"openai\";\nimport type { Config } from \"./config.js\";\nimport type { StreamMetrics } from \"./metrics.js\";\nimport { createTokenizer } from \"./tokenizer.js\";\n\n/**\n * 执行 Anthropic API 流式测试\n */\nexport async function anthropicStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.messages.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const event of stream) {\n const currentTime = performance.now();\n\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n const text = event.delta.text;\n\n if (text && text.length > 0) {\n process.stdout.write(text);\n wroteOutput = true;\n const encoded = encoding.encode(text);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Anthropic API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 执行 OpenAI API 流式测试\n */\nexport async function openaiStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.chat.completions.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const chunk of stream) {\n const currentTime = performance.now();\n\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n const content = delta.content;\n\n if (content.length > 0) {\n process.stdout.write(content);\n wroteOutput = true;\n const encoded = encoding.encode(content);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`OpenAI API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 根据配置执行流式测试\n */\nexport async function streamTest(config: Config): Promise<StreamMetrics> {\n if (config.provider === \"anthropic\") {\n return anthropicStreamTest(config);\n } else {\n return openaiStreamTest(config);\n }\n}\n\n/**\n * 执行多次测试\n */\nexport async function runMultipleTests(config: Config): Promise<StreamMetrics[]> {\n const results: StreamMetrics[] = [];\n\n for (let i = 0; i < config.runCount; i++) {\n if (config.runCount > 1) {\n const label = `\\n[运行 ${i + 1}/${config.runCount}]`;\n console.log(label);\n console.log(\"-\".repeat(label.length - 1));\n }\n const result = await streamTest(config);\n results.push(result);\n }\n\n return results;\n}\n","import { encoding_for_model, get_encoding } from \"tiktoken\";\nimport type { TiktokenModel } from \"tiktoken\";\n\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\nexport function createTokenizer(model: string) {\n try {\n const normalized = model.trim();\n if (!normalized) {\n return get_encoding(FALLBACK_ENCODING);\n }\n return encoding_for_model(normalized as TiktokenModel);\n } catch {\n return get_encoding(FALLBACK_ENCODING);\n }\n}\n","/**\n * 单次流式测试的原始计时数据\n */\nexport interface StreamMetrics {\n ttft: number; // Time to First Token (ms)\n tokens: number[]; // 每个 token 的到达时间(相对开始时间,单位:ms)\n totalTokens: number;\n totalTime: number;\n}\n\n/**\n * 计算后的统计指标\n */\nexport interface CalculatedMetrics {\n ttft: number; // Time to First Token (ms)\n totalTime: number; // 总耗时 (ms)\n totalTokens: number; // 总 token 数\n averageSpeed: number; // 平均速度 (tokens/s)\n peakSpeed: number; // 峰值速度 (tokens/s)\n peakTps: number; // 峰值 TPS (tokens/s)\n tps: number[]; // 每秒 token 数 (TPS curve)\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n sampleSize: number;\n}\n\n/**\n * 计算 TTFT (Time to First Token)\n */\nexport function calculateTTFT(metrics: StreamMetrics): number {\n return metrics.ttft;\n}\n\n/**\n * 计算平均速度 (tokens/s)\n */\nexport function calculateAverageSpeed(metrics: StreamMetrics): number {\n if (metrics.totalTime <= 0) {\n return 0;\n }\n return (metrics.totalTokens / metrics.totalTime) * 1000;\n}\n\n/**\n * 计算峰值速度 - 最快连续 N 个 token 的平均速度\n */\nconst MIN_PEAK_WINDOW_MS = 50;\n\nexport function calculatePeakSpeed(metrics: StreamMetrics, windowSize: number = 10): number {\n if (metrics.tokens.length < windowSize) {\n // 如果 token 数少于窗口大小,使用全部 token 计算\n if (metrics.tokens.length < 2) {\n return 0;\n }\n const totalTime = metrics.tokens[metrics.tokens.length - 1] - metrics.tokens[0];\n const durationMs = Math.max(totalTime, MIN_PEAK_WINDOW_MS);\n return ((metrics.tokens.length - 1) / durationMs) * 1000;\n }\n\n let maxSpeed = 0;\n for (let i = 0; i <= metrics.tokens.length - windowSize; i++) {\n const startTime = metrics.tokens[i];\n const endTime = metrics.tokens[i + windowSize - 1];\n const duration = endTime - startTime;\n const durationMs = Math.max(duration, MIN_PEAK_WINDOW_MS);\n const speed = ((windowSize - 1) / durationMs) * 1000;\n maxSpeed = Math.max(maxSpeed, speed);\n }\n\n return maxSpeed;\n}\n\n/**\n * 计算 TPS (Tokens Per Second) 曲线\n */\nexport function calculateTPS(metrics: StreamMetrics): number[] {\n if (metrics.tokens.length === 0) {\n return [];\n }\n\n const totalDuration = metrics.tokens[metrics.tokens.length - 1];\n const totalSeconds = Math.ceil(totalDuration / 1000);\n\n if (totalSeconds <= 0) {\n return metrics.tokens.length > 0 ? [metrics.tokens.length] : [];\n }\n\n const tps: number[] = new Array(totalSeconds).fill(0);\n\n // 计算每秒内的 token 数\n for (const tokenTime of metrics.tokens) {\n const secondIndex = Math.floor(tokenTime / 1000);\n if (secondIndex < tps.length) {\n tps[secondIndex]++;\n }\n }\n\n return tps;\n}\n\n/**\n * 从 StreamMetrics 计算完整的指标\n */\nexport function calculateMetrics(metrics: StreamMetrics): CalculatedMetrics {\n const tps = calculateTPS(metrics);\n return {\n ttft: calculateTTFT(metrics),\n totalTime: metrics.totalTime,\n totalTokens: metrics.totalTokens,\n averageSpeed: calculateAverageSpeed(metrics),\n peakSpeed: calculatePeakSpeed(metrics),\n peakTps: tps.length > 0 ? Math.max(...tps) : 0,\n tps,\n };\n}\n\n/**\n * 计算一组数值的平均值\n */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((sum, v) => sum + v, 0) / values.length;\n}\n\n/**\n * 计算一组数值的标准差\n */\nfunction standardDeviation(values: number[]): number {\n if (values.length < 2) return 0;\n const avg = mean(values);\n const squareDiffs = values.map((v) => Math.pow(v - avg, 2));\n return Math.sqrt(mean(squareDiffs));\n}\n\n/**\n * 从多个 CalculatedMetrics 计算统计结果\n */\nexport function calculateStats(allMetrics: CalculatedMetrics[]): StatsResult {\n if (allMetrics.length === 0) {\n throw new Error(\"Cannot calculate stats from empty metrics array\");\n }\n\n const sampleSize = allMetrics.length;\n\n // 提取各项指标的数组\n const ttfts = allMetrics.map((m) => m.ttft);\n const totalTimes = allMetrics.map((m) => m.totalTime);\n const totalTokens = allMetrics.map((m) => m.totalTokens);\n const averageSpeeds = allMetrics.map((m) => m.averageSpeed);\n const peakSpeeds = allMetrics.map((m) => m.peakSpeed);\n const peakTpsValues = allMetrics.map((m) => m.peakTps);\n\n // 找到最长的 TPS 数组\n const maxTpsLength = Math.max(...allMetrics.map((m) => m.tps.length));\n const avgTps: number[] = [];\n for (let i = 0; i < maxTpsLength; i++) {\n const values = allMetrics.map((m) => m.tps[i] ?? 0);\n avgTps.push(mean(values));\n }\n\n return {\n mean: {\n ttft: mean(ttfts),\n totalTime: mean(totalTimes),\n totalTokens: mean(totalTokens),\n averageSpeed: mean(averageSpeeds),\n peakSpeed: mean(peakSpeeds),\n peakTps: mean(peakTpsValues),\n tps: avgTps,\n },\n min: {\n ttft: Math.min(...ttfts),\n totalTime: Math.min(...totalTimes),\n totalTokens: Math.min(...totalTokens),\n averageSpeed: Math.min(...averageSpeeds),\n peakSpeed: Math.min(...peakSpeeds),\n peakTps: Math.min(...peakTpsValues),\n tps: [],\n },\n max: {\n ttft: Math.max(...ttfts),\n totalTime: Math.max(...totalTimes),\n totalTokens: Math.max(...totalTokens),\n averageSpeed: Math.max(...averageSpeeds),\n peakSpeed: Math.max(...peakSpeeds),\n peakTps: Math.max(...peakTpsValues),\n tps: [],\n },\n stdDev: {\n ttft: standardDeviation(ttfts),\n totalTime: standardDeviation(totalTimes),\n totalTokens: standardDeviation(totalTokens),\n averageSpeed: standardDeviation(averageSpeeds),\n peakSpeed: standardDeviation(peakSpeeds),\n peakTps: standardDeviation(peakTpsValues),\n tps: [],\n },\n sampleSize,\n };\n}\n\n/**\n * 格式化速度显示\n */\nexport function formatSpeed(tokensPerSecond: number): string {\n return tokensPerSecond.toFixed(2);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatTime(ms: number): string {\n if (ms < 1000) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n","import stringWidth from \"string-width\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\n\nconst BLOCK_CHAR = \"█\";\nconst CHART_WIDTH = 50;\nconst CHART_HEIGHT = 10;\nconst STAT_LABEL_WIDTH = 15;\nconst STAT_VALUE_WIDTH = 10;\nconst Y_LABEL_WIDTH = 4;\n\nfunction padEndWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return text + \" \".repeat(width - currentWidth);\n}\n\nfunction padStartWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return \" \".repeat(width - currentWidth) + text;\n}\n\n/**\n * 渲染速度趋势图\n */\nexport function renderSpeedChart(tps: number[], maxSpeed?: number): string {\n if (tps.length === 0) {\n return \"No data available for chart\";\n }\n\n const actualMax = maxSpeed ?? Math.max(...tps, 1);\n const maxVal = Math.max(actualMax, 1);\n\n const buildRow = (label: string, bars: string) =>\n `│ ${padStartWidth(label, Y_LABEL_WIDTH)} ┤${bars} │`;\n const emptyRow = buildRow(\"0\", \" \".repeat(CHART_WIDTH));\n const chartWidth = stringWidth(emptyRow) - 2;\n const axisPrefix = `│ ${padStartWidth(\"\", Y_LABEL_WIDTH)} ┼`;\n\n const lines: string[] = [];\n lines.push(\"Token 速度趋势图 (TPS)\");\n\n // Y 轴标签和边框\n lines.push(\"┌\" + \"─\".repeat(chartWidth) + \"┐\");\n\n for (let row = CHART_HEIGHT - 1; row >= 0; row--) {\n const value = (row / (CHART_HEIGHT - 1)) * maxVal;\n const label = value.toFixed(0);\n\n let bars = \"\";\n for (let col = 0; col < CHART_WIDTH; col++) {\n const index = Math.floor((col / CHART_WIDTH) * tps.length);\n const tpsValue = tps[index] ?? 0;\n const normalizedHeight = (tpsValue / maxVal) * (CHART_HEIGHT - 1);\n bars += normalizedHeight >= row ? BLOCK_CHAR : \" \";\n }\n\n lines.push(buildRow(label, bars));\n }\n\n // X 轴\n lines.push(`${axisPrefix}${\"─\".repeat(CHART_WIDTH)} │`);\n lines.push(\"└\" + \"─\".repeat(chartWidth) + \"┘\");\n\n // X 轴标签 (时间标记)\n const xLabels = generateXLabels(tps.length, 6);\n const labelLine = new Array(CHART_WIDTH).fill(\" \");\n const maxIndex = Math.max(tps.length - 1, 1);\n for (const label of xLabels) {\n const seconds = parseInt(label.replace(\"s\", \"\"), 10);\n const position = Math.min(\n CHART_WIDTH - 1,\n Math.round((seconds / maxIndex) * (CHART_WIDTH - 1))\n );\n for (let i = 0; i < label.length && position + i < CHART_WIDTH; i++) {\n labelLine[position + i] = label[i];\n }\n }\n lines.push(\" \".repeat(stringWidth(axisPrefix)) + labelLine.join(\"\"));\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 生成 X 轴时间标签\n */\nfunction generateXLabels(dataPoints: number, maxLabels: number): string[] {\n if (dataPoints <= 1) {\n return [\"0s\"];\n }\n\n const labels: string[] = [];\n const step = Math.max(1, Math.floor(dataPoints / maxLabels));\n\n for (let i = 0; i < dataPoints; i += step) {\n labels.push(`${i}s`);\n }\n\n // 确保最后一个标签是结束时间\n if (labels[labels.length - 1] !== `${dataPoints - 1}s`) {\n labels.push(`${dataPoints - 1}s`);\n }\n\n return labels;\n}\n\n/**\n * 渲染 TPS 直方图\n */\nexport function renderTPSHistogram(tps: number[]): string {\n if (tps.length === 0) {\n return \"No TPS data available\";\n }\n\n const lines: string[] = [];\n lines.push(\"TPS 分布\");\n\n // 计算分布区间\n const maxTps = Math.max(...tps, 1);\n const buckets = 10;\n const bucketSize = maxTps / buckets;\n const histogram = new Array(buckets).fill(0);\n\n for (const t of tps) {\n const bucketIndex = Math.min(Math.floor(t / bucketSize), buckets - 1);\n histogram[bucketIndex]++;\n }\n\n const maxCount = Math.max(...histogram, 1);\n\n const labels = histogram.map((_, i) => {\n const bucketStart = (i * bucketSize).toFixed(1);\n const bucketEnd = ((i + 1) * bucketSize).toFixed(1);\n return `${bucketStart}-${bucketEnd}`;\n });\n const labelWidth = Math.max(...labels.map((l) => stringWidth(l)));\n\n for (let i = 0; i < buckets; i++) {\n const label = padEndWidth(labels[i], labelWidth);\n const count = histogram[i];\n const barLength = Math.round((count / maxCount) * CHART_WIDTH);\n const bar = BLOCK_CHAR.repeat(barLength);\n\n const countSuffix = count > 0 ? ` ${count}` : \"\";\n lines.push(`${label} │${bar}${countSuffix}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染统计汇总表\n */\nexport function renderStatsTable(stats: StatsResult): string {\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(\"统计汇总 (N=\" + stats.sampleSize + \")\");\n\n // 表头\n const headerRow =\n \"│ \" +\n padEndWidth(\"指标\", STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(\"均值\", STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(\"最小值\", STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(\"最大值\", STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(\"标准差\", STAT_VALUE_WIDTH) +\n \" │\";\n\n const tableWidth = stringWidth(headerRow) - 2;\n lines.push(\"┌\" + \"─\".repeat(tableWidth) + \"┐\");\n lines.push(headerRow);\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // TTFT\n lines.push(\n formatStatRow(\n \"TTFT (ms)\",\n stats.mean.ttft,\n stats.min.ttft,\n stats.max.ttft,\n stats.stdDev.ttft,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总耗时\n lines.push(\n formatStatRow(\n \"总耗时 (ms)\",\n stats.mean.totalTime,\n stats.min.totalTime,\n stats.max.totalTime,\n stats.stdDev.totalTime,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总 token 数\n lines.push(\n formatStatRow(\n \"总 Token 数\",\n stats.mean.totalTokens,\n stats.min.totalTokens,\n stats.max.totalTokens,\n stats.stdDev.totalTokens,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 平均速度\n lines.push(\n formatStatRow(\n \"平均速度\",\n stats.mean.averageSpeed,\n stats.min.averageSpeed,\n stats.max.averageSpeed,\n stats.stdDev.averageSpeed,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值速度\n lines.push(\n formatStatRow(\n \"峰值速度\",\n stats.mean.peakSpeed,\n stats.min.peakSpeed,\n stats.max.peakSpeed,\n stats.stdDev.peakSpeed,\n \"f\"\n )\n );\n\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值 TPS\n lines.push(\n formatStatRow(\n \"峰值 TPS\",\n stats.mean.peakTps,\n stats.min.peakTps,\n stats.max.peakTps,\n stats.stdDev.peakTps,\n \"f\"\n )\n );\n\n lines.push(\"└\" + \"─\".repeat(tableWidth) + \"┘\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化统计表格的一行\n */\nfunction formatStatRow(\n label: string,\n mean: number,\n min: number,\n max: number,\n stdDev: number,\n format: \"f\" | \"d\"\n): string {\n const fmt = (n: number) => (format === \"f\" ? n.toFixed(2) : n.toFixed(0));\n\n return (\n \"│ \" +\n padEndWidth(label, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(mean), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(stdDev), STAT_VALUE_WIDTH) +\n \" │\"\n );\n}\n\n/**\n * 格式化时间显示(带小数)\n */\nfunction formatTimeWithDecimals(ms: number): string {\n if (ms === Math.floor(ms)) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${ms.toFixed(2)}ms`;\n}\n\n/**\n * 渲染单次测试结果\n */\nexport function renderSingleResult(metrics: CalculatedMetrics, runIndex: number): string {\n const lines: string[] = [];\n lines.push(`\\n[运行 ${runIndex + 1}]`);\n lines.push(` TTFT: ${formatTimeWithDecimals(metrics.ttft)}`);\n lines.push(` 总耗时: ${formatTimeWithDecimals(metrics.totalTime)}`);\n lines.push(` 总 Token 数: ${metrics.totalTokens}`);\n lines.push(` 平均速度: ${metrics.averageSpeed.toFixed(2)} tokens/s`);\n lines.push(` 峰值速度: ${metrics.peakSpeed.toFixed(2)} tokens/s`);\n lines.push(` 峰值 TPS: ${metrics.peakTps.toFixed(2)} tokens/s`);\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染完整的测试报告\n */\nexport function renderReport(stats: StatsResult): string {\n const lines: string[] = [];\n\n lines.push(\"\\n\" + \"═\".repeat(72));\n lines.push(\"Token 速度测试报告\");\n lines.push(\"═\".repeat(72));\n\n // 汇总统计\n lines.push(renderStatsTable(stats));\n\n // 速度趋势图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderSpeedChart(stats.mean.tps));\n }\n\n // TPS 直方图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderTPSHistogram(stats.mean.tps));\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["mean"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/i18n.ts","../src/config.ts","../src/client.ts","../src/tokenizer.ts","../src/metrics.ts","../src/chart.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport type { Provider } from \"./config.js\";\nimport { parseConfig } from \"./config.js\";\nimport { runMultipleTests } from \"./client.js\";\nimport { calculateMetrics, calculateStats } from \"./metrics.js\";\nimport { renderReport, renderSingleResult } from \"./chart.js\";\nimport { DEFAULT_LANG, getMessages } from \"./i18n.js\";\n\nfunction getCliVersion(): string {\n try {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packagePath = join(currentDir, \"..\", \"package.json\");\n const packageJson = JSON.parse(readFileSync(packagePath, \"utf-8\")) as { version?: string };\n return packageJson.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"token-speed-test\")\n .description(\"A CLI tool to test LLM API token output speed\")\n .version(getCliVersion());\n\nprogram\n .option(\"-k, --api-key <key>\", \"API Key (required)\", \"\")\n .option(\"-p, --provider <provider>\", \"API provider: anthropic or openai\", \"anthropic\")\n .option(\"-u, --url <url>\", \"Custom API endpoint URL\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"--max-tokens <number>\", \"Maximum output tokens\", \"1024\")\n .option(\"-r, --runs <number>\", \"Number of test runs\", \"3\")\n .option(\"--prompt <text>\", \"Test prompt\")\n .option(\"--lang <lang>\", \"Output language: zh or en\", \"zh\")\n .parse(process.argv);\n\nconst options = program.opts();\n\nasync function main() {\n let messages = getMessages(DEFAULT_LANG);\n try {\n // 解析配置\n const config = parseConfig({\n apiKey: options.apiKey,\n provider: options.provider as Provider,\n url: options.url,\n model: options.model,\n maxTokens: parseInt(options.maxTokens, 10),\n runs: parseInt(options.runs, 10),\n prompt: options.prompt,\n lang: options.lang,\n });\n messages = getMessages(config.lang);\n\n // 显示配置信息\n console.log(chalk.cyan(`\\n${messages.appTitle}`));\n console.log(chalk.gray(\"─\".repeat(50)));\n console.log(chalk.gray(`${messages.configLabels.provider}: ${chalk.white(config.provider)}`));\n console.log(chalk.gray(`${messages.configLabels.model}: ${chalk.white(config.model)}`));\n console.log(chalk.gray(`${messages.configLabels.maxTokens}: ${chalk.white(config.maxTokens)}`));\n console.log(chalk.gray(`${messages.configLabels.runs}: ${chalk.white(config.runCount)}`));\n console.log(\n chalk.gray(\n `${messages.configLabels.prompt}: ${chalk.white(config.prompt.substring(0, 50))}${\n config.prompt.length > 50 ? \"...\" : \"\"\n }`\n )\n );\n console.log(chalk.gray(\"─\".repeat(50)));\n\n // 执行测试\n console.log(chalk.yellow(`\\n${messages.runningTests}\\n`));\n console.log(chalk.gray(`${messages.streamingOutput}\\n`));\n\n const results = await runMultipleTests(config);\n\n // 计算指标\n const allMetrics = results.map((r) => calculateMetrics(r));\n\n // 显示每次运行的结果\n for (let i = 0; i < allMetrics.length; i++) {\n console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));\n }\n\n // 计算统计\n const stats = calculateStats(allMetrics);\n\n // 显示报告\n console.log(chalk.cyan(\"\\n\" + renderReport(stats, config.lang)));\n\n console.log(chalk.green(`\\n${messages.testComplete}\\n`));\n } catch (error) {\n if (error instanceof Error) {\n console.error(chalk.red(`\\n${messages.errorPrefix}: ${error.message}\\n`));\n } else {\n console.error(chalk.red(`\\n${messages.unknownError}\\n`));\n }\n process.exit(1);\n }\n}\n\nvoid main();\n","export const SUPPORTED_LANGS = [\"zh\", \"en\"] as const;\nexport type Lang = (typeof SUPPORTED_LANGS)[number];\n\nexport const DEFAULT_LANG: Lang = \"zh\";\n\nexport interface Messages {\n defaultPrompt: string;\n appTitle: string;\n runningTests: string;\n streamingOutput: string;\n testComplete: string;\n errorPrefix: string;\n unknownError: string;\n configLabels: {\n provider: string;\n model: string;\n maxTokens: string;\n runs: string;\n prompt: string;\n };\n runLabel: (index: number) => string;\n runProgressLabel: (current: number, total: number) => string;\n reportTitle: string;\n speedChartTitle: string;\n tpsHistogramTitle: string;\n noChartData: string;\n noTpsData: string;\n statsSummaryTitle: (sampleSize: number) => string;\n statsHeaders: {\n metric: string;\n mean: string;\n min: string;\n max: string;\n stdDev: string;\n };\n statsLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n resultLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n}\n\nconst zhMessages: Messages = {\n defaultPrompt: \"写一篇关于 AI 的短文\",\n appTitle: \"🚀 Token 速度测试工具\",\n runningTests: \"⏳ 正在运行测试...\",\n streamingOutput: \"模型输出 (流式):\",\n testComplete: \"✅ 测试完成!\",\n errorPrefix: \"❌ 错误\",\n unknownError: \"❌ 发生未知错误\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[运行 ${index}]`,\n runProgressLabel: (current: number, total: number) => `[运行 ${current}/${total}]`,\n reportTitle: \"Token 速度测试报告\",\n speedChartTitle: \"Token 速度趋势图 (TPS)\",\n tpsHistogramTitle: \"TPS 分布\",\n noChartData: \"没有可用于图表的数据\",\n noTpsData: \"没有 TPS 数据可用\",\n statsSummaryTitle: (sampleSize: number) => `统计汇总 (N=${sampleSize})`,\n statsHeaders: {\n metric: \"指标\",\n mean: \"均值\",\n min: \"最小值\",\n max: \"最大值\",\n stdDev: \"标准差\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"总耗时 (ms)\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"总耗时\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n};\n\nconst enMessages: Messages = {\n defaultPrompt: \"Write a short essay about AI\",\n appTitle: \"🚀 Token Speed Test\",\n runningTests: \"⏳ Running tests...\",\n streamingOutput: \"Model output (streaming):\",\n testComplete: \"✅ Tests complete!\",\n errorPrefix: \"❌ Error\",\n unknownError: \"❌ An unknown error occurred\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[Run ${index}]`,\n runProgressLabel: (current: number, total: number) => `[Run ${current}/${total}]`,\n reportTitle: \"Token Speed Test Report\",\n speedChartTitle: \"Token Speed Trend (TPS)\",\n tpsHistogramTitle: \"TPS Distribution\",\n noChartData: \"No data available for chart\",\n noTpsData: \"No TPS data available\",\n statsSummaryTitle: (sampleSize: number) => `Summary (N=${sampleSize})`,\n statsHeaders: {\n metric: \"Metric\",\n mean: \"Mean\",\n min: \"Min\",\n max: \"Max\",\n stdDev: \"Std Dev\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"Total Time (ms)\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"Total Time\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n};\n\nexport function isSupportedLang(value: string): value is Lang {\n return SUPPORTED_LANGS.includes(value as Lang);\n}\n\nexport function resolveLang(value?: string): Lang {\n if (!value) {\n return DEFAULT_LANG;\n }\n const normalized = value.toLowerCase();\n if (!isSupportedLang(normalized)) {\n throw new Error(`Invalid lang: ${value}. Must be 'zh' or 'en'.`);\n }\n return normalized;\n}\n\nexport function getMessages(lang: Lang): Messages {\n return lang === \"en\" ? enMessages : zhMessages;\n}\n","import { getMessages, resolveLang, type Lang } from \"./i18n.js\";\n\nexport type Provider = \"anthropic\" | \"openai\";\n\nexport interface Config {\n provider: Provider;\n apiKey: string;\n baseURL?: string;\n model: string;\n maxTokens: number;\n runCount: number;\n prompt: string;\n lang: Lang;\n}\n\nexport interface ParsedArgs {\n apiKey: string;\n provider?: Provider;\n url?: string;\n model?: string;\n maxTokens?: number;\n runs?: number;\n prompt?: string;\n lang?: string;\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n anthropic: \"claude-opus-4-5-20251101\",\n openai: \"gpt-5.2\",\n};\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_RUNS = 3;\n/**\n * 解析命令行参数并生成配置\n */\nexport function parseConfig(args: ParsedArgs): Config {\n const {\n apiKey,\n provider = \"anthropic\",\n url,\n model,\n maxTokens = DEFAULT_MAX_TOKENS,\n runs = DEFAULT_RUNS,\n prompt,\n lang: langInput,\n } = args;\n const lang = resolveLang(langInput);\n const messages = getMessages(lang);\n const finalPrompt = prompt ?? messages.defaultPrompt;\n\n // 验证必填参数\n if (!apiKey || apiKey.trim() === \"\") {\n throw new Error(\"API Key is required. Use --api-key or -k to provide it.\");\n }\n\n // 验证 provider\n if (provider !== \"anthropic\" && provider !== \"openai\") {\n throw new Error(`Invalid provider: ${provider}. Must be 'anthropic' or 'openai'.`);\n }\n\n // 验证数值参数\n if (!Number.isFinite(maxTokens) || !Number.isInteger(maxTokens) || maxTokens <= 0) {\n throw new Error(`Invalid max-tokens: ${maxTokens}. Must be a positive integer.`);\n }\n\n if (!Number.isFinite(runs) || !Number.isInteger(runs) || runs <= 0) {\n throw new Error(`Invalid runs: ${runs}. Must be a positive integer.`);\n }\n\n // 使用默认模型或用户指定的模型\n const finalModel = model || DEFAULT_MODELS[provider];\n\n return {\n provider,\n apiKey: apiKey.trim(),\n baseURL: url?.trim(),\n model: finalModel,\n maxTokens,\n runCount: runs,\n prompt: finalPrompt.trim(),\n lang,\n };\n}\n\n/**\n * 获取默认模型名称\n */\nexport function getDefaultModel(provider: Provider): string {\n return DEFAULT_MODELS[provider];\n}\n\n/**\n * 验证配置有效性\n */\nexport function validateConfig(config: Config): { valid: boolean; error?: string } {\n if (!config.apiKey) {\n return { valid: false, error: \"API Key is required\" };\n }\n\n if (config.provider !== \"anthropic\" && config.provider !== \"openai\") {\n return { valid: false, error: `Invalid provider: ${config.provider}` };\n }\n\n if (\n !Number.isFinite(config.maxTokens) ||\n !Number.isInteger(config.maxTokens) ||\n config.maxTokens <= 0\n ) {\n return { valid: false, error: \"maxTokens must be a positive integer\" };\n }\n\n if (\n !Number.isFinite(config.runCount) ||\n !Number.isInteger(config.runCount) ||\n config.runCount <= 0\n ) {\n return { valid: false, error: \"runCount must be a positive integer\" };\n }\n\n if (!config.prompt || config.prompt.trim() === \"\") {\n return { valid: false, error: \"prompt cannot be empty\" };\n }\n\n if (config.lang !== \"zh\" && config.lang !== \"en\") {\n return { valid: false, error: `Invalid lang: ${config.lang}` };\n }\n\n return { valid: true };\n}\n// test\n","import { performance } from \"node:perf_hooks\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport OpenAI from \"openai\";\nimport type { Config } from \"./config.js\";\nimport { getMessages } from \"./i18n.js\";\nimport type { StreamMetrics } from \"./metrics.js\";\nimport { createTokenizer } from \"./tokenizer.js\";\n\n/**\n * 执行 Anthropic API 流式测试\n */\nexport async function anthropicStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.messages.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const event of stream) {\n const currentTime = performance.now();\n\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n const text = event.delta.text;\n\n if (text && text.length > 0) {\n process.stdout.write(text);\n wroteOutput = true;\n const encoded = encoding.encode(text);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Anthropic API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 执行 OpenAI API 流式测试\n */\nexport async function openaiStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.chat.completions.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const chunk of stream) {\n const currentTime = performance.now();\n\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n const content = delta.content;\n\n if (content.length > 0) {\n process.stdout.write(content);\n wroteOutput = true;\n const encoded = encoding.encode(content);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`OpenAI API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 根据配置执行流式测试\n */\nexport async function streamTest(config: Config): Promise<StreamMetrics> {\n if (config.provider === \"anthropic\") {\n return anthropicStreamTest(config);\n } else {\n return openaiStreamTest(config);\n }\n}\n\n/**\n * 执行多次测试\n */\nexport async function runMultipleTests(config: Config): Promise<StreamMetrics[]> {\n const results: StreamMetrics[] = [];\n const messages = getMessages(config.lang);\n\n for (let i = 0; i < config.runCount; i++) {\n if (config.runCount > 1) {\n const label = `\\n${messages.runProgressLabel(i + 1, config.runCount)}`;\n console.log(label);\n console.log(\"-\".repeat(label.length - 1));\n }\n const result = await streamTest(config);\n results.push(result);\n }\n\n return results;\n}\n","import { encoding_for_model, get_encoding } from \"tiktoken\";\nimport type { TiktokenModel } from \"tiktoken\";\n\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\nexport function createTokenizer(model: string) {\n try {\n const normalized = model.trim();\n if (!normalized) {\n return get_encoding(FALLBACK_ENCODING);\n }\n return encoding_for_model(normalized as TiktokenModel);\n } catch {\n return get_encoding(FALLBACK_ENCODING);\n }\n}\n","/**\n * 单次流式测试的原始计时数据\n */\nexport interface StreamMetrics {\n ttft: number; // Time to First Token (ms)\n tokens: number[]; // 每个 token 的到达时间(相对开始时间,单位:ms)\n totalTokens: number;\n totalTime: number;\n}\n\n/**\n * 计算后的统计指标\n */\nexport interface CalculatedMetrics {\n ttft: number; // Time to First Token (ms)\n totalTime: number; // 总耗时 (ms)\n totalTokens: number; // 总 token 数\n averageSpeed: number; // 平均速度 (tokens/s)\n peakSpeed: number; // 峰值速度 (tokens/s)\n peakTps: number; // 峰值 TPS (tokens/s)\n tps: number[]; // 每秒 token 数 (TPS curve)\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n sampleSize: number;\n}\n\n/**\n * 计算 TTFT (Time to First Token)\n */\nexport function calculateTTFT(metrics: StreamMetrics): number {\n return metrics.ttft;\n}\n\n/**\n * 计算平均速度 (tokens/s)\n */\nexport function calculateAverageSpeed(metrics: StreamMetrics): number {\n if (metrics.totalTime <= 0) {\n return 0;\n }\n return (metrics.totalTokens / metrics.totalTime) * 1000;\n}\n\n/**\n * 计算峰值速度 - 最快连续 N 个 token 的平均速度\n */\nconst MIN_PEAK_WINDOW_MS = 50;\n\nexport function calculatePeakSpeed(metrics: StreamMetrics, windowSize: number = 10): number {\n if (metrics.tokens.length < windowSize) {\n // 如果 token 数少于窗口大小,使用全部 token 计算\n if (metrics.tokens.length < 2) {\n return 0;\n }\n const totalTime = metrics.tokens[metrics.tokens.length - 1] - metrics.tokens[0];\n const durationMs = Math.max(totalTime, MIN_PEAK_WINDOW_MS);\n return ((metrics.tokens.length - 1) / durationMs) * 1000;\n }\n\n let maxSpeed = 0;\n for (let i = 0; i <= metrics.tokens.length - windowSize; i++) {\n const startTime = metrics.tokens[i];\n const endTime = metrics.tokens[i + windowSize - 1];\n const duration = endTime - startTime;\n const durationMs = Math.max(duration, MIN_PEAK_WINDOW_MS);\n const speed = ((windowSize - 1) / durationMs) * 1000;\n maxSpeed = Math.max(maxSpeed, speed);\n }\n\n return maxSpeed;\n}\n\n/**\n * 计算 TPS (Tokens Per Second) 曲线\n */\nexport function calculateTPS(metrics: StreamMetrics): number[] {\n if (metrics.tokens.length === 0) {\n return [];\n }\n\n const totalDuration = metrics.tokens[metrics.tokens.length - 1];\n const totalSeconds = Math.ceil(totalDuration / 1000);\n\n if (totalSeconds <= 0) {\n return metrics.tokens.length > 0 ? [metrics.tokens.length] : [];\n }\n\n const tps: number[] = new Array(totalSeconds).fill(0);\n\n // 计算每秒内的 token 数\n for (const tokenTime of metrics.tokens) {\n const secondIndex = Math.floor(tokenTime / 1000);\n if (secondIndex < tps.length) {\n tps[secondIndex]++;\n }\n }\n\n return tps;\n}\n\n/**\n * 从 StreamMetrics 计算完整的指标\n */\nexport function calculateMetrics(metrics: StreamMetrics): CalculatedMetrics {\n const tps = calculateTPS(metrics);\n return {\n ttft: calculateTTFT(metrics),\n totalTime: metrics.totalTime,\n totalTokens: metrics.totalTokens,\n averageSpeed: calculateAverageSpeed(metrics),\n peakSpeed: calculatePeakSpeed(metrics),\n peakTps: tps.length > 0 ? Math.max(...tps) : 0,\n tps,\n };\n}\n\n/**\n * 计算一组数值的平均值\n */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((sum, v) => sum + v, 0) / values.length;\n}\n\n/**\n * 计算一组数值的标准差\n */\nfunction standardDeviation(values: number[]): number {\n if (values.length < 2) return 0;\n const avg = mean(values);\n const squareDiffs = values.map((v) => Math.pow(v - avg, 2));\n return Math.sqrt(mean(squareDiffs));\n}\n\n/**\n * 从多个 CalculatedMetrics 计算统计结果\n */\nexport function calculateStats(allMetrics: CalculatedMetrics[]): StatsResult {\n if (allMetrics.length === 0) {\n throw new Error(\"Cannot calculate stats from empty metrics array\");\n }\n\n const sampleSize = allMetrics.length;\n\n // 提取各项指标的数组\n const ttfts = allMetrics.map((m) => m.ttft);\n const totalTimes = allMetrics.map((m) => m.totalTime);\n const totalTokens = allMetrics.map((m) => m.totalTokens);\n const averageSpeeds = allMetrics.map((m) => m.averageSpeed);\n const peakSpeeds = allMetrics.map((m) => m.peakSpeed);\n const peakTpsValues = allMetrics.map((m) => m.peakTps);\n\n // 找到最长的 TPS 数组\n const maxTpsLength = Math.max(...allMetrics.map((m) => m.tps.length));\n const avgTps: number[] = [];\n for (let i = 0; i < maxTpsLength; i++) {\n const values = allMetrics.map((m) => m.tps[i] ?? 0);\n avgTps.push(mean(values));\n }\n\n return {\n mean: {\n ttft: mean(ttfts),\n totalTime: mean(totalTimes),\n totalTokens: mean(totalTokens),\n averageSpeed: mean(averageSpeeds),\n peakSpeed: mean(peakSpeeds),\n peakTps: mean(peakTpsValues),\n tps: avgTps,\n },\n min: {\n ttft: Math.min(...ttfts),\n totalTime: Math.min(...totalTimes),\n totalTokens: Math.min(...totalTokens),\n averageSpeed: Math.min(...averageSpeeds),\n peakSpeed: Math.min(...peakSpeeds),\n peakTps: Math.min(...peakTpsValues),\n tps: [],\n },\n max: {\n ttft: Math.max(...ttfts),\n totalTime: Math.max(...totalTimes),\n totalTokens: Math.max(...totalTokens),\n averageSpeed: Math.max(...averageSpeeds),\n peakSpeed: Math.max(...peakSpeeds),\n peakTps: Math.max(...peakTpsValues),\n tps: [],\n },\n stdDev: {\n ttft: standardDeviation(ttfts),\n totalTime: standardDeviation(totalTimes),\n totalTokens: standardDeviation(totalTokens),\n averageSpeed: standardDeviation(averageSpeeds),\n peakSpeed: standardDeviation(peakSpeeds),\n peakTps: standardDeviation(peakTpsValues),\n tps: [],\n },\n sampleSize,\n };\n}\n\n/**\n * 格式化速度显示\n */\nexport function formatSpeed(tokensPerSecond: number): string {\n return tokensPerSecond.toFixed(2);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatTime(ms: number): string {\n if (ms < 1000) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n","import stringWidth from \"string-width\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\nimport { DEFAULT_LANG, getMessages, type Lang } from \"./i18n.js\";\n\nconst BLOCK_CHAR = \"█\";\nconst CHART_WIDTH = 50;\nconst CHART_HEIGHT = 10;\nconst STAT_LABEL_WIDTH = 15;\nconst STAT_VALUE_WIDTH = 10;\nconst Y_LABEL_WIDTH = 4;\n\nfunction padEndWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return text + \" \".repeat(width - currentWidth);\n}\n\nfunction padStartWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return \" \".repeat(width - currentWidth) + text;\n}\n\n/**\n * 渲染速度趋势图\n */\nexport function renderSpeedChart(\n tps: number[],\n maxSpeed?: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noChartData;\n }\n\n const actualMax = maxSpeed ?? Math.max(...tps, 1);\n const maxVal = Math.max(actualMax, 1);\n\n const buildRow = (label: string, bars: string) =>\n `│ ${padStartWidth(label, Y_LABEL_WIDTH)} ┤${bars} │`;\n const emptyRow = buildRow(\"0\", \" \".repeat(CHART_WIDTH));\n const chartWidth = stringWidth(emptyRow) - 2;\n const axisPrefix = `│ ${padStartWidth(\"\", Y_LABEL_WIDTH)} ┼`;\n\n const lines: string[] = [];\n lines.push(messages.speedChartTitle);\n\n // Y 轴标签和边框\n lines.push(\"┌\" + \"─\".repeat(chartWidth) + \"┐\");\n\n for (let row = CHART_HEIGHT - 1; row >= 0; row--) {\n const value = (row / (CHART_HEIGHT - 1)) * maxVal;\n const label = value.toFixed(0);\n\n let bars = \"\";\n for (let col = 0; col < CHART_WIDTH; col++) {\n const index = Math.floor((col / CHART_WIDTH) * tps.length);\n const tpsValue = tps[index] ?? 0;\n const normalizedHeight = (tpsValue / maxVal) * (CHART_HEIGHT - 1);\n bars += normalizedHeight >= row ? BLOCK_CHAR : \" \";\n }\n\n lines.push(buildRow(label, bars));\n }\n\n // X 轴\n lines.push(`${axisPrefix}${\"─\".repeat(CHART_WIDTH)} │`);\n lines.push(\"└\" + \"─\".repeat(chartWidth) + \"┘\");\n\n // X 轴标签 (时间标记)\n const xLabels = generateXLabels(tps.length, 6);\n const labelLine = new Array(CHART_WIDTH).fill(\" \");\n const maxIndex = Math.max(tps.length - 1, 1);\n for (const label of xLabels) {\n const seconds = parseInt(label.replace(\"s\", \"\"), 10);\n const position = Math.min(\n CHART_WIDTH - 1,\n Math.round((seconds / maxIndex) * (CHART_WIDTH - 1))\n );\n for (let i = 0; i < label.length && position + i < CHART_WIDTH; i++) {\n labelLine[position + i] = label[i];\n }\n }\n lines.push(\" \".repeat(stringWidth(axisPrefix)) + labelLine.join(\"\"));\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 生成 X 轴时间标签\n */\nfunction generateXLabels(dataPoints: number, maxLabels: number): string[] {\n if (dataPoints <= 1) {\n return [\"0s\"];\n }\n\n const labels: string[] = [];\n const step = Math.max(1, Math.floor(dataPoints / maxLabels));\n\n for (let i = 0; i < dataPoints; i += step) {\n labels.push(`${i}s`);\n }\n\n // 确保最后一个标签是结束时间\n if (labels[labels.length - 1] !== `${dataPoints - 1}s`) {\n labels.push(`${dataPoints - 1}s`);\n }\n\n return labels;\n}\n\n/**\n * 渲染 TPS 直方图\n */\nexport function renderTPSHistogram(tps: number[], lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noTpsData;\n }\n\n const lines: string[] = [];\n lines.push(messages.tpsHistogramTitle);\n\n // 计算分布区间\n const maxTps = Math.max(...tps, 1);\n const buckets = 10;\n const bucketSize = maxTps / buckets;\n const histogram = new Array(buckets).fill(0);\n\n for (const t of tps) {\n const bucketIndex = Math.min(Math.floor(t / bucketSize), buckets - 1);\n histogram[bucketIndex]++;\n }\n\n const maxCount = Math.max(...histogram, 1);\n\n const labels = histogram.map((_, i) => {\n const bucketStart = (i * bucketSize).toFixed(1);\n const bucketEnd = ((i + 1) * bucketSize).toFixed(1);\n return `${bucketStart}-${bucketEnd}`;\n });\n const labelWidth = Math.max(...labels.map((l) => stringWidth(l)));\n\n for (let i = 0; i < buckets; i++) {\n const label = padEndWidth(labels[i], labelWidth);\n const count = histogram[i];\n const barLength = Math.round((count / maxCount) * CHART_WIDTH);\n const bar = BLOCK_CHAR.repeat(barLength);\n\n const countSuffix = count > 0 ? ` ${count}` : \"\";\n lines.push(`${label} │${bar}${countSuffix}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染统计汇总表\n */\nexport function renderStatsTable(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(messages.statsSummaryTitle(stats.sampleSize));\n\n // 表头\n const headerRow =\n \"│ \" +\n padEndWidth(messages.statsHeaders.metric, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.mean, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.max, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.stdDev, STAT_VALUE_WIDTH) +\n \" │\";\n\n const tableWidth = stringWidth(headerRow) - 2;\n lines.push(\"┌\" + \"─\".repeat(tableWidth) + \"┐\");\n lines.push(headerRow);\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // TTFT\n lines.push(\n formatStatRow(\n messages.statsLabels.ttft,\n stats.mean.ttft,\n stats.min.ttft,\n stats.max.ttft,\n stats.stdDev.ttft,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总耗时\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTime,\n stats.mean.totalTime,\n stats.min.totalTime,\n stats.max.totalTime,\n stats.stdDev.totalTime,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总 token 数\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTokens,\n stats.mean.totalTokens,\n stats.min.totalTokens,\n stats.max.totalTokens,\n stats.stdDev.totalTokens,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 平均速度\n lines.push(\n formatStatRow(\n messages.statsLabels.averageSpeed,\n stats.mean.averageSpeed,\n stats.min.averageSpeed,\n stats.max.averageSpeed,\n stats.stdDev.averageSpeed,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值速度\n lines.push(\n formatStatRow(\n messages.statsLabels.peakSpeed,\n stats.mean.peakSpeed,\n stats.min.peakSpeed,\n stats.max.peakSpeed,\n stats.stdDev.peakSpeed,\n \"f\"\n )\n );\n\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值 TPS\n lines.push(\n formatStatRow(\n messages.statsLabels.peakTps,\n stats.mean.peakTps,\n stats.min.peakTps,\n stats.max.peakTps,\n stats.stdDev.peakTps,\n \"f\"\n )\n );\n\n lines.push(\"└\" + \"─\".repeat(tableWidth) + \"┘\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化统计表格的一行\n */\nfunction formatStatRow(\n label: string,\n mean: number,\n min: number,\n max: number,\n stdDev: number,\n format: \"f\" | \"d\"\n): string {\n const fmt = (n: number) => (format === \"f\" ? n.toFixed(2) : n.toFixed(0));\n\n return (\n \"│ \" +\n padEndWidth(label, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(mean), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(stdDev), STAT_VALUE_WIDTH) +\n \" │\"\n );\n}\n\n/**\n * 格式化时间显示(带小数)\n */\nfunction formatTimeWithDecimals(ms: number): string {\n if (ms === Math.floor(ms)) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${ms.toFixed(2)}ms`;\n}\n\n/**\n * 渲染单次测试结果\n */\nexport function renderSingleResult(\n metrics: CalculatedMetrics,\n runIndex: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(`\\n${messages.runLabel(runIndex + 1)}`);\n lines.push(` ${messages.resultLabels.ttft}: ${formatTimeWithDecimals(metrics.ttft)}`);\n lines.push(` ${messages.resultLabels.totalTime}: ${formatTimeWithDecimals(metrics.totalTime)}`);\n lines.push(` ${messages.resultLabels.totalTokens}: ${metrics.totalTokens}`);\n lines.push(\n ` ${messages.resultLabels.averageSpeed}: ${metrics.averageSpeed.toFixed(2)} tokens/s`\n );\n lines.push(` ${messages.resultLabels.peakSpeed}: ${metrics.peakSpeed.toFixed(2)} tokens/s`);\n lines.push(` ${messages.resultLabels.peakTps}: ${metrics.peakTps.toFixed(2)} tokens/s`);\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染完整的测试报告\n */\nexport function renderReport(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n\n lines.push(\"\\n\" + \"═\".repeat(72));\n lines.push(messages.reportTitle);\n lines.push(\"═\".repeat(72));\n\n // 汇总统计\n lines.push(renderStatsTable(stats, lang));\n\n // 速度趋势图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderSpeedChart(stats.mean.tps, undefined, lang));\n }\n\n // TPS 直方图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderTPSHistogram(stats.mean.tps, lang));\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["mean"]}
|