@camstack/addon-pipeline 1.2.368 → 1.2.370

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.
Files changed (35) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-BY-aEI2s.js → custom-model-catalog-BFosahFu.js} +2 -2
  4. package/dist/{custom-model-catalog-Bnel86hH.mjs → custom-model-catalog-CVbK0xKc.mjs} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-5AhGOWWu.mjs → dist-CbFPOFAD.mjs} +407 -14
  8. package/dist/{dist-DDXsZaRH.js → dist-jkwxbcbK.js} +407 -14
  9. package/dist/{model-catalogs-DWR9EsGI.mjs → model-catalogs-DJmwfLfC.mjs} +1 -1
  10. package/dist/{model-catalogs-DJN58c_U.js → model-catalogs-Ds2hC2Br.js} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-DqZJFOzR.mjs → node-C-2fB9kH.mjs} +1 -1
  14. package/dist/{node-CiciZWts.js → node-C_1U1uzn.js} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-Batknjjt.mjs → process-memory-B3I-NvnX.mjs} +1 -1
  18. package/dist/{process-memory-BRu19Dgr.js → process-memory-Cfh1BQYg.js} +1 -1
  19. package/dist/recorder/index.js +12 -2
  20. package/dist/recorder/index.mjs +12 -2
  21. package/dist/{segment-demux-js-dlhO3Q8W.mjs → segment-demux-js-D3JBJeN6.mjs} +1 -1
  22. package/dist/{segment-demux-js-0CRf7GAb.js → segment-demux-js-uxBcJy15.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CSajlHKC.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DwwDKaZJ.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Bna0L8uC.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAvXcDKy.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-KJ1eka58.mjs → hostInit-DVwYY7oo.mjs} +2 -2
  30. package/dist/stream-broker/index.js +302 -90
  31. package/dist/stream-broker/index.mjs +302 -90
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BfVREmhg.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-5KL24-a_.mjs +0 -26
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-DDXsZaRH.js");
7
- const require_node = require("../node-CiciZWts.js");
6
+ const require_dist = require("../dist-jkwxbcbK.js");
7
+ const require_node = require("../node-C_1U1uzn.js");
8
8
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
9
9
  const require_remote_restream = require("../remote-restream-CO36Sr30.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
11
- const require_segment_demux_js = require("../segment-demux-js-0CRf7GAb.js");
11
+ const require_segment_demux_js = require("../segment-demux-js-uxBcJy15.js");
12
12
  let node_os = require("node:os");
13
13
  node_os = require_chunk.__toESM(node_os);
14
14
  let node_crypto = require("node:crypto");
@@ -2367,6 +2367,10 @@ var PacedRtpReplay = class {
2367
2367
  this.enqueuedLive++;
2368
2368
  return true;
2369
2369
  }
2370
+ /** Visit every still-queued item (probe time only; no allocation). */
2371
+ forEachQueued(visit) {
2372
+ for (const item of this.queue) visit(item);
2373
+ }
2370
2374
  /** Cancel the replay: pending packets are dropped, `onDone` fires with
2371
2375
  * `aborted: true`. Safe to call multiple times / before `start`. */
2372
2376
  abort() {
@@ -3086,6 +3090,133 @@ var RtpPacketizer = class {
3086
3090
  }
3087
3091
  };
3088
3092
  //#endregion
3093
+ //#region src/stream-broker/stream-broker/owned-buffer.ts
3094
+ /**
3095
+ * Buffers that enter a LONG-LIVED ring own their backing store.
3096
+ *
3097
+ * Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
3098
+ * below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
3099
+ * That is the right choice for the ingest path — a start code, a 4-byte
3100
+ * interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
3101
+ * within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
3102
+ * for 60 s in a clip ring pins its whole slab, and with it every short-lived
3103
+ * packet that happened to be allocated beside it. On the hub that held
3104
+ * 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
3105
+ * pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
3106
+ *
3107
+ * The ring byte caps counted payload bytes, so they never saw it. Both rings
3108
+ * now pass every entry through {@link detachFromPool} on the way in and
3109
+ * account it with {@link backingStoreBytes}, so the cap measures what the
3110
+ * entry costs. Short-lived traffic stays pooled.
3111
+ */
3112
+ /** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
3113
+ function ownsBackingStore(buf) {
3114
+ return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
3115
+ }
3116
+ /**
3117
+ * What retaining `buf` really costs: its own length when it owns its
3118
+ * ArrayBuffer, the whole shared slab when it is a view of one.
3119
+ */
3120
+ function backingStoreBytes(buf) {
3121
+ return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
3122
+ }
3123
+ /**
3124
+ * `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
3125
+ * buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
3126
+ * runtime detail), else a copy that does. One copy, never two: a buffer that is
3127
+ * already unpooled is returned as-is.
3128
+ */
3129
+ function detachFromPool(buf) {
3130
+ if (ownsBackingStore(buf)) return buf;
3131
+ const owned = Buffer.allocUnsafeSlow(buf.length);
3132
+ buf.copy(owned);
3133
+ return owned;
3134
+ }
3135
+ /**
3136
+ * A copy of `src[start, end)` that OWNS its backing store — the shape a reader
3137
+ * hands out when the packet it cuts from its socket buffer is going to be
3138
+ * retained anyway.
3139
+ *
3140
+ * Every source video packet is: `captureRtpForPreBuffer` keeps it for the life
3141
+ * of a GOP, and the restreamer's keyframe cache keeps the keyframe ones. With
3142
+ * the readers cutting packets as `Buffer.from(subarray)` — a pooled view — the
3143
+ * ring then had to copy each one a second time to own it: two copies of every
3144
+ * packet on the ingest path (measured 15.1 + 14.8 MB/s on the hub's load).
3145
+ * Cutting the packet owned up front makes the ring's and the cache's
3146
+ * `detachFromPool` an identity, so the packet is copied once and shared by the
3147
+ * reader's fan-out, the ring, the cache and every session's write queue.
3148
+ */
3149
+ function copyOwned(src, start, end) {
3150
+ const owned = Buffer.allocUnsafeSlow(end - start);
3151
+ src.copy(owned, 0, start, end);
3152
+ recordCopyOwned(end - start);
3153
+ return owned;
3154
+ }
3155
+ /** Accumulates entries; each distinct backing ArrayBuffer is summed once. */
3156
+ var BackingTally = class {
3157
+ seen = /* @__PURE__ */ new Set();
3158
+ entries = 0;
3159
+ payload = 0;
3160
+ naive = 0;
3161
+ distinct = 0;
3162
+ capped = false;
3163
+ add(buf) {
3164
+ if (this.entries >= 2e5) {
3165
+ this.capped = true;
3166
+ return;
3167
+ }
3168
+ this.entries += 1;
3169
+ this.payload += buf.length;
3170
+ const backing = backingStoreBytes(buf);
3171
+ this.naive += backing;
3172
+ if (!this.seen.has(buf.buffer)) {
3173
+ this.seen.add(buf.buffer);
3174
+ this.distinct += backing;
3175
+ }
3176
+ }
3177
+ totals() {
3178
+ return {
3179
+ entries: this.entries,
3180
+ payloadBytes: this.payload,
3181
+ naiveBackingBytes: this.naive,
3182
+ distinctBackingBytes: this.distinct,
3183
+ capped: this.capped
3184
+ };
3185
+ }
3186
+ };
3187
+ /** Add every populated slot of a sender's `rtpCache` (≤ RTP_HISTORY_SIZE). */
3188
+ function tallyRtpCache(tally, cache) {
3189
+ if (cache === void 0) return;
3190
+ for (let i = 0; i < cache.length; i++) {
3191
+ const slot = cache[i];
3192
+ if (slot !== void 0) tally.add(slot.payload);
3193
+ }
3194
+ }
3195
+ /** Distinct backing bytes a session pins across all four structures. */
3196
+ function sessionBackingBytes(r) {
3197
+ return r.rtpCache.distinctBackingBytes + r.pushQueue.distinctBackingBytes + r.rtpReplay.distinctBackingBytes + r.scrub.distinctBackingBytes;
3198
+ }
3199
+ function mbOf(bytes) {
3200
+ return Number((bytes / (1024 * 1024)).toFixed(1));
3201
+ }
3202
+ /** Flat log meta for the session open / close lines. */
3203
+ function sessionMemMeta(r) {
3204
+ return {
3205
+ rtpCacheEntries: r.rtpCache.entries,
3206
+ rtpCachePayloadMb: mbOf(r.rtpCache.payloadBytes),
3207
+ rtpCacheNaiveMb: mbOf(r.rtpCache.naiveBackingBytes),
3208
+ rtpCacheBackingMb: mbOf(r.rtpCache.distinctBackingBytes),
3209
+ pushQueueFrames: r.pushQueue.entries,
3210
+ pushQueueBackingMb: mbOf(r.pushQueue.distinctBackingBytes),
3211
+ rtpReplayPackets: r.rtpReplay.entries,
3212
+ rtpReplayBackingMb: mbOf(r.rtpReplay.distinctBackingBytes),
3213
+ scrubPayloadMb: mbOf(r.scrub.payloadBytes),
3214
+ scrubBackingMb: mbOf(r.scrub.distinctBackingBytes),
3215
+ sessionBackingMb: mbOf(sessionBackingBytes(r)),
3216
+ memCapped: r.rtpCache.capped || r.pushQueue.capped || r.rtpReplay.capped || r.scrub.capped
3217
+ };
3218
+ }
3219
+ //#endregion
3089
3220
  //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3090
3221
  /**
3091
3222
  * broker-mem-probe — what owns the broker runner's NATIVE memory.
@@ -3329,8 +3460,58 @@ function formatBrokerMem(input) {
3329
3460
  const w = input.worker;
3330
3461
  if (w !== void 0 && w !== null) parts.push(`workers=${w.workers}`, `workerHeapMb=${mb(w.heapBytes)}`, `workerPhysicalMb=${mb(w.physicalBytes)}`, `workerUsedMb=${mb(w.usedBytes)}`, `workerAgeS=${Math.round(w.maxAgeMs / 1e3)}`, `workerIdleExit=${w.idleTerminates ? "yes" : "never"}`);
3331
3462
  parts.push(`brokers=${samples.length}`, `ingesting=${ingesting}`, `annexBPlanes=${planes}`, `rtsp=${rtsp}`, `webrtc=${input.webrtcSessions}`, `encoded=${kindList(consumers)}`, `audioSubs=${audio}`, `pipeClients=${pipe}`, `egress=${input.egressTranscodes}`, `children=${input.childProcesses}`, `opened=${churn.opened}`, `closed=${churn.closed}`, `openedKinds=${kindList(churn.openedByKind)}`, `closedKinds=${kindList(churn.closedByKind)}`, `preBufferMb=${mb(pre)}`, `clipMb=${mb(clip)}`, `rtpRingMb=${mb(rtp)}`, `keyframeMb=${mb(kf)}`, `writeQueueSumMb=${mb(qSum)}`, `writeQueueMaxMb=${mb(qMax)}`, `copiedPackets=${copied.packets}`, `copiedMb=${mb(copied.bytes)}`, `topRingMb=${top === "" ? "none" : top}`);
3463
+ const sessions = input.sessions;
3464
+ if (sessions !== void 0) parts.push(...formatSessionMem(sessions, input));
3332
3465
  return parts.join(" ");
3333
3466
  }
3467
+ /**
3468
+ * Sums over every WebRTC session, each distinct backing store counted once PER
3469
+ * SESSION, and the three views of the NACK history so pinning shows:
3470
+ * `rtpCachePayloadMb` (what the packets say), `rtpCacheNaiveMb` (each entry's
3471
+ * own backing, a shared slab repeated) and `rtpCacheBackingMb` (each slab once).
3472
+ * `fragMb` is the brk heap NOT explained by v8 external memory.
3473
+ */
3474
+ function formatSessionMem(sessions, input) {
3475
+ let rtpEntries = 0;
3476
+ let rtpPayload = 0;
3477
+ let rtpNaive = 0;
3478
+ let rtpBacking = 0;
3479
+ let pushFrames = 0;
3480
+ let pushBacking = 0;
3481
+ let replayPackets = 0;
3482
+ let replayBacking = 0;
3483
+ let scrubPayload = 0;
3484
+ let scrubBacking = 0;
3485
+ for (const r of sessions) {
3486
+ rtpEntries += r.rtpCache.entries;
3487
+ rtpPayload += r.rtpCache.payloadBytes;
3488
+ rtpNaive += r.rtpCache.naiveBackingBytes;
3489
+ rtpBacking += r.rtpCache.distinctBackingBytes;
3490
+ pushFrames += r.pushQueue.entries;
3491
+ pushBacking += r.pushQueue.distinctBackingBytes;
3492
+ replayPackets += r.rtpReplay.entries;
3493
+ replayBacking += r.rtpReplay.distinctBackingBytes;
3494
+ scrubPayload += r.scrub.payloadBytes;
3495
+ scrubBacking += r.scrub.distinctBackingBytes;
3496
+ }
3497
+ const top = [...sessions].sort((a, b) => sessionBackingBytes(b) - sessionBackingBytes(a)).slice(0, 3).map((r) => `${r.deviceId ?? "x"}:${mb(sessionBackingBytes(r))}`).join(",");
3498
+ const parts = [
3499
+ `rtpCacheEntries=${rtpEntries}`,
3500
+ `rtpCachePayloadMb=${mb(rtpPayload)}`,
3501
+ `rtpCacheNaiveMb=${mb(rtpNaive)}`,
3502
+ `rtpCacheBackingMb=${mb(rtpBacking)}`,
3503
+ `pushQueueFrames=${pushFrames}`,
3504
+ `pushQueueBackingMb=${mb(pushBacking)}`,
3505
+ `rtpReplayPackets=${replayPackets}`,
3506
+ `rtpReplayBackingMb=${mb(replayBacking)}`,
3507
+ `scrubPayloadMb=${mb(scrubPayload)}`,
3508
+ `scrubBackingMb=${mb(scrubBacking)}`
3509
+ ];
3510
+ if (input.arrayBuffersBytes !== void 0) parts.push(`arrayBuffersMb=${mb(input.arrayBuffersBytes)}`);
3511
+ if (input.proc.maps !== null && input.v8 !== void 0 && input.v8 !== null) parts.push(`fragMb=${mb(input.proc.maps.heapBytes - input.v8.externalBytes)}`);
3512
+ parts.push(`topSessionMb=${top === "" ? "none" : top}`);
3513
+ return parts;
3514
+ }
3334
3515
  /** Count entries named `name` in `process.getActiveResourcesInfo()`. */
3335
3516
  function countActiveResources(resources, name) {
3336
3517
  let n = 0;
@@ -3338,69 +3519,6 @@ function countActiveResources(resources, name) {
3338
3519
  return n;
3339
3520
  }
3340
3521
  //#endregion
3341
- //#region src/stream-broker/stream-broker/owned-buffer.ts
3342
- /**
3343
- * Buffers that enter a LONG-LIVED ring own their backing store.
3344
- *
3345
- * Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
3346
- * below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
3347
- * That is the right choice for the ingest path — a start code, a 4-byte
3348
- * interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
3349
- * within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
3350
- * for 60 s in a clip ring pins its whole slab, and with it every short-lived
3351
- * packet that happened to be allocated beside it. On the hub that held
3352
- * 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
3353
- * pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
3354
- *
3355
- * The ring byte caps counted payload bytes, so they never saw it. Both rings
3356
- * now pass every entry through {@link detachFromPool} on the way in and
3357
- * account it with {@link backingStoreBytes}, so the cap measures what the
3358
- * entry costs. Short-lived traffic stays pooled.
3359
- */
3360
- /** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
3361
- function ownsBackingStore(buf) {
3362
- return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
3363
- }
3364
- /**
3365
- * What retaining `buf` really costs: its own length when it owns its
3366
- * ArrayBuffer, the whole shared slab when it is a view of one.
3367
- */
3368
- function backingStoreBytes(buf) {
3369
- return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
3370
- }
3371
- /**
3372
- * `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
3373
- * buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
3374
- * runtime detail), else a copy that does. One copy, never two: a buffer that is
3375
- * already unpooled is returned as-is.
3376
- */
3377
- function detachFromPool(buf) {
3378
- if (ownsBackingStore(buf)) return buf;
3379
- const owned = Buffer.allocUnsafeSlow(buf.length);
3380
- buf.copy(owned);
3381
- return owned;
3382
- }
3383
- /**
3384
- * A copy of `src[start, end)` that OWNS its backing store — the shape a reader
3385
- * hands out when the packet it cuts from its socket buffer is going to be
3386
- * retained anyway.
3387
- *
3388
- * Every source video packet is: `captureRtpForPreBuffer` keeps it for the life
3389
- * of a GOP, and the restreamer's keyframe cache keeps the keyframe ones. With
3390
- * the readers cutting packets as `Buffer.from(subarray)` — a pooled view — the
3391
- * ring then had to copy each one a second time to own it: two copies of every
3392
- * packet on the ingest path (measured 15.1 + 14.8 MB/s on the hub's load).
3393
- * Cutting the packet owned up front makes the ring's and the cache's
3394
- * `detachFromPool` an identity, so the packet is copied once and shared by the
3395
- * reader's fan-out, the ring, the cache and every session's write queue.
3396
- */
3397
- function copyOwned(src, start, end) {
3398
- const owned = Buffer.allocUnsafeSlow(end - start);
3399
- src.copy(owned, 0, start, end);
3400
- recordCopyOwned(end - start);
3401
- return owned;
3402
- }
3403
- //#endregion
3404
3522
  //#region src/stream-broker/rtsp/sdp-builder.ts
3405
3523
  function buildSdp(options) {
3406
3524
  const { codec, parameterSets, streamName } = options;
@@ -24943,6 +25061,23 @@ var AdaptiveSession = class AdaptiveSession {
24943
25061
  } });
24944
25062
  }
24945
25063
  /** Whether the session has an active feed (vs idle/pooled). */
25064
+ /**
25065
+ * What this session pins outside the heap, by backing store: werift's NACK
25066
+ * history (video + audio, one tally so a slab shared by both counts once) and
25067
+ * the paced replay queue. Probe / lifecycle-line time only — it walks
25068
+ * ≤ 2 × RTP_HISTORY_SIZE slots and the replay tail, never per packet.
25069
+ */
25070
+ readMemTotals() {
25071
+ const cache = new BackingTally();
25072
+ tallyRtpCache(cache, this.videoSender?.rtpCache);
25073
+ tallyRtpCache(cache, this.audioSender?.rtpCache);
25074
+ const replay = new BackingTally();
25075
+ this.activeRtpReplay?.forEachQueued((pkt) => replay.add(pkt));
25076
+ return {
25077
+ rtpCache: cache.totals(),
25078
+ rtpReplay: replay.totals()
25079
+ };
25080
+ }
24946
25081
  get isFeeding() {
24947
25082
  return this.feedAbort !== null && !this.feedAbort.signal.aborted;
24948
25083
  }
@@ -28206,6 +28341,13 @@ var ScrubFragmentCache = class {
28206
28341
  get size() {
28207
28342
  return this.map.size;
28208
28343
  }
28344
+ /**
28345
+ * Feed every cached access unit to `tally` — the BACKING-store view of what
28346
+ * `byteSize` counts by `.length`. Probe time only (`session-mem.ts`).
28347
+ */
28348
+ tallyBacking(tally) {
28349
+ for (const frag of this.map.values()) for (const au of frag.aus) tally.add(au.data);
28350
+ }
28209
28351
  };
28210
28352
  //#endregion
28211
28353
  //#region src/stream-broker/recorded/segment-pts-repair.ts
@@ -35260,7 +35402,8 @@ var BrokerWebrtcServer = class {
35260
35402
  iceServers: setup.iceServerCount,
35261
35403
  codec: setup.sessionCodec,
35262
35404
  useRtpRepacketizer: setup.useRtpRepacketizer,
35263
- relayOnly: opts.relayOnly === true
35405
+ relayOnly: opts.relayOnly === true,
35406
+ ...this.sessionMemMetaFor(setup.sessionId)
35264
35407
  } });
35265
35408
  return {
35266
35409
  sessionId: setup.sessionId,
@@ -35310,7 +35453,8 @@ var BrokerWebrtcServer = class {
35310
35453
  setupMs,
35311
35454
  negotiateMs,
35312
35455
  totalMs: Date.now() - offerStart,
35313
- ...setup.setupTimings
35456
+ ...setup.setupTimings,
35457
+ ...this.sessionMemMetaFor(setup.sessionId)
35314
35458
  } });
35315
35459
  return {
35316
35460
  sessionId: setup.sessionId,
@@ -35419,7 +35563,7 @@ var BrokerWebrtcServer = class {
35419
35563
  let framesDroppedSinceLog = 0;
35420
35564
  let purgesSinceLog = 0;
35421
35565
  let unalignedPurgesSinceLog = 0;
35422
- const { source, pushFrame, close: closeSource } = createPushFrameSource((count, keyframeAligned) => {
35566
+ const { source, pushFrame, close: closeSource, forEachQueued: forEachQueuedFrame } = createPushFrameSource((count, keyframeAligned) => {
35423
35567
  framesDroppedSinceLog += count;
35424
35568
  purgesSinceLog += 1;
35425
35569
  if (!keyframeAligned) unalignedPurgesSinceLog += 1;
@@ -35698,6 +35842,7 @@ var BrokerWebrtcServer = class {
35698
35842
  unsubSdpParams,
35699
35843
  unsubSourceRestart,
35700
35844
  closeSource,
35845
+ forEachQueuedFrame,
35701
35846
  ...talkback !== void 0 ? { closeTalkback: () => void talkback.close() } : {},
35702
35847
  rebind: {
35703
35848
  deviceId,
@@ -35990,6 +36135,7 @@ var BrokerWebrtcServer = class {
35990
36135
  })
35991
36136
  });
35992
36137
  entry.timeline = timeline;
36138
+ entry.scrubCache = scrubCache;
35993
36139
  sessionLogger.info("recorded: TimelineSession wired (control accessors present)", { meta: {
35994
36140
  sessionId,
35995
36141
  deviceId
@@ -36121,6 +36267,55 @@ var BrokerWebrtcServer = class {
36121
36267
  return this.sessions.size;
36122
36268
  }
36123
36269
  /**
36270
+ * One session's pinned-memory reading, by backing store (`session-mem.ts`).
36271
+ * Walks the session's NACK history, push queue, replay tail and scrub cache:
36272
+ * call at probe / lifecycle-line time only.
36273
+ */
36274
+ readSessionMem(sessionId, entry) {
36275
+ const own = entry.session.readMemTotals();
36276
+ const push = new BackingTally();
36277
+ entry.forEachQueuedFrame((mf) => push.add(mf.frame.data));
36278
+ const scrub = new BackingTally();
36279
+ entry.scrubCache?.tallyBacking(scrub);
36280
+ return {
36281
+ sessionId,
36282
+ deviceId: deviceIdFromBrokerId(entry.brokerId),
36283
+ rtpCache: own.rtpCache,
36284
+ rtpReplay: own.rtpReplay,
36285
+ pushQueue: push.totals(),
36286
+ scrub: scrub.totals()
36287
+ };
36288
+ }
36289
+ /** Every live session's reading, for the `[broker-mem]` line. */
36290
+ getSessionMemReadings() {
36291
+ const readings = [];
36292
+ for (const [sessionId, entry] of this.sessions) try {
36293
+ readings.push(this.readSessionMem(sessionId, entry));
36294
+ } catch (err) {
36295
+ this.logger.warn("session memory reading failed", {
36296
+ tags: { deviceId: deviceIdFromBrokerId(entry.brokerId) },
36297
+ meta: {
36298
+ sessionId,
36299
+ error: require_dist.errMsg(err)
36300
+ }
36301
+ });
36302
+ }
36303
+ return readings;
36304
+ }
36305
+ /** The counters a lifecycle line carries; empty when the session is gone. */
36306
+ sessionMemMetaFor(sessionId) {
36307
+ const entry = this.sessions.get(sessionId);
36308
+ if (entry === void 0) return {};
36309
+ try {
36310
+ return sessionMemMeta(this.readSessionMem(sessionId, entry));
36311
+ } catch (err) {
36312
+ return {
36313
+ memReadFailed: true,
36314
+ memReadError: require_dist.errMsg(err)
36315
+ };
36316
+ }
36317
+ }
36318
+ /**
36124
36319
  * People watching LIVE through this server, per camera (`live-viewers.ts`):
36125
36320
  * sessions of a viewer kind whose track is on the live edge. A session on a
36126
36321
  * recorded playhead is not watching live; the system's own subscriptions
@@ -36929,6 +37124,15 @@ var BrokerWebrtcServer = class {
36929
37124
  cleanupSession(sessionId) {
36930
37125
  const entry = this.sessions.get(sessionId);
36931
37126
  if (!entry) return;
37127
+ const deviceId = deviceIdFromBrokerId(entry.brokerId);
37128
+ this.logger.info("WebRTC session closing: pinned-memory counters", {
37129
+ tags: { deviceId },
37130
+ meta: {
37131
+ sessionId,
37132
+ deviceId,
37133
+ ...this.sessionMemMetaFor(sessionId)
37134
+ }
37135
+ });
36932
37136
  this.sessions.delete(sessionId);
36933
37137
  reportConsumerEvent(this.logger, "detach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
36934
37138
  entry.pendingSwitch?.cancel();
@@ -37123,28 +37327,33 @@ function createPushFrameSource(onFramesDropped) {
37123
37327
  });
37124
37328
  }
37125
37329
  };
37126
- return {
37127
- source: (async function* () {
37128
- try {
37129
- while (true) {
37130
- const item = queue.shift();
37131
- if (item) {
37132
- yield item;
37133
- continue;
37134
- }
37135
- if (done) return;
37136
- const result = await new Promise((r) => {
37137
- resolve = r;
37138
- });
37139
- if (result.done) return;
37140
- yield result.value;
37330
+ const source = (async function* () {
37331
+ try {
37332
+ while (true) {
37333
+ const item = queue.shift();
37334
+ if (item) {
37335
+ yield item;
37336
+ continue;
37141
37337
  }
37142
- } finally {
37143
- done = true;
37338
+ if (done) return;
37339
+ const result = await new Promise((r) => {
37340
+ resolve = r;
37341
+ });
37342
+ if (result.done) return;
37343
+ yield result.value;
37144
37344
  }
37145
- })(),
37345
+ } finally {
37346
+ done = true;
37347
+ }
37348
+ })();
37349
+ const forEachQueued = (visit) => {
37350
+ for (const frame of queue) visit(frame);
37351
+ };
37352
+ return {
37353
+ source,
37146
37354
  pushFrame,
37147
- close
37355
+ close,
37356
+ forEachQueued
37148
37357
  };
37149
37358
  }
37150
37359
  //#endregion
@@ -37678,7 +37887,9 @@ async function emitOnce(deps) {
37678
37887
  churn: consumerChurn.drain(),
37679
37888
  copied: readCopyOwnedTotals(),
37680
37889
  v8: readV8Heap(),
37681
- worker
37890
+ worker,
37891
+ sessions: deps.sessionMem(),
37892
+ arrayBuffersBytes: process.memoryUsage().arrayBuffers
37682
37893
  });
37683
37894
  deps.logger.info(line);
37684
37895
  }
@@ -38229,7 +38440,8 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
38229
38440
  this.stopBrokerMemReport = startBrokerMemReport({
38230
38441
  logger: this.ctx.logger,
38231
38442
  manager: this.brokerManager,
38232
- webrtcSessions: () => webrtcServer.getSessionCount()
38443
+ webrtcSessions: () => webrtcServer.getSessionCount(),
38444
+ sessionMem: () => webrtcServer.getSessionMemReadings()
38233
38445
  });
38234
38446
  const widgetsProvider = { listWidgets: async () => [{
38235
38447
  tab: "device-tab",