rl-rockcli 0.0.2 → 0.0.3
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 +400 -0
- package/index.js +51 -21
- package/package.json +1 -1
- package/commands/log/core/constants.js +0 -237
- package/commands/log/core/display.js +0 -370
- package/commands/log/core/search.js +0 -330
- package/commands/log/core/tail.js +0 -216
- package/commands/log/core/utils.js +0 -424
- package/commands/log.js +0 -298
- package/commands/sandbox/core/log-bridge.js +0 -119
- package/commands/sandbox/core/replay/analyzer.js +0 -311
- package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/core/replay/batch-task.js +0 -369
- package/commands/sandbox/core/replay/concurrent-display.js +0 -70
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/core/replay/data-source.js +0 -86
- package/commands/sandbox/core/replay/display.js +0 -231
- package/commands/sandbox/core/replay/executor.js +0 -634
- package/commands/sandbox/core/replay/history-fetcher.js +0 -124
- package/commands/sandbox/core/replay/index.js +0 -338
- package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
- package/commands/sandbox/core/replay/pid-mapping.js +0 -26
- package/commands/sandbox/core/replay/request.js +0 -109
- package/commands/sandbox/core/replay/worker.js +0 -166
- package/commands/sandbox/core/session.js +0 -346
- package/commands/sandbox/log-bridge.js +0 -2
- package/commands/sandbox/ray.js +0 -2
- package/commands/sandbox/replay/analyzer.js +0 -311
- package/commands/sandbox/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/replay/batch-task.js +0 -369
- package/commands/sandbox/replay/concurrent-display.js +0 -70
- package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/replay/display.js +0 -231
- package/commands/sandbox/replay/executor.js +0 -634
- package/commands/sandbox/replay/history-fetcher.js +0 -118
- package/commands/sandbox/replay/index.js +0 -338
- package/commands/sandbox/replay/pid-mapping.js +0 -26
- package/commands/sandbox/replay/request.js +0 -109
- package/commands/sandbox/replay/worker.js +0 -166
- package/commands/sandbox/replay.js +0 -2
- package/commands/sandbox/session.js +0 -2
- package/commands/sandbox-original.js +0 -1393
- package/commands/sandbox.js +0 -499
- package/help/help.json +0 -1071
- package/help/middleware.js +0 -71
- package/help/renderer.js +0 -800
- package/lib/plugin-context.js +0 -40
- package/sdks/sandbox/core/client.js +0 -845
- package/sdks/sandbox/core/config.js +0 -70
- package/sdks/sandbox/core/types.js +0 -74
- package/sdks/sandbox/httpLogger.js +0 -251
- package/sdks/sandbox/index.js +0 -9
- package/utils/asciiArt.js +0 -138
- package/utils/bun-compat.js +0 -59
- package/utils/ciPipelines.js +0 -138
- package/utils/cli.js +0 -17
- package/utils/command-router.js +0 -79
- package/utils/configManager.js +0 -503
- package/utils/dependency-resolver.js +0 -135
- package/utils/eagleeye_traceid.js +0 -151
- package/utils/envDetector.js +0 -78
- package/utils/execution_logger.js +0 -415
- package/utils/featureManager.js +0 -68
- package/utils/firstTimeTip.js +0 -44
- package/utils/hook-manager.js +0 -125
- package/utils/http-logger.js +0 -264
- package/utils/i18n.js +0 -139
- package/utils/image-progress.js +0 -159
- package/utils/logger.js +0 -154
- package/utils/plugin-loader.js +0 -124
- package/utils/plugin-manager.js +0 -348
- package/utils/ray_cli_wrapper.js +0 -746
- package/utils/sandbox-client.js +0 -419
- package/utils/terminal.js +0 -32
- package/utils/tips.js +0 -106
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 批量回放任务状态常量
|
|
3
|
-
*/
|
|
4
|
-
const TaskStatus = {
|
|
5
|
-
PENDING: 'pending', // 待处理
|
|
6
|
-
FETCHING: 'fetching', // 正在获取历史
|
|
7
|
-
READY: 'ready', // 历史已就绪,待执行
|
|
8
|
-
RUNNING: 'running', // 正在执行回放
|
|
9
|
-
COMPLETED: 'completed', // 执行完成
|
|
10
|
-
FAILED: 'failed', // 执行失败
|
|
11
|
-
SKIPPED: 'skipped' // 跳过(历史获取失败等)
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 错误发生阶段
|
|
16
|
-
*/
|
|
17
|
-
const ErrorPhase = {
|
|
18
|
-
PARSE: 'parse', // 解析输入
|
|
19
|
-
HISTORY_FETCH: 'history_fetch', // 获取历史
|
|
20
|
-
REPLAY: 'replay' // 执行回放
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 单个沙箱任务
|
|
25
|
-
* 管理单个沙箱的回放状态和结果
|
|
26
|
-
*/
|
|
27
|
-
class SandboxTask {
|
|
28
|
-
/**
|
|
29
|
-
* @param {number} index - 任务索引(0-based)
|
|
30
|
-
* @param {Object} sandboxConfig - 沙箱配置
|
|
31
|
-
* @param {Object} defaults - 默认配置(从输入文件的 defaults 字段)
|
|
32
|
-
*/
|
|
33
|
-
constructor(index, sandboxConfig, defaults = {}) {
|
|
34
|
-
this.index = index;
|
|
35
|
-
this.sandboxId = sandboxConfig.sandboxId;
|
|
36
|
-
this.name = sandboxConfig.name || sandboxConfig.sandboxId.substring(0, 8);
|
|
37
|
-
|
|
38
|
-
// 合并配置(沙箱配置覆盖默认配置)
|
|
39
|
-
this.cluster = sandboxConfig.cluster || defaults.cluster || null;
|
|
40
|
-
this.userId = sandboxConfig.userId || defaults.userId || null;
|
|
41
|
-
this.experimentId = sandboxConfig.experimentId || defaults.experimentId || null;
|
|
42
|
-
this.mode = sandboxConfig.mode || defaults.mode || 'smart';
|
|
43
|
-
this.timing = sandboxConfig.timing || defaults.timing || 'none';
|
|
44
|
-
|
|
45
|
-
// 元数据
|
|
46
|
-
this.tags = sandboxConfig.tags || {};
|
|
47
|
-
this.uris = sandboxConfig.uris || [];
|
|
48
|
-
|
|
49
|
-
// 合并过滤器(默认 + 沙箱特定)
|
|
50
|
-
this.filters = [
|
|
51
|
-
...(defaults.filters || []),
|
|
52
|
-
...(sandboxConfig.filters || [])
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
// 状态
|
|
56
|
-
this.status = TaskStatus.PENDING;
|
|
57
|
-
|
|
58
|
-
// 历史数据
|
|
59
|
-
this.requests = sandboxConfig.requests || null; // 预加载的请求列表
|
|
60
|
-
this.historyData = null; // 完整的历史数据(包含 startParams, targetCluster 等)
|
|
61
|
-
|
|
62
|
-
// 回放结果
|
|
63
|
-
this.newSandboxId = null; // 新创建的沙箱 ID
|
|
64
|
-
this.replayResult = null; // ReplayExecutor 返回的结果
|
|
65
|
-
this.pidMappings = []; // PID 映射表
|
|
66
|
-
|
|
67
|
-
// 错误信息
|
|
68
|
-
this.error = null;
|
|
69
|
-
this.errorPhase = null;
|
|
70
|
-
|
|
71
|
-
// 时间记录
|
|
72
|
-
this.historyFetchStartTime = null;
|
|
73
|
-
this.historyFetchEndTime = null;
|
|
74
|
-
this.replayStartTime = null;
|
|
75
|
-
this.replayEndTime = null;
|
|
76
|
-
|
|
77
|
-
// 日志文件
|
|
78
|
-
this.logFile = null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 是否有预加载的请求列表
|
|
83
|
-
* @returns {boolean}
|
|
84
|
-
*/
|
|
85
|
-
hasRequests() {
|
|
86
|
-
return !!(this.requests && Array.isArray(this.requests) && this.requests.length > 0);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 设置历史获取开始
|
|
91
|
-
*/
|
|
92
|
-
startHistoryFetch() {
|
|
93
|
-
this.status = TaskStatus.FETCHING;
|
|
94
|
-
this.historyFetchStartTime = Date.now();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 设置历史获取完成
|
|
99
|
-
* @param {Object} historyData - 历史数据
|
|
100
|
-
*/
|
|
101
|
-
completeHistoryFetch(historyData) {
|
|
102
|
-
this.historyData = historyData;
|
|
103
|
-
this.requests = historyData.requests || [];
|
|
104
|
-
this.status = TaskStatus.READY;
|
|
105
|
-
this.historyFetchEndTime = Date.now();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 设置历史获取失败
|
|
110
|
-
* @param {Error} error - 错误对象
|
|
111
|
-
*/
|
|
112
|
-
failHistoryFetch(error) {
|
|
113
|
-
this.status = TaskStatus.FAILED;
|
|
114
|
-
this.error = error.message || String(error);
|
|
115
|
-
this.errorPhase = ErrorPhase.HISTORY_FETCH;
|
|
116
|
-
this.historyFetchEndTime = Date.now();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 设置回放开始
|
|
121
|
-
*/
|
|
122
|
-
startReplay() {
|
|
123
|
-
this.status = TaskStatus.RUNNING;
|
|
124
|
-
this.replayStartTime = Date.now();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 设置回放完成
|
|
129
|
-
* @param {Object} result - 回放结果
|
|
130
|
-
* @param {string} newSandboxId - 新沙箱 ID
|
|
131
|
-
* @param {Array} pidMappings - PID 映射
|
|
132
|
-
*/
|
|
133
|
-
completeReplay(result, newSandboxId, pidMappings = []) {
|
|
134
|
-
// 如果有失败的请求,标记为 FAILED 而不是 COMPLETED
|
|
135
|
-
if (result && result.failed > 0) {
|
|
136
|
-
this.status = TaskStatus.FAILED;
|
|
137
|
-
this.error = `${result.failed} request(s) failed during replay`;
|
|
138
|
-
this.errorPhase = ErrorPhase.REPLAY;
|
|
139
|
-
} else {
|
|
140
|
-
this.status = TaskStatus.COMPLETED;
|
|
141
|
-
}
|
|
142
|
-
this.replayResult = result;
|
|
143
|
-
this.newSandboxId = newSandboxId;
|
|
144
|
-
this.pidMappings = pidMappings;
|
|
145
|
-
this.replayEndTime = Date.now();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 设置回放失败
|
|
150
|
-
* @param {Error} error - 错误对象
|
|
151
|
-
*/
|
|
152
|
-
failReplay(error) {
|
|
153
|
-
this.status = TaskStatus.FAILED;
|
|
154
|
-
this.error = error.message || String(error);
|
|
155
|
-
this.errorPhase = ErrorPhase.REPLAY;
|
|
156
|
-
this.replayEndTime = Date.now();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 获取历史获取耗时(毫秒)
|
|
161
|
-
* @returns {number|null}
|
|
162
|
-
*/
|
|
163
|
-
getHistoryFetchDuration() {
|
|
164
|
-
if (this.historyFetchStartTime && this.historyFetchEndTime) {
|
|
165
|
-
return this.historyFetchEndTime - this.historyFetchStartTime;
|
|
166
|
-
}
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* 获取回放耗时(毫秒)
|
|
172
|
-
* @returns {number|null}
|
|
173
|
-
*/
|
|
174
|
-
getReplayDuration() {
|
|
175
|
-
if (this.replayStartTime && this.replayEndTime) {
|
|
176
|
-
return this.replayEndTime - this.replayStartTime;
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 转换为结果对象
|
|
183
|
-
* @returns {SandboxResult}
|
|
184
|
-
*/
|
|
185
|
-
toResult() {
|
|
186
|
-
return new SandboxResult(this);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* 单个沙箱的执行结果
|
|
192
|
-
*/
|
|
193
|
-
class SandboxResult {
|
|
194
|
-
/**
|
|
195
|
-
* @param {SandboxTask} task - 任务对象
|
|
196
|
-
*/
|
|
197
|
-
constructor(task) {
|
|
198
|
-
this.index = task.index;
|
|
199
|
-
this.sandboxId = task.sandboxId;
|
|
200
|
-
this.name = task.name;
|
|
201
|
-
this.tags = task.tags;
|
|
202
|
-
this.status = task.status;
|
|
203
|
-
this.newSandboxId = task.newSandboxId;
|
|
204
|
-
|
|
205
|
-
// 历史获取结果
|
|
206
|
-
this.historyFetch = {
|
|
207
|
-
success: task.status !== TaskStatus.FAILED || task.errorPhase !== ErrorPhase.HISTORY_FETCH,
|
|
208
|
-
requestCount: task.requests ? task.requests.length : 0,
|
|
209
|
-
duration: task.getHistoryFetchDuration(),
|
|
210
|
-
error: task.errorPhase === ErrorPhase.HISTORY_FETCH ? task.error : null
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// 回放结果
|
|
214
|
-
if (task.replayResult) {
|
|
215
|
-
this.replay = {
|
|
216
|
-
originalRequests: task.replayResult.total || 0,
|
|
217
|
-
executedRequests: (task.replayResult.success || 0) + (task.replayResult.failed || 0),
|
|
218
|
-
success: task.replayResult.success || 0,
|
|
219
|
-
failed: task.replayResult.failed || 0,
|
|
220
|
-
skipped: task.replayResult.skipped || 0,
|
|
221
|
-
duration: task.getReplayDuration()
|
|
222
|
-
};
|
|
223
|
-
} else {
|
|
224
|
-
this.replay = null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// PID 映射
|
|
228
|
-
this.pidMappings = task.pidMappings;
|
|
229
|
-
|
|
230
|
-
// 错误信息
|
|
231
|
-
this.errors = [];
|
|
232
|
-
if (task.error) {
|
|
233
|
-
this.errors.push({
|
|
234
|
-
phase: task.errorPhase,
|
|
235
|
-
message: task.error
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// 日志文件
|
|
240
|
-
this.logFile = task.logFile;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* 批量回放结果汇总
|
|
246
|
-
*/
|
|
247
|
-
class BatchReplayResult {
|
|
248
|
-
/**
|
|
249
|
-
* @param {Object} options - 配置选项
|
|
250
|
-
*/
|
|
251
|
-
constructor(options = {}) {
|
|
252
|
-
this.summary = {
|
|
253
|
-
totalSandboxes: 0,
|
|
254
|
-
successful: 0,
|
|
255
|
-
failed: 0,
|
|
256
|
-
skipped: 0,
|
|
257
|
-
startTime: null,
|
|
258
|
-
endTime: null,
|
|
259
|
-
totalDuration: null,
|
|
260
|
-
concurrency: options.concurrency || 1
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
this.sandboxResults = [];
|
|
264
|
-
this.failedSandboxes = [];
|
|
265
|
-
|
|
266
|
-
this.metadata = {
|
|
267
|
-
inputFile: options.inputFile || null,
|
|
268
|
-
inputMetadata: options.inputMetadata || null,
|
|
269
|
-
cliVersion: options.cliVersion || null,
|
|
270
|
-
mode: options.mode || 'smart',
|
|
271
|
-
timing: options.timing || 'none'
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* 设置开始时间
|
|
277
|
-
*/
|
|
278
|
-
start() {
|
|
279
|
-
this.summary.startTime = new Date().toISOString();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* 设置结束时间并计算总耗时
|
|
284
|
-
*/
|
|
285
|
-
end() {
|
|
286
|
-
this.summary.endTime = new Date().toISOString();
|
|
287
|
-
|
|
288
|
-
if (this.summary.startTime && this.summary.endTime) {
|
|
289
|
-
const startMs = new Date(this.summary.startTime).getTime();
|
|
290
|
-
const endMs = new Date(this.summary.endTime).getTime();
|
|
291
|
-
const durationMs = endMs - startMs;
|
|
292
|
-
this.summary.totalDuration = formatDuration(durationMs);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* 添加沙箱结果
|
|
298
|
-
* @param {SandboxResult} result - 沙箱结果
|
|
299
|
-
*/
|
|
300
|
-
addResult(result) {
|
|
301
|
-
this.sandboxResults.push(result);
|
|
302
|
-
this.summary.totalSandboxes++;
|
|
303
|
-
|
|
304
|
-
if (result.status === TaskStatus.COMPLETED) {
|
|
305
|
-
this.summary.successful++;
|
|
306
|
-
} else if (result.status === TaskStatus.FAILED) {
|
|
307
|
-
this.summary.failed++;
|
|
308
|
-
this.failedSandboxes.push({
|
|
309
|
-
sandboxId: result.sandboxId,
|
|
310
|
-
name: result.name,
|
|
311
|
-
error: result.errors.length > 0 ? result.errors[0].message : 'Unknown error',
|
|
312
|
-
phase: result.errors.length > 0 ? result.errors[0].phase : 'unknown'
|
|
313
|
-
});
|
|
314
|
-
} else if (result.status === TaskStatus.SKIPPED) {
|
|
315
|
-
this.summary.skipped++;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* 从任务列表生成结果
|
|
321
|
-
* @param {SandboxTask[]} tasks - 任务列表
|
|
322
|
-
*/
|
|
323
|
-
generateFromTasks(tasks) {
|
|
324
|
-
for (const task of tasks) {
|
|
325
|
-
this.addResult(task.toResult());
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* 转换为 JSON 对象
|
|
331
|
-
* @returns {Object}
|
|
332
|
-
*/
|
|
333
|
-
toJSON() {
|
|
334
|
-
return {
|
|
335
|
-
summary: this.summary,
|
|
336
|
-
sandboxResults: this.sandboxResults,
|
|
337
|
-
failedSandboxes: this.failedSandboxes,
|
|
338
|
-
metadata: this.metadata
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* 格式化持续时间
|
|
345
|
-
* @param {number} ms - 毫秒
|
|
346
|
-
* @returns {string}
|
|
347
|
-
*/
|
|
348
|
-
function formatDuration(ms) {
|
|
349
|
-
if (ms === null || ms === undefined) return '-';
|
|
350
|
-
|
|
351
|
-
if (ms < 1000) {
|
|
352
|
-
return `${ms}ms`;
|
|
353
|
-
} else if (ms < 60000) {
|
|
354
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
355
|
-
} else {
|
|
356
|
-
const minutes = Math.floor(ms / 60000);
|
|
357
|
-
const seconds = Math.floor((ms % 60000) / 1000);
|
|
358
|
-
return `${minutes}m ${seconds}s`;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
module.exports = {
|
|
363
|
-
TaskStatus,
|
|
364
|
-
ErrorPhase,
|
|
365
|
-
SandboxTask,
|
|
366
|
-
SandboxResult,
|
|
367
|
-
BatchReplayResult,
|
|
368
|
-
formatDuration
|
|
369
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 格式化持续时间
|
|
3
|
-
* @param {number} ms - 毫秒
|
|
4
|
-
* @returns {string}
|
|
5
|
-
*/
|
|
6
|
-
function formatDuration(ms) {
|
|
7
|
-
if (ms == null || isNaN(ms)) return 'N/A';
|
|
8
|
-
if (ms < 1000) return `${ms}ms`;
|
|
9
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
10
|
-
const minutes = Math.floor(ms / 60000);
|
|
11
|
-
const seconds = Math.round((ms % 60000) / 1000);
|
|
12
|
-
return `${minutes}m${seconds}s`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 显示并发回放结果摘要
|
|
17
|
-
* @param {ConcurrentReplayResult} result
|
|
18
|
-
* @param {Object} options
|
|
19
|
-
* @param {boolean} options.quiet - 静默模式
|
|
20
|
-
*/
|
|
21
|
-
function displayConcurrentResultsSummary(result, options = {}) {
|
|
22
|
-
if (options.quiet) return;
|
|
23
|
-
|
|
24
|
-
console.log('\n═══════════════════════════════════════════════════════════════');
|
|
25
|
-
console.log('📊 Concurrent Replay Summary');
|
|
26
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
27
|
-
|
|
28
|
-
console.log(`\nConcurrency: ${result.concurrency} sandboxes`);
|
|
29
|
-
console.log(`Status: ${result.allSucceeded ? '✅ All succeeded' : '⚠️ Some failed'}`);
|
|
30
|
-
|
|
31
|
-
// 显示每个 worker 的结果
|
|
32
|
-
console.log('\nPer-Sandbox Results:');
|
|
33
|
-
console.log('┌─────────┬────────────────────────────────────────┬─────────┬─────────┬─────────┐');
|
|
34
|
-
console.log('│ Worker │ Sandbox ID │ Success │ Failed │ Duration│');
|
|
35
|
-
console.log('├─────────┼────────────────────────────────────────┼─────────┼─────────┼─────────┤');
|
|
36
|
-
|
|
37
|
-
result.workers.forEach(w => {
|
|
38
|
-
const status = w.status === 'completed' ? '✅' : '❌';
|
|
39
|
-
const sandboxId = (w.sandboxId || 'N/A').padEnd(38).substring(0, 38);
|
|
40
|
-
const success = String(w.results?.success || 0).padStart(7);
|
|
41
|
-
const failed = String(w.results?.failed || 0).padStart(7);
|
|
42
|
-
const duration = formatDuration(w.duration).padStart(7);
|
|
43
|
-
|
|
44
|
-
console.log(`│ ${status} ${String(w.workerId).padEnd(5)} │ ${sandboxId} │ ${success} │ ${failed} │ ${duration} │`);
|
|
45
|
-
|
|
46
|
-
if (w.error) {
|
|
47
|
-
const errorMsg = w.error.length > 60 ? w.error.substring(0, 57) + '...' : w.error;
|
|
48
|
-
console.log(`│ │ Error: ${errorMsg.padEnd(52)} │`);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
console.log('└─────────┴────────────────────────────────────────┴─────────┴─────────┴─────────┘');
|
|
53
|
-
|
|
54
|
-
// 显示汇总
|
|
55
|
-
console.log('\nAggregated Results:');
|
|
56
|
-
console.log(`├── ✅ Total Successful: ${result.aggregated.success}`);
|
|
57
|
-
console.log(`├── ❌ Total Failed: ${result.aggregated.failed}`);
|
|
58
|
-
console.log(`└── ⏭️ Total Skipped: ${result.aggregated.skipped}`);
|
|
59
|
-
|
|
60
|
-
// 显示日志文件
|
|
61
|
-
console.log('\nLog Files:');
|
|
62
|
-
result.workers.forEach((w, index) => {
|
|
63
|
-
const prefix = index === result.workers.length - 1 ? '└──' : '├──';
|
|
64
|
-
console.log(`${prefix} Worker ${w.workerId}: ${w.logFile}`);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = { displayConcurrentResultsSummary, formatDuration };
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
const { ReplayWorker } = require('./worker');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 并发回放协调器
|
|
5
|
-
* 管理多个 ReplayWorker 的并发执行
|
|
6
|
-
*/
|
|
7
|
-
class ConcurrentReplayOrchestrator {
|
|
8
|
-
/**
|
|
9
|
-
* @param {Object} options
|
|
10
|
-
* @param {number} options.concurrency - 并发数
|
|
11
|
-
* @param {Object} options.config - 沙箱配置
|
|
12
|
-
* @param {boolean} options.quiet - 静默模式
|
|
13
|
-
* @param {string} options.logFile - 日志文件基础路径
|
|
14
|
-
* @param {string} options.failureStrategy - 失败策略: 'continue' | 'stop-all'
|
|
15
|
-
*/
|
|
16
|
-
constructor(options) {
|
|
17
|
-
this.concurrency = options.concurrency || 1;
|
|
18
|
-
this.config = options.config;
|
|
19
|
-
this.quiet = options.quiet || false;
|
|
20
|
-
this.logFile = options.logFile || 'replay.log';
|
|
21
|
-
this.failureStrategy = options.failureStrategy || 'continue';
|
|
22
|
-
|
|
23
|
-
this.workers = [];
|
|
24
|
-
this.progressIntervalId = null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 执行并发回放
|
|
29
|
-
* @param {ReplayPlan} plan - 回放计划
|
|
30
|
-
* @param {Object} executorOptions - 执行器选项
|
|
31
|
-
* @returns {Promise<ConcurrentReplayResult>}
|
|
32
|
-
*/
|
|
33
|
-
async execute(plan, executorOptions) {
|
|
34
|
-
// 1. 显示并发回放摘要
|
|
35
|
-
this.displayConcurrentSummary(plan);
|
|
36
|
-
|
|
37
|
-
// 2. 创建所有 workers
|
|
38
|
-
for (let i = 1; i <= this.concurrency; i++) {
|
|
39
|
-
const worker = new ReplayWorker(i, this.config, plan, {
|
|
40
|
-
...executorOptions,
|
|
41
|
-
logFile: this.logFile
|
|
42
|
-
});
|
|
43
|
-
this.workers.push(worker);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 3. 启动进度监控(如果非静默模式)
|
|
47
|
-
if (!this.quiet) {
|
|
48
|
-
this.startProgressMonitor();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 4. 并发执行所有 workers
|
|
52
|
-
let workerResults;
|
|
53
|
-
|
|
54
|
-
if (this.failureStrategy === 'stop-all') {
|
|
55
|
-
// 使用 Promise.all,任一失败则全部停止
|
|
56
|
-
try {
|
|
57
|
-
workerResults = await Promise.all(
|
|
58
|
-
this.workers.map(w => w.execute())
|
|
59
|
-
);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// 如果 Promise.all 抛出异常,收集已有结果
|
|
62
|
-
workerResults = this.workers.map(w => w.getWorkerResult());
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
// 使用 Promise.allSettled,允许部分失败
|
|
66
|
-
const settled = await Promise.allSettled(
|
|
67
|
-
this.workers.map(w => w.execute())
|
|
68
|
-
);
|
|
69
|
-
workerResults = settled.map((result, index) => {
|
|
70
|
-
if (result.status === 'fulfilled') {
|
|
71
|
-
return result.value;
|
|
72
|
-
} else {
|
|
73
|
-
return this.workers[index].getWorkerResult();
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 5. 停止进度监控
|
|
79
|
-
this.stopProgressMonitor();
|
|
80
|
-
|
|
81
|
-
// 6. 汇总结果
|
|
82
|
-
return this.aggregateResults(workerResults);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 显示并发回放摘要
|
|
87
|
-
* @param {ReplayPlan} plan
|
|
88
|
-
*/
|
|
89
|
-
displayConcurrentSummary(plan) {
|
|
90
|
-
if (this.quiet) return;
|
|
91
|
-
|
|
92
|
-
console.log(`\n🔀 Concurrent Replay Mode`);
|
|
93
|
-
console.log(` Concurrency: ${this.concurrency} sandboxes`);
|
|
94
|
-
console.log(` Requests per sandbox: ${plan.executeCount}`);
|
|
95
|
-
console.log(` Total requests: ${plan.executeCount * this.concurrency}`);
|
|
96
|
-
console.log(`\n⏳ Starting ${this.concurrency} sandboxes...\n`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 启动进度监控
|
|
101
|
-
*/
|
|
102
|
-
startProgressMonitor() {
|
|
103
|
-
this.progressIntervalId = setInterval(() => {
|
|
104
|
-
this.displayProgress();
|
|
105
|
-
}, 10000); // 每 10 秒更新一次进度
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 停止进度监控
|
|
110
|
-
*/
|
|
111
|
-
stopProgressMonitor() {
|
|
112
|
-
if (this.progressIntervalId) {
|
|
113
|
-
clearInterval(this.progressIntervalId);
|
|
114
|
-
this.progressIntervalId = null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 显示当前进度
|
|
120
|
-
*/
|
|
121
|
-
displayProgress() {
|
|
122
|
-
const statuses = this.workers.map(w => {
|
|
123
|
-
const progress = w.getProgress();
|
|
124
|
-
const icon = progress.status === 'completed' ? '✅' :
|
|
125
|
-
progress.status === 'failed' ? '❌' :
|
|
126
|
-
progress.status === 'running' ? '⏳' : '⏸️';
|
|
127
|
-
const sandboxId = progress.sandboxId || '--------';
|
|
128
|
-
return ` Worker ${progress.workerId}: ${icon} sandbox=${sandboxId}... ${progress.success}/${progress.total}`;
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
console.log('\n--- Progress Update ---');
|
|
132
|
-
statuses.forEach(s => console.log(s));
|
|
133
|
-
console.log('-----------------------\n');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 汇总执行结果
|
|
138
|
-
* @param {WorkerResult[]} workerResults
|
|
139
|
-
* @returns {ConcurrentReplayResult}
|
|
140
|
-
*/
|
|
141
|
-
aggregateResults(workerResults) {
|
|
142
|
-
const totalResults = {
|
|
143
|
-
total: 0,
|
|
144
|
-
success: 0,
|
|
145
|
-
failed: 0,
|
|
146
|
-
skipped: 0
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
workerResults.forEach(r => {
|
|
150
|
-
if (r.results) {
|
|
151
|
-
totalResults.total += r.results.total;
|
|
152
|
-
totalResults.success += r.results.success;
|
|
153
|
-
totalResults.failed += r.results.failed;
|
|
154
|
-
totalResults.skipped += r.results.skipped;
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
concurrency: this.concurrency,
|
|
160
|
-
workers: workerResults,
|
|
161
|
-
aggregated: totalResults,
|
|
162
|
-
allSucceeded: workerResults.every(r => r.status === 'completed'),
|
|
163
|
-
sandboxIds: workerResults
|
|
164
|
-
.filter(r => r.sandboxId)
|
|
165
|
-
.map(r => ({ workerId: r.workerId, sandboxId: r.sandboxId }))
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = { ConcurrentReplayOrchestrator };
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ReplayDataSource Interface
|
|
5
|
-
*
|
|
6
|
-
* Abstract interface for fetching sandbox history data.
|
|
7
|
-
* Different environments (internal/opensource) can implement this interface
|
|
8
|
-
* with their own data sources (Loghouse, local files, cloud storage, etc.)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {Object} HistoryRequest
|
|
13
|
-
* @property {string} method - HTTP method (GET, POST, etc.)
|
|
14
|
-
* @property {string} uri - Request URI
|
|
15
|
-
* @property {Object} [requestBody] - Request body
|
|
16
|
-
* @property {Object} [headers] - Request headers
|
|
17
|
-
* @property {string} timestamp - Timestamp of the request
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {Object} HistoryData
|
|
22
|
-
* @property {string} sandboxId - Sandbox ID
|
|
23
|
-
* @property {HistoryRequest[]} requests - List of historical requests
|
|
24
|
-
* @property {Object} [metadata] - Additional metadata (cluster, time range, etc.)
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Abstract base class for replay data sources
|
|
29
|
-
* Subclasses must implement the fetch method
|
|
30
|
-
*/
|
|
31
|
-
class ReplayDataSource {
|
|
32
|
-
/**
|
|
33
|
-
* Fetch history data for a sandbox
|
|
34
|
-
* @param {string} sandboxId - Sandbox ID
|
|
35
|
-
* @param {Object} options - Fetch options
|
|
36
|
-
* @param {string} [options.cluster] - Cluster name
|
|
37
|
-
* @param {Array<string>} [options.uris] - URI filters
|
|
38
|
-
* @param {boolean} [options.debug] - Enable debug mode
|
|
39
|
-
* @returns {Promise<HistoryData>} History data
|
|
40
|
-
* @throws {Error} If fetch fails
|
|
41
|
-
*/
|
|
42
|
-
async fetch(sandboxId, options = {}) {
|
|
43
|
-
throw new Error('ReplayDataSource.fetch() must be implemented by subclass');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Batch fetch history data for multiple sandboxes
|
|
48
|
-
* @param {Array<{sandboxId: string, options: Object}>} requests - Fetch requests
|
|
49
|
-
* @param {number} [concurrency=5] - Concurrency limit
|
|
50
|
-
* @returns {Promise<Map<string, HistoryData>>} Map of sandboxId to history data
|
|
51
|
-
*/
|
|
52
|
-
async fetchBatch(requests, concurrency = 5) {
|
|
53
|
-
const results = new Map();
|
|
54
|
-
const queue = [...requests];
|
|
55
|
-
|
|
56
|
-
const worker = async () => {
|
|
57
|
-
while (queue.length > 0) {
|
|
58
|
-
const item = queue.shift();
|
|
59
|
-
if (!item) break;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const data = await this.fetch(item.sandboxId, item.options);
|
|
63
|
-
results.set(item.sandboxId, data);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
// Store error as metadata for failed fetches
|
|
66
|
-
results.set(item.sandboxId, {
|
|
67
|
-
sandboxId: item.sandboxId,
|
|
68
|
-
requests: [],
|
|
69
|
-
metadata: { error: error.message }
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const workers = Array(Math.min(concurrency, requests.length))
|
|
76
|
-
.fill(null)
|
|
77
|
-
.map(() => worker());
|
|
78
|
-
|
|
79
|
-
await Promise.all(workers);
|
|
80
|
-
return results;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
module.exports = {
|
|
85
|
-
ReplayDataSource,
|
|
86
|
-
};
|