@livedesk/hub 0.1.26 → 0.1.28
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/package.json +2 -2
- package/src/agents/agent-manager.js +27 -11
- package/src/agents/codex-agent-runtime.js +325 -41
- package/src/frame-packet-contract.mjs +427 -0
- package/src/live-capture-transition-retry.mjs +297 -0
- package/src/mode4-atlas-pool.js +17 -3
- package/src/mode4-atlas-worker.js +134 -21
- package/src/mode4-atlas.js +180 -16
- package/src/remote-audio-subscription-contract.mjs +109 -0
- package/src/remote-audio-subscription-contract.test.mjs +72 -0
- package/src/remote-hub.js +6374 -3890
- package/src/server.js +3234 -2202
- package/src/transport/agent-binary-ingress.js +810 -0
- package/src/transport/relay-hub-control.js +372 -56
- package/src/transport/udp-hub-transport.js +215 -51
- package/src/transport/udp-protocol.js +302 -70
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
export function createRemoteFramePacketMetrics() {
|
|
2
|
+
return {
|
|
3
|
+
packetBuilds: 0,
|
|
4
|
+
packetAllocations: 0,
|
|
5
|
+
metadataSerializations: 0,
|
|
6
|
+
metadataUtf8Writes: 0,
|
|
7
|
+
payloadCopyOperations: 0,
|
|
8
|
+
payloadCopyBytes: 0,
|
|
9
|
+
packetBytes: 0,
|
|
10
|
+
fanoutFrames: 0,
|
|
11
|
+
fanoutEligibleSubscribers: 0,
|
|
12
|
+
fanoutEnqueuedSubscribers: 0,
|
|
13
|
+
fanoutMaxEligibleSubscribers: 0,
|
|
14
|
+
sparseTelemetryFrames: 0,
|
|
15
|
+
sparseTelemetrySuppressedFrames: 0,
|
|
16
|
+
sparseTelemetryFields: 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const REMOTE_FRAME_SPARSE_NATIVE_TELEMETRY_FIELDS = Object.freeze([
|
|
21
|
+
'nativeCaptureTelemetryAvailable',
|
|
22
|
+
'nativeCaptureTelemetryVersion',
|
|
23
|
+
'nativeEncoderHardwareAccelerationKnown',
|
|
24
|
+
'nativeEncoderHardwareAccelerated',
|
|
25
|
+
'nativeEncoderHardwareQueryStatus',
|
|
26
|
+
'nativeCaptureDisplayId',
|
|
27
|
+
'nativeCaptureFps',
|
|
28
|
+
'nativeCompressionFps',
|
|
29
|
+
'nativeEncodedFps',
|
|
30
|
+
'nativeCaptureSampleCount',
|
|
31
|
+
'nativeEncodableFrameCount',
|
|
32
|
+
'nativeSkippedFrameCount',
|
|
33
|
+
'nativeCompressionOutputCount',
|
|
34
|
+
'nativeEncodedFrameCount',
|
|
35
|
+
'nativeEncodeFramesInFlight',
|
|
36
|
+
'nativeEncodePendingFrameCount',
|
|
37
|
+
'nativeEncodeTimestampOwnerCount',
|
|
38
|
+
'nativeEncodeTimestampOwnerHighWatermark',
|
|
39
|
+
'nativeBackpressureCoalescedFrameCount',
|
|
40
|
+
'nativeBackpressureSkippedFrameCount',
|
|
41
|
+
'nativeEncodePendingReplacedFrameCount',
|
|
42
|
+
'nativeIdleSkippedFrameCount',
|
|
43
|
+
'nativeVideoToolboxActiveSessionCount',
|
|
44
|
+
'nativeVideoToolboxSessionHighWatermark',
|
|
45
|
+
'nativeOutputQueueDepth',
|
|
46
|
+
'nativeOutputQueueBytes',
|
|
47
|
+
'nativeOutputQueueOldestAgeMs',
|
|
48
|
+
'nativeOutputQueueHighWatermark',
|
|
49
|
+
'nativeOutputQueueDroppedCount',
|
|
50
|
+
'nativeOutputQueueAwaitingKeyFrame',
|
|
51
|
+
'nativeCopyTelemetryAvailable',
|
|
52
|
+
'nativeAnnexBAccessUnitAllocationCount',
|
|
53
|
+
'nativeAnnexBPayloadCopyCount',
|
|
54
|
+
'nativeAnnexBPayloadCopiedBytes',
|
|
55
|
+
'nativeAndPipeResidenceAgeMs',
|
|
56
|
+
'pipePayloadAllocationCount',
|
|
57
|
+
'pipePayloadCopyCount',
|
|
58
|
+
'pipePayloadCopiedBytes',
|
|
59
|
+
'handoffDroppedFrameCount',
|
|
60
|
+
'handoffRecoveryKeyFrameRequestCount',
|
|
61
|
+
'handoffOldestFrameAgeMs',
|
|
62
|
+
'nativeCaptureResourceTelemetryAvailable',
|
|
63
|
+
'nativeCaptureTelemetryAt'
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const EMPTY_SPARSE_TELEMETRY = Object.freeze({});
|
|
67
|
+
const REMOTE_FRAME_NATIVE_RESOURCE_TELEMETRY_FIELDS = new Set([
|
|
68
|
+
'nativeEncodeFramesInFlight',
|
|
69
|
+
'nativeEncodePendingFrameCount',
|
|
70
|
+
'nativeEncodeTimestampOwnerCount',
|
|
71
|
+
'nativeEncodeTimestampOwnerHighWatermark',
|
|
72
|
+
'nativeBackpressureCoalescedFrameCount',
|
|
73
|
+
'nativeBackpressureSkippedFrameCount',
|
|
74
|
+
'nativeEncodePendingReplacedFrameCount',
|
|
75
|
+
'nativeIdleSkippedFrameCount',
|
|
76
|
+
'nativeVideoToolboxActiveSessionCount',
|
|
77
|
+
'nativeVideoToolboxSessionHighWatermark',
|
|
78
|
+
'nativeOutputQueueDepth',
|
|
79
|
+
'nativeOutputQueueBytes',
|
|
80
|
+
'nativeOutputQueueOldestAgeMs',
|
|
81
|
+
'nativeOutputQueueHighWatermark',
|
|
82
|
+
'nativeOutputQueueDroppedCount',
|
|
83
|
+
'nativeOutputQueueAwaitingKeyFrame',
|
|
84
|
+
'nativeCopyTelemetryAvailable',
|
|
85
|
+
'nativeAnnexBAccessUnitAllocationCount',
|
|
86
|
+
'nativeAnnexBPayloadCopyCount',
|
|
87
|
+
'nativeAnnexBPayloadCopiedBytes',
|
|
88
|
+
'nativeAndPipeResidenceAgeMs',
|
|
89
|
+
'pipePayloadAllocationCount',
|
|
90
|
+
'pipePayloadCopyCount',
|
|
91
|
+
'pipePayloadCopiedBytes',
|
|
92
|
+
'handoffDroppedFrameCount',
|
|
93
|
+
'handoffRecoveryKeyFrameRequestCount',
|
|
94
|
+
'handoffOldestFrameAgeMs'
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
function optionalFiniteNumber(value) {
|
|
98
|
+
if (value === null || value === undefined || value === '') {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const parsed = Number(value);
|
|
102
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createSparseTelemetryLane() {
|
|
106
|
+
return {
|
|
107
|
+
sessionId: '',
|
|
108
|
+
streamId: '',
|
|
109
|
+
commandId: '',
|
|
110
|
+
captureGeneration: 0,
|
|
111
|
+
monitorIndex: -1,
|
|
112
|
+
telemetryAt: '',
|
|
113
|
+
telemetryVersion: undefined,
|
|
114
|
+
captureSampleCount: undefined,
|
|
115
|
+
encodedFrameCount: undefined,
|
|
116
|
+
telemetryAvailable: undefined,
|
|
117
|
+
resourceTelemetryAvailable: undefined,
|
|
118
|
+
hardwareKnown: undefined,
|
|
119
|
+
lastEmittedAtMs: 0
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Contract:
|
|
125
|
+
* - At most two fixed lane records are retained per device (Wall + Control).
|
|
126
|
+
* - The device map has an explicit upper bound and only scans on cold-path
|
|
127
|
+
* admission after that bound has been reached.
|
|
128
|
+
* - A capture-generation handoff cannot inherit telemetry from its predecessor.
|
|
129
|
+
*/
|
|
130
|
+
export function createRemoteFrameSparseTelemetryTracker(options = {}) {
|
|
131
|
+
return {
|
|
132
|
+
cadenceMs: Math.max(250, Number(options.cadenceMs) || 1_000),
|
|
133
|
+
maxDevices: Math.max(1, Number(options.maxDevices) || 1_024),
|
|
134
|
+
devices: new Map()
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function retainRemoteFrameSparseTelemetryDevices(tracker, activeDeviceIds) {
|
|
139
|
+
if (!tracker?.devices) {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
const active = activeDeviceIds instanceof Set
|
|
143
|
+
? activeDeviceIds
|
|
144
|
+
: new Set(activeDeviceIds || []);
|
|
145
|
+
let removed = 0;
|
|
146
|
+
for (const deviceId of tracker.devices.keys()) {
|
|
147
|
+
if (!active.has(deviceId)) {
|
|
148
|
+
tracker.devices.delete(deviceId);
|
|
149
|
+
removed += 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return removed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sparseTelemetryDeviceState(tracker, deviceId, nowMs) {
|
|
156
|
+
let state = tracker.devices.get(deviceId);
|
|
157
|
+
if (state) {
|
|
158
|
+
state.lastSeenAtMs = nowMs;
|
|
159
|
+
return state;
|
|
160
|
+
}
|
|
161
|
+
if (tracker.devices.size >= tracker.maxDevices) {
|
|
162
|
+
let oldestDeviceId = '';
|
|
163
|
+
let oldestSeenAtMs = Number.POSITIVE_INFINITY;
|
|
164
|
+
for (const [candidateDeviceId, candidate] of tracker.devices) {
|
|
165
|
+
if (candidate.lastSeenAtMs < oldestSeenAtMs) {
|
|
166
|
+
oldestDeviceId = candidateDeviceId;
|
|
167
|
+
oldestSeenAtMs = candidate.lastSeenAtMs;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (oldestDeviceId) {
|
|
171
|
+
tracker.devices.delete(oldestDeviceId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
state = {
|
|
175
|
+
lastSeenAtMs: nowMs,
|
|
176
|
+
wall: createSparseTelemetryLane(),
|
|
177
|
+
control: createSparseTelemetryLane()
|
|
178
|
+
};
|
|
179
|
+
tracker.devices.set(deviceId, state);
|
|
180
|
+
return state;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function sparseTelemetryOwnerChanged(lane, frame) {
|
|
184
|
+
return lane.sessionId !== String(frame.sessionId || '')
|
|
185
|
+
|| lane.streamId !== String(frame.streamId || '')
|
|
186
|
+
|| lane.commandId !== String(frame.commandId || '')
|
|
187
|
+
|| lane.captureGeneration !== (Number(frame.captureGeneration || 0) || 0)
|
|
188
|
+
|| lane.monitorIndex !== (Number.isFinite(Number(frame.monitorIndex)) ? Number(frame.monitorIndex) : 0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function updateSparseTelemetryOwner(lane, frame) {
|
|
192
|
+
lane.sessionId = String(frame.sessionId || '');
|
|
193
|
+
lane.streamId = String(frame.streamId || '');
|
|
194
|
+
lane.commandId = String(frame.commandId || '');
|
|
195
|
+
lane.captureGeneration = Number(frame.captureGeneration || 0) || 0;
|
|
196
|
+
lane.monitorIndex = Number.isFinite(Number(frame.monitorIndex)) ? Number(frame.monitorIndex) : 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function appendSparseTelemetryValue(target, frame, field) {
|
|
200
|
+
if (!Object.prototype.hasOwnProperty.call(frame, field)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const value = frame[field];
|
|
204
|
+
if (value === undefined || value === null || value === '') {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (field === 'nativeEncoderHardwareAccelerated'
|
|
208
|
+
|| field === 'nativeOutputQueueAwaitingKeyFrame'
|
|
209
|
+
|| field === 'nativeCopyTelemetryAvailable'
|
|
210
|
+
|| field === 'nativeCaptureTelemetryAvailable'
|
|
211
|
+
|| field === 'nativeEncoderHardwareAccelerationKnown'
|
|
212
|
+
|| field === 'nativeCaptureResourceTelemetryAvailable') {
|
|
213
|
+
if (typeof value === 'boolean') {
|
|
214
|
+
target[field] = value;
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (field === 'nativeEncoderHardwareQueryStatus' || field === 'nativeCaptureTelemetryAt') {
|
|
219
|
+
target[field] = String(value);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const parsed = optionalFiniteNumber(value);
|
|
223
|
+
if (parsed !== undefined) {
|
|
224
|
+
target[field] = parsed;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Returns only native capture diagnostics that need to cross the browser
|
|
230
|
+
* packet boundary now. Per-frame timing/currentness remains in the base packet.
|
|
231
|
+
* Repeated one-Hz native samples are not expanded into 24/30 identical JSON
|
|
232
|
+
* objects per second.
|
|
233
|
+
*/
|
|
234
|
+
export function selectSparseRemoteFrameTelemetry(
|
|
235
|
+
frame,
|
|
236
|
+
tracker,
|
|
237
|
+
nowMs = Date.now(),
|
|
238
|
+
metrics = null,
|
|
239
|
+
deviceIdOverride = ''
|
|
240
|
+
) {
|
|
241
|
+
if (!frame || !tracker?.devices) {
|
|
242
|
+
return EMPTY_SPARSE_TELEMETRY;
|
|
243
|
+
}
|
|
244
|
+
const deviceId = String(deviceIdOverride || frame.deviceId || '').trim();
|
|
245
|
+
if (!deviceId) {
|
|
246
|
+
return EMPTY_SPARSE_TELEMETRY;
|
|
247
|
+
}
|
|
248
|
+
const purpose = String(frame.streamPurpose || '').toLowerCase() === 'control'
|
|
249
|
+
? 'control'
|
|
250
|
+
: 'wall';
|
|
251
|
+
const state = sparseTelemetryDeviceState(tracker, deviceId, nowMs);
|
|
252
|
+
const lane = state[purpose];
|
|
253
|
+
const ownerChanged = sparseTelemetryOwnerChanged(lane, frame);
|
|
254
|
+
if (ownerChanged) {
|
|
255
|
+
updateSparseTelemetryOwner(lane, frame);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const telemetryAt = String(frame.nativeCaptureTelemetryAt || '');
|
|
259
|
+
const telemetryVersion = optionalFiniteNumber(frame.nativeCaptureTelemetryVersion);
|
|
260
|
+
const captureSampleCount = optionalFiniteNumber(frame.nativeCaptureSampleCount);
|
|
261
|
+
const encodedFrameCount = optionalFiniteNumber(frame.nativeEncodedFrameCount);
|
|
262
|
+
const telemetryAvailable = typeof frame.nativeCaptureTelemetryAvailable === 'boolean'
|
|
263
|
+
? frame.nativeCaptureTelemetryAvailable
|
|
264
|
+
: undefined;
|
|
265
|
+
const resourceTelemetryAvailable = typeof frame.nativeCaptureResourceTelemetryAvailable === 'boolean'
|
|
266
|
+
? frame.nativeCaptureResourceTelemetryAvailable
|
|
267
|
+
: undefined;
|
|
268
|
+
const hardwareKnown = typeof frame.nativeEncoderHardwareAccelerationKnown === 'boolean'
|
|
269
|
+
? frame.nativeEncoderHardwareAccelerationKnown
|
|
270
|
+
: undefined;
|
|
271
|
+
const sampleChanged = lane.telemetryAt !== telemetryAt
|
|
272
|
+
|| lane.telemetryVersion !== telemetryVersion
|
|
273
|
+
|| lane.captureSampleCount !== captureSampleCount
|
|
274
|
+
|| lane.encodedFrameCount !== encodedFrameCount
|
|
275
|
+
|| lane.telemetryAvailable !== telemetryAvailable
|
|
276
|
+
|| lane.resourceTelemetryAvailable !== resourceTelemetryAvailable
|
|
277
|
+
|| lane.hardwareKnown !== hardwareKnown;
|
|
278
|
+
const cadenceElapsed = nowMs - lane.lastEmittedAtMs >= tracker.cadenceMs;
|
|
279
|
+
|
|
280
|
+
lane.telemetryAt = telemetryAt;
|
|
281
|
+
lane.telemetryVersion = telemetryVersion;
|
|
282
|
+
lane.captureSampleCount = captureSampleCount;
|
|
283
|
+
lane.encodedFrameCount = encodedFrameCount;
|
|
284
|
+
lane.telemetryAvailable = telemetryAvailable;
|
|
285
|
+
lane.resourceTelemetryAvailable = resourceTelemetryAvailable;
|
|
286
|
+
lane.hardwareKnown = hardwareKnown;
|
|
287
|
+
|
|
288
|
+
if (!ownerChanged && !sampleChanged && !cadenceElapsed) {
|
|
289
|
+
incrementMetric(metrics, 'sparseTelemetrySuppressedFrames');
|
|
290
|
+
return EMPTY_SPARSE_TELEMETRY;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
lane.lastEmittedAtMs = nowMs;
|
|
294
|
+
const selected = {};
|
|
295
|
+
for (const field of REMOTE_FRAME_SPARSE_NATIVE_TELEMETRY_FIELDS) {
|
|
296
|
+
if (field !== 'nativeCaptureTelemetryAvailable' && telemetryAvailable !== true) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (REMOTE_FRAME_NATIVE_RESOURCE_TELEMETRY_FIELDS.has(field)
|
|
300
|
+
&& resourceTelemetryAvailable !== true) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
appendSparseTelemetryValue(selected, frame, field);
|
|
304
|
+
}
|
|
305
|
+
incrementMetric(metrics, 'sparseTelemetryFrames');
|
|
306
|
+
incrementMetric(metrics, 'sparseTelemetryFields', Object.keys(selected).length);
|
|
307
|
+
return selected;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function incrementMetric(metrics, property, amount = 1) {
|
|
311
|
+
if (!metrics || !Object.prototype.hasOwnProperty.call(metrics, property)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
metrics[property] = Math.max(0, Number(metrics[property] || 0) + amount);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Contract:
|
|
319
|
+
* - One call owns exactly one browser wire packet.
|
|
320
|
+
* - Metadata is serialized and UTF-8 encoded once directly into that packet.
|
|
321
|
+
* - The compressed payload is copied once into the exact packet owner.
|
|
322
|
+
* - Callers must treat the returned frozen descriptor and its Buffer as
|
|
323
|
+
* immutable and may share the same descriptor across every subscriber lane.
|
|
324
|
+
*/
|
|
325
|
+
export function buildImmutableRemoteFramePacket(metadata, payload, metrics = null) {
|
|
326
|
+
if (!Buffer.isBuffer(payload) || payload.length === 0) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const metadataJson = JSON.stringify(metadata);
|
|
331
|
+
const metadataByteLength = Buffer.byteLength(metadataJson, 'utf8');
|
|
332
|
+
const packetByteLength = 4 + metadataByteLength + payload.length;
|
|
333
|
+
const packet = Buffer.allocUnsafeSlow(packetByteLength);
|
|
334
|
+
packet.writeUInt32BE(metadataByteLength, 0);
|
|
335
|
+
const metadataBytesWritten = packet.write(
|
|
336
|
+
metadataJson,
|
|
337
|
+
4,
|
|
338
|
+
metadataByteLength,
|
|
339
|
+
'utf8'
|
|
340
|
+
);
|
|
341
|
+
if (metadataBytesWritten !== metadataByteLength) {
|
|
342
|
+
throw new Error('remote-frame-metadata-write-incomplete');
|
|
343
|
+
}
|
|
344
|
+
const payloadBytesWritten = payload.copy(packet, 4 + metadataByteLength);
|
|
345
|
+
if (payloadBytesWritten !== payload.length) {
|
|
346
|
+
throw new Error('remote-frame-payload-copy-incomplete');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
incrementMetric(metrics, 'packetBuilds');
|
|
350
|
+
incrementMetric(metrics, 'packetAllocations');
|
|
351
|
+
incrementMetric(metrics, 'metadataSerializations');
|
|
352
|
+
incrementMetric(metrics, 'metadataUtf8Writes');
|
|
353
|
+
incrementMetric(metrics, 'payloadCopyOperations');
|
|
354
|
+
incrementMetric(metrics, 'payloadCopyBytes', payload.length);
|
|
355
|
+
incrementMetric(metrics, 'packetBytes', packetByteLength);
|
|
356
|
+
|
|
357
|
+
return Object.freeze({
|
|
358
|
+
packet,
|
|
359
|
+
byteLength: packetByteLength,
|
|
360
|
+
metadataByteLength,
|
|
361
|
+
payloadByteLength: payload.length
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Fan out one immutable packet owner without rebuilding subscriber-specific
|
|
367
|
+
* metadata or payload storage. Per-subscriber queue admission remains the
|
|
368
|
+
* enqueue callback's responsibility.
|
|
369
|
+
*/
|
|
370
|
+
export function fanoutImmutableRemoteFramePacket(
|
|
371
|
+
packetOwner,
|
|
372
|
+
eligibleSubscribers,
|
|
373
|
+
enqueue,
|
|
374
|
+
metrics = null
|
|
375
|
+
) {
|
|
376
|
+
if (!packetOwner?.packet || typeof enqueue !== 'function') {
|
|
377
|
+
return {
|
|
378
|
+
eligibleSubscribers: 0,
|
|
379
|
+
enqueuedSubscribers: 0
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const subscribers = Array.isArray(eligibleSubscribers)
|
|
384
|
+
? eligibleSubscribers
|
|
385
|
+
: [...(eligibleSubscribers || [])];
|
|
386
|
+
let enqueuedSubscribers = 0;
|
|
387
|
+
for (const subscriber of subscribers) {
|
|
388
|
+
if (enqueue(subscriber, packetOwner) === true) {
|
|
389
|
+
enqueuedSubscribers += 1;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
incrementMetric(metrics, 'fanoutFrames');
|
|
394
|
+
incrementMetric(metrics, 'fanoutEligibleSubscribers', subscribers.length);
|
|
395
|
+
incrementMetric(metrics, 'fanoutEnqueuedSubscribers', enqueuedSubscribers);
|
|
396
|
+
if (metrics) {
|
|
397
|
+
metrics.fanoutMaxEligibleSubscribers = Math.max(
|
|
398
|
+
Math.max(0, Number(metrics.fanoutMaxEligibleSubscribers || 0)),
|
|
399
|
+
subscribers.length
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
eligibleSubscribers: subscribers.length,
|
|
405
|
+
enqueuedSubscribers
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function snapshotRemoteFramePacketMetrics(metrics) {
|
|
410
|
+
const source = metrics || {};
|
|
411
|
+
return {
|
|
412
|
+
packetBuilds: Math.max(0, Number(source.packetBuilds || 0)),
|
|
413
|
+
packetAllocations: Math.max(0, Number(source.packetAllocations || 0)),
|
|
414
|
+
metadataSerializations: Math.max(0, Number(source.metadataSerializations || 0)),
|
|
415
|
+
metadataUtf8Writes: Math.max(0, Number(source.metadataUtf8Writes || 0)),
|
|
416
|
+
payloadCopyOperations: Math.max(0, Number(source.payloadCopyOperations || 0)),
|
|
417
|
+
payloadCopyBytes: Math.max(0, Number(source.payloadCopyBytes || 0)),
|
|
418
|
+
packetBytes: Math.max(0, Number(source.packetBytes || 0)),
|
|
419
|
+
fanoutFrames: Math.max(0, Number(source.fanoutFrames || 0)),
|
|
420
|
+
fanoutEligibleSubscribers: Math.max(0, Number(source.fanoutEligibleSubscribers || 0)),
|
|
421
|
+
fanoutEnqueuedSubscribers: Math.max(0, Number(source.fanoutEnqueuedSubscribers || 0)),
|
|
422
|
+
fanoutMaxEligibleSubscribers: Math.max(0, Number(source.fanoutMaxEligibleSubscribers || 0)),
|
|
423
|
+
sparseTelemetryFrames: Math.max(0, Number(source.sparseTelemetryFrames || 0)),
|
|
424
|
+
sparseTelemetrySuppressedFrames: Math.max(0, Number(source.sparseTelemetrySuppressedFrames || 0)),
|
|
425
|
+
sparseTelemetryFields: Math.max(0, Number(source.sparseTelemetryFields || 0))
|
|
426
|
+
};
|
|
427
|
+
}
|