aicodeswitch 5.2.11 → 5.2.12
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/dist/server/fs-database.js +3 -3
- package/dist/server/main.js +77 -0
- package/dist/server/performance-tracker.js +377 -0
- package/dist/server/proxy-server.js +82 -18
- package/dist/server/rules-status-service.js +21 -6
- package/dist/server/transformers/stream-timing-transform.js +63 -0
- package/dist/ui/assets/index-BFVjD9Y2.js +799 -0
- package/dist/ui/assets/index-Dm34-4zP.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-DR6cZIa7.js +0 -799
- package/dist/ui/assets/index-MjMlew6J.css +0 -1
|
@@ -52,6 +52,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
52
52
|
const streaming_1 = require("./transformers/streaming");
|
|
53
53
|
const model_rewrite_transform_1 = require("./transformers/model-rewrite-transform");
|
|
54
54
|
const chunk_collector_1 = require("./transformers/chunk-collector");
|
|
55
|
+
const stream_timing_transform_1 = require("./transformers/stream-timing-transform");
|
|
55
56
|
const rules_status_service_1 = require("./rules-status-service");
|
|
56
57
|
const index_1 = require("./conversions/index");
|
|
57
58
|
const stream_converter_adapter_1 = require("./conversions/stream-converter-adapter");
|
|
@@ -219,6 +220,12 @@ class ProxyServer {
|
|
|
219
220
|
writable: true,
|
|
220
221
|
value: null
|
|
221
222
|
});
|
|
223
|
+
Object.defineProperty(this, "performanceTracker", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
configurable: true,
|
|
226
|
+
writable: true,
|
|
227
|
+
value: null
|
|
228
|
+
});
|
|
222
229
|
// 请求去重缓存:用于防止同一个请求被重复计数(如网络重试)
|
|
223
230
|
// key: requestHash, value: timestamp
|
|
224
231
|
Object.defineProperty(this, "requestDedupeCache", {
|
|
@@ -253,6 +260,43 @@ class ProxyServer {
|
|
|
253
260
|
getAccessKeyModule() {
|
|
254
261
|
return this.accessKeyModule;
|
|
255
262
|
}
|
|
263
|
+
/** 设置服务性能统计 tracker(全局,与 AUTH 无关) */
|
|
264
|
+
setPerformanceTracker(tracker) {
|
|
265
|
+
this.performanceTracker = tracker;
|
|
266
|
+
}
|
|
267
|
+
/** 获取服务性能统计 tracker */
|
|
268
|
+
getPerformanceTracker() {
|
|
269
|
+
return this.performanceTracker;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 采集一次请求的服务性能数据点(全局,与 AUTH 无关)。
|
|
273
|
+
* 在两条转发路径的 finalizeLog 公共点调用,覆盖 AccessKey + 普通路由。
|
|
274
|
+
* 流式:依据 streamTiming 精确计算 TTFT 与生成阶段吞吐;非流式:端到端估算(estimated)。
|
|
275
|
+
*/
|
|
276
|
+
emitPerformance(params) {
|
|
277
|
+
const tracker = this.performanceTracker;
|
|
278
|
+
if (!tracker)
|
|
279
|
+
return;
|
|
280
|
+
const { statusCode, startTime, usage, streamTiming, service, vendorId, vendorName, model } = params;
|
|
281
|
+
const isError = statusCode >= 400;
|
|
282
|
+
const outputTokens = usage === null || usage === void 0 ? void 0 : usage.outputTokens;
|
|
283
|
+
const responseMs = Date.now() - startTime;
|
|
284
|
+
let ttftMs;
|
|
285
|
+
let tokensPerSecond;
|
|
286
|
+
let timingAccuracy = 'estimated';
|
|
287
|
+
if (streamTiming && streamTiming.hasTiming()) {
|
|
288
|
+
timingAccuracy = 'precise';
|
|
289
|
+
ttftMs = streamTiming.firstEventAt - startTime;
|
|
290
|
+
const generationMs = streamTiming.lastEventAt - streamTiming.firstEventAt;
|
|
291
|
+
if (outputTokens && generationMs > 0) {
|
|
292
|
+
tokensPerSecond = outputTokens / (generationMs / 1000);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (outputTokens && responseMs > 0) {
|
|
296
|
+
tokensPerSecond = outputTokens / (responseMs / 1000);
|
|
297
|
+
}
|
|
298
|
+
tracker.recordPerformance(vendorId !== null && vendorId !== void 0 ? vendorId : service.vendorId, vendorName, service.id, service.name, model, { ttftMs, tokensPerSecond, outputTokens, timingAccuracy, isError });
|
|
299
|
+
}
|
|
256
300
|
/**
|
|
257
301
|
* 从请求中提取 API Key(支持三种 Header,按优先级依次尝试)
|
|
258
302
|
*/
|
|
@@ -3544,12 +3588,20 @@ class ProxyServer {
|
|
|
3544
3588
|
let downstreamResponseBodyForLog;
|
|
3545
3589
|
let upstreamRequestForLog;
|
|
3546
3590
|
let actuallyUsedProxy = false; // 标记是否实际使用了代理
|
|
3591
|
+
// 服务性能打点:流式分支会创建实例并注入 pipeline;finalizeLog 据此判定 precise/estimated
|
|
3592
|
+
let streamTiming = null;
|
|
3547
3593
|
// 标记规则正在使用
|
|
3548
3594
|
rules_status_service_1.rulesStatusBroadcaster.markRuleInUse(route.id, rule.id);
|
|
3549
3595
|
const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
|
|
3550
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
3596
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3551
3597
|
if (logged)
|
|
3552
3598
|
return;
|
|
3599
|
+
// 服务性能数据点采集(全局,与 AUTH 无关;独立于 enableLogging 开关)
|
|
3600
|
+
this.emitPerformance({
|
|
3601
|
+
statusCode, startTime, usage: usageForLog, streamTiming,
|
|
3602
|
+
service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3603
|
+
model: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
3604
|
+
});
|
|
3553
3605
|
const isError = statusCode >= 400;
|
|
3554
3606
|
if (isError) {
|
|
3555
3607
|
console.log(`\x1b[31m[Request Error]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms${error ? `, error=${error}` : ''}`);
|
|
@@ -3558,7 +3610,7 @@ class ProxyServer {
|
|
|
3558
3610
|
console.log(`\x1b[33m[Request End]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms`);
|
|
3559
3611
|
}
|
|
3560
3612
|
// 检查是否启用日志记录(默认启用)
|
|
3561
|
-
const enableLogging = ((
|
|
3613
|
+
const enableLogging = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.enableLogging) !== false; // 默认为 true
|
|
3562
3614
|
if (!enableLogging) {
|
|
3563
3615
|
return;
|
|
3564
3616
|
}
|
|
@@ -3585,10 +3637,10 @@ class ProxyServer {
|
|
|
3585
3637
|
targetType,
|
|
3586
3638
|
targetServiceId: service.id,
|
|
3587
3639
|
targetServiceName: service.name,
|
|
3588
|
-
targetModel: rule.targetModel || ((
|
|
3640
|
+
targetModel: rule.targetModel || ((_c = req.body) === null || _c === void 0 ? void 0 : _c.model),
|
|
3589
3641
|
vendorId: service.vendorId,
|
|
3590
3642
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3591
|
-
requestModel: (
|
|
3643
|
+
requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
|
|
3592
3644
|
tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
|
|
3593
3645
|
responseHeaders: responseHeadersForLog,
|
|
3594
3646
|
responseBody: responseBodyForLog,
|
|
@@ -3622,7 +3674,7 @@ class ProxyServer {
|
|
|
3622
3674
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3623
3675
|
serviceId: service.id,
|
|
3624
3676
|
serviceName: service.name,
|
|
3625
|
-
model: rule.targetModel || ((
|
|
3677
|
+
model: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
3626
3678
|
totalTokens: sessionTokens,
|
|
3627
3679
|
}).catch(err => console.error('[KeySession] upsert error:', err));
|
|
3628
3680
|
}
|
|
@@ -3649,10 +3701,10 @@ class ProxyServer {
|
|
|
3649
3701
|
targetType,
|
|
3650
3702
|
targetServiceId: service.id,
|
|
3651
3703
|
targetServiceName: service.name,
|
|
3652
|
-
targetModel: rule.targetModel || ((
|
|
3704
|
+
targetModel: rule.targetModel || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
|
|
3653
3705
|
vendorId: service.vendorId,
|
|
3654
3706
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3655
|
-
requestModel: (
|
|
3707
|
+
requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
|
|
3656
3708
|
tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
|
|
3657
3709
|
});
|
|
3658
3710
|
}
|
|
@@ -3665,7 +3717,7 @@ class ProxyServer {
|
|
|
3665
3717
|
const vendors = this.dbManager.getVendors();
|
|
3666
3718
|
const vendorForLog = vendors.find(v => v.id === service.vendorId);
|
|
3667
3719
|
// 从请求体中提取模型信息
|
|
3668
|
-
const requestModel = (
|
|
3720
|
+
const requestModel = (_h = req.body) === null || _h === void 0 ? void 0 : _h.model;
|
|
3669
3721
|
const tagsForLog = this.buildRelayTags(relayedForLog, useOriginalConfig);
|
|
3670
3722
|
if (extraTagsForLog.length > 0) {
|
|
3671
3723
|
tagsForLog.push(...extraTagsForLog);
|
|
@@ -4061,6 +4113,8 @@ class ProxyServer {
|
|
|
4061
4113
|
const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
|
|
4062
4114
|
rules_status_service_1.rulesStatusBroadcaster.refreshRuleInUse(route.id, rule.id);
|
|
4063
4115
|
});
|
|
4116
|
+
// 服务性能打点:记录首/末 SSE 事件时间,用于 TTFT 与生成阶段吞吐
|
|
4117
|
+
streamTiming = new stream_timing_transform_1.StreamTimingTransform(startTime);
|
|
4064
4118
|
const compactResponseSanitizer = rule.contentType === 'compact' && targetType === 'claude-code'
|
|
4065
4119
|
? new ClaudeCompactResponseSanitizer()
|
|
4066
4120
|
: null;
|
|
@@ -4102,7 +4156,7 @@ class ProxyServer {
|
|
|
4102
4156
|
ensureResponseWritable();
|
|
4103
4157
|
return yield new Promise((resolve, reject) => {
|
|
4104
4158
|
if (converter) {
|
|
4105
|
-
const streamStages = [streamSource, parser, eventCollector, converter];
|
|
4159
|
+
const streamStages = [streamSource, parser, eventCollector, streamTiming, converter];
|
|
4106
4160
|
if (compactResponseSanitizer) {
|
|
4107
4161
|
streamStages.push(compactResponseSanitizer);
|
|
4108
4162
|
}
|
|
@@ -4120,7 +4174,7 @@ class ProxyServer {
|
|
|
4120
4174
|
});
|
|
4121
4175
|
return;
|
|
4122
4176
|
}
|
|
4123
|
-
const streamStages = [streamSource, parser, eventCollector];
|
|
4177
|
+
const streamStages = [streamSource, parser, eventCollector, streamTiming];
|
|
4124
4178
|
if (compactResponseSanitizer) {
|
|
4125
4179
|
streamStages.push(compactResponseSanitizer);
|
|
4126
4180
|
}
|
|
@@ -4608,6 +4662,8 @@ class ProxyServer {
|
|
|
4608
4662
|
let responseBodyForLog;
|
|
4609
4663
|
let downstreamResponseBodyForLog;
|
|
4610
4664
|
let streamChunksForLog;
|
|
4665
|
+
// 服务性能打点:流式分支会创建实例并注入 pipeline
|
|
4666
|
+
let streamTiming = null;
|
|
4611
4667
|
let responseHeadersForLog;
|
|
4612
4668
|
let upstreamRequestForLog;
|
|
4613
4669
|
let relayedForLog = true;
|
|
@@ -4629,10 +4685,16 @@ class ProxyServer {
|
|
|
4629
4685
|
requestBody = (0, compact_1.normalizeClaudeCompactRequestBody)(requestBody);
|
|
4630
4686
|
}
|
|
4631
4687
|
const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
|
|
4632
|
-
var _a, _b, _c, _d, _e;
|
|
4688
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4633
4689
|
if (logged)
|
|
4634
4690
|
return;
|
|
4635
4691
|
logged = true;
|
|
4692
|
+
// 服务性能数据点采集(全局,与 AUTH 无关;独立于 enableLogging 开关)
|
|
4693
|
+
this.emitPerformance({
|
|
4694
|
+
statusCode, startTime, usage: usageForLog, streamTiming,
|
|
4695
|
+
service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
4696
|
+
model: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
4697
|
+
});
|
|
4636
4698
|
// AccessKey 独立日志处理
|
|
4637
4699
|
const accessKeyCtx = req._accessKeyCtx;
|
|
4638
4700
|
if (accessKeyCtx && this.accessKeyModule) {
|
|
@@ -4651,10 +4713,10 @@ class ProxyServer {
|
|
|
4651
4713
|
ruleId: rule.id,
|
|
4652
4714
|
targetServiceId: service.id,
|
|
4653
4715
|
targetServiceName: service.name,
|
|
4654
|
-
targetModel: rule.targetModel || ((
|
|
4716
|
+
targetModel: rule.targetModel || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
|
|
4655
4717
|
vendorId: service.vendorId,
|
|
4656
4718
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
4657
|
-
requestModel: (
|
|
4719
|
+
requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
|
|
4658
4720
|
tags: this.buildRelayTags(relayedForLog),
|
|
4659
4721
|
});
|
|
4660
4722
|
if (usageForLog && statusCode < 400) {
|
|
@@ -4680,7 +4742,7 @@ class ProxyServer {
|
|
|
4680
4742
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
4681
4743
|
serviceId: service.id,
|
|
4682
4744
|
serviceName: service.name,
|
|
4683
|
-
model: rule.targetModel || ((
|
|
4745
|
+
model: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
|
|
4684
4746
|
totalTokens: sessionTokens,
|
|
4685
4747
|
}).catch(err => console.error('[KeySession] upsert error:', err));
|
|
4686
4748
|
}
|
|
@@ -4704,10 +4766,10 @@ class ProxyServer {
|
|
|
4704
4766
|
ruleId: rule.id,
|
|
4705
4767
|
targetServiceId: service.id,
|
|
4706
4768
|
targetServiceName: service.name,
|
|
4707
|
-
targetModel: rule.targetModel || ((
|
|
4769
|
+
targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
4708
4770
|
vendorId: service.vendorId,
|
|
4709
4771
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
4710
|
-
requestModel: (
|
|
4772
|
+
requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
|
|
4711
4773
|
tags: this.buildRelayTags(relayedForLog),
|
|
4712
4774
|
});
|
|
4713
4775
|
}
|
|
@@ -4877,6 +4939,8 @@ class ProxyServer {
|
|
|
4877
4939
|
const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
|
|
4878
4940
|
rules_status_service_1.rulesStatusBroadcaster.refreshRuleInUse(route.id, rule.id);
|
|
4879
4941
|
});
|
|
4942
|
+
// 服务性能打点:记录首/末 SSE 事件时间
|
|
4943
|
+
streamTiming = new stream_timing_transform_1.StreamTimingTransform(startTime);
|
|
4880
4944
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
4881
4945
|
// 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
|
|
4882
4946
|
const originalModel = (_d = req.body) === null || _d === void 0 ? void 0 : _d.model;
|
|
@@ -4911,7 +4975,7 @@ class ProxyServer {
|
|
|
4911
4975
|
return stages;
|
|
4912
4976
|
};
|
|
4913
4977
|
if (converter) {
|
|
4914
|
-
const stages = buildStages(streamSource, parser, eventCollector, converter);
|
|
4978
|
+
const stages = buildStages(streamSource, parser, eventCollector, streamTiming, converter);
|
|
4915
4979
|
stream_1.pipeline(...stages, (error) => {
|
|
4916
4980
|
if (error) {
|
|
4917
4981
|
reject(error);
|
|
@@ -4921,7 +4985,7 @@ class ProxyServer {
|
|
|
4921
4985
|
});
|
|
4922
4986
|
}
|
|
4923
4987
|
else {
|
|
4924
|
-
const stages = buildStages(streamSource, parser, eventCollector);
|
|
4988
|
+
const stages = buildStages(streamSource, parser, eventCollector, streamTiming);
|
|
4925
4989
|
stream_1.pipeline(...stages, (error) => {
|
|
4926
4990
|
if (error) {
|
|
4927
4991
|
reject(error);
|
|
@@ -52,8 +52,8 @@ class RulesStatusBroadcaster extends events_1.EventEmitter {
|
|
|
52
52
|
enumerable: true,
|
|
53
53
|
configurable: true,
|
|
54
54
|
writable: true,
|
|
55
|
-
value:
|
|
56
|
-
}); //
|
|
55
|
+
value: 120000
|
|
56
|
+
}); // 120秒无活动后标记为空闲(兜底安全网,覆盖 thinking hold 等长静默场景)
|
|
57
57
|
Object.defineProperty(this, "IDLE_DEBOUNCE_DELAY", {
|
|
58
58
|
enumerable: true,
|
|
59
59
|
configurable: true,
|
|
@@ -258,16 +258,31 @@ class RulesStatusBroadcaster extends events_1.EventEmitter {
|
|
|
258
258
|
});
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
261
|
-
*
|
|
262
|
-
* 用于 streaming 过程中持续保持 in_use
|
|
261
|
+
* 刷新规则使用中的不活动定时器(轻量级,仅重置定时器,通常不修改状态)
|
|
262
|
+
* 用于 streaming 过程中持续保持 in_use 状态。
|
|
263
|
+
*
|
|
264
|
+
* 行为:
|
|
265
|
+
* - status === 'in_use':重置不活动定时器,并清除可能 pending 的 idle debounce,
|
|
266
|
+
* 避免已触发的 idle 经 SSE 推送出去(thinking hold 场景的关键修复)。
|
|
267
|
+
* - status === 'idle':说明此前已被错误判空闲,但请求仍在出流——重新标记为 in_use
|
|
268
|
+
* 以便经 SSE 把状态推回"使用中",实现前端自愈。
|
|
269
|
+
* - status === 'error' / 'suspended':早退,这两种终态有独立恢复机制,不应被流式刷新覆盖。
|
|
263
270
|
*/
|
|
264
271
|
refreshRuleInUse(routeId, ruleId) {
|
|
265
272
|
const currentStatus = this.ruleStates.get(ruleId);
|
|
266
|
-
//
|
|
267
|
-
if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status)
|
|
273
|
+
// 终态有独立恢复机制,刷新不应覆盖
|
|
274
|
+
if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'error' || (currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'suspended') {
|
|
268
275
|
return;
|
|
276
|
+
}
|
|
269
277
|
const timeoutKey = `${routeId}:${ruleId}`;
|
|
278
|
+
// 已被错误判空闲:重新标记为 in_use(内部会清旧定时器/debounce 并 emit statusChanged → SSE 推回使用中)
|
|
279
|
+
if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'idle') {
|
|
280
|
+
this.markRuleInUse(routeId, ruleId);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// in_use:重置不活动定时器,并清除 pending 的 idle debounce(阻止已触发的 idle 经 SSE 推送)
|
|
270
284
|
this.clearRuleTimeout(timeoutKey);
|
|
285
|
+
this.clearIdleDebounce(timeoutKey);
|
|
271
286
|
const timeout = setTimeout(() => {
|
|
272
287
|
this.markRuleIdle(routeId, ruleId);
|
|
273
288
|
}, this.INACTIVITY_TIMEOUT);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamTimingTransform = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
/**
|
|
6
|
+
* StreamTimingTransform - 流式打点 Transform(服务性能统计专用)
|
|
7
|
+
*
|
|
8
|
+
* 透传所有数据(对象模式 / Buffer / string 均可),仅记录:
|
|
9
|
+
* - firstEventAt:首个被解析出的 SSE 事件流经本 Transform 的时刻(≈ 首 Token 返回时刻)
|
|
10
|
+
* - lastEventAt:最后一个事件流经的时刻(≈ 整个返回结束时刻)
|
|
11
|
+
*
|
|
12
|
+
* 由调用方在转发开始前注入 startTime(请求发起时刻),即可派生:
|
|
13
|
+
* - ttftMs = firstEventAt - startTime
|
|
14
|
+
* - generationMs = lastEventAt - firstEventAt
|
|
15
|
+
*
|
|
16
|
+
* 该 Transform 仅做时间记录,不修改任何数据内容,对转发链路零影响。
|
|
17
|
+
*/
|
|
18
|
+
class StreamTimingTransform extends stream_1.Transform {
|
|
19
|
+
constructor(startTime) {
|
|
20
|
+
super({ writableObjectMode: true, readableObjectMode: true });
|
|
21
|
+
/** 请求发起时刻(由外部注入) */
|
|
22
|
+
Object.defineProperty(this, "startTime", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: void 0
|
|
27
|
+
});
|
|
28
|
+
/** 首个事件到达时刻(未收到则为 0) */
|
|
29
|
+
Object.defineProperty(this, "firstEventAt", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: 0
|
|
34
|
+
});
|
|
35
|
+
/** 最后一个事件到达时刻 */
|
|
36
|
+
Object.defineProperty(this, "lastEventAt", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: 0
|
|
41
|
+
});
|
|
42
|
+
this.startTime = startTime;
|
|
43
|
+
}
|
|
44
|
+
_transform(chunk, _encoding, callback) {
|
|
45
|
+
try {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (this.firstEventAt === 0) {
|
|
48
|
+
this.firstEventAt = now;
|
|
49
|
+
}
|
|
50
|
+
this.lastEventAt = now;
|
|
51
|
+
this.push(chunk);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('[StreamTimingTransform] Error in _transform:', error);
|
|
55
|
+
}
|
|
56
|
+
callback();
|
|
57
|
+
}
|
|
58
|
+
/** 是否采集到至少一个事件(用于判定精确口径可用性) */
|
|
59
|
+
hasTiming() {
|
|
60
|
+
return this.firstEventAt > 0 && this.lastEventAt > 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.StreamTimingTransform = StreamTimingTransform;
|