@camstack/addon-pipeline 1.2.365 → 1.2.367

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-CtpK3FKL.mjs → custom-model-catalog-BqKEZYFI.mjs} +2 -2
  4. package/dist/{custom-model-catalog-C34VqcPo.js → custom-model-catalog-CESZ9DM5.js} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-i4kS_3O7.js → dist-BmQm6_nm.js} +19889 -1345
  8. package/dist/{dist-BshC0NZR.mjs → dist-ClKIJKn0.mjs} +19889 -1345
  9. package/dist/{model-catalogs-Cy7mZRWh.js → model-catalogs-B3lwxS5R.js} +1 -1
  10. package/dist/{model-catalogs-DNx5ATgm.mjs → model-catalogs-DKHNVZC0.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-TzgGsCvW.js → node-BqTmCMlP.js} +1 -1
  14. package/dist/{node-Dqmrb990.mjs → node-DlpWXqvO.mjs} +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-DyfrbFY4.mjs → process-memory-BjIiiXGZ.mjs} +1 -1
  18. package/dist/{process-memory-DLCPFTGC.js → process-memory-DHF3ynR3.js} +1 -1
  19. package/dist/recorder/index.js +2 -2
  20. package/dist/recorder/index.mjs +2 -2
  21. package/dist/{segment-demux-js-C9AYdKkD.js → segment-demux-js-D1Up0mTP.js} +1 -1
  22. package/dist/{segment-demux-js-COoPqx93.mjs → segment-demux-js-Du_eAog1.mjs} +1 -1
  23. package/dist/stream-broker/_stub.js +83 -83
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVgnss_e.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CRxJwkgE.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-CIBBQAJc.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-DNGQ-Tic.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-ZvA7Cl_1.mjs → hostInit-DEnYuDjZ.mjs} +2 -2
  30. package/dist/stream-broker/index.js +441 -17
  31. package/dist/stream-broker/index.mjs +441 -17
  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-DWI1BCfa.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-BUBmyDde.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-i4kS_3O7.js");
7
- const require_node = require("../node-TzgGsCvW.js");
6
+ const require_dist = require("../dist-BmQm6_nm.js");
7
+ const require_node = require("../node-BqTmCMlP.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-C9AYdKkD.js");
11
+ const require_segment_demux_js = require("../segment-demux-js-D1Up0mTP.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");
@@ -22,6 +22,7 @@ let node_fs_promises = require("node:fs/promises");
22
22
  let node_url = require("node:url");
23
23
  let node_module = require("node:module");
24
24
  let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
25
+ let node_v8 = require("node:v8");
25
26
  let node_net = require("node:net");
26
27
  node_net = require_chunk.__toESM(node_net);
27
28
  let node_dgram = require("node:dgram");
@@ -3085,6 +3086,258 @@ var RtpPacketizer = class {
3085
3086
  }
3086
3087
  };
3087
3088
  //#endregion
3089
+ //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3090
+ /**
3091
+ * broker-mem-probe — what owns the broker runner's NATIVE memory.
3092
+ *
3093
+ * The runner's `[mem]` heartbeat (`kernel/heap-watch.ts`) says how big the
3094
+ * process is; it cannot say who holds it. On the hub, `stream-broker` grew
3095
+ * 250 -> 1015 MB in steps while `heapUsed` stayed at ~90 MB and `arrayBuffers`
3096
+ * at ~150 MB, so ~600 MB sat outside everything the heartbeat reports. This
3097
+ * module is the attribution half: one `[broker-mem]` line every five minutes
3098
+ * carrying the kernel's view of the process (RssAnon / RssFile, the `[heap]`
3099
+ * brk mapping, the count of anonymous mappings) NEXT TO the broker's own
3100
+ * structures (rings, write queues, consumers by kind, egress children), so a
3101
+ * step in RSS can be laid against what changed.
3102
+ *
3103
+ * It is a probe, not a fix: no caps, no allocator tuning, no snapshot. Cost is
3104
+ * O(brokers + sessions) per line and O(1) per packet (two integer adds in
3105
+ * `copyOwned`). Everything here is a pure function over plain data so the
3106
+ * aggregation and the `/proc` parsers are testable on fixture text.
3107
+ */
3108
+ var MB = 1024 * 1024;
3109
+ /** Cadence of the `[broker-mem]` line — aligned with the runner's `[mem]`. */
3110
+ var BROKER_MEM_INTERVAL_MS = 5 * 6e4;
3111
+ var copiedPackets = 0;
3112
+ var copiedBytes = 0;
3113
+ /** O(1) per packet: called by `copyOwned`. No allocation. */
3114
+ function recordCopyOwned(bytes) {
3115
+ copiedPackets += 1;
3116
+ copiedBytes += bytes;
3117
+ }
3118
+ function readCopyOwnedTotals() {
3119
+ return {
3120
+ packets: copiedPackets,
3121
+ bytes: copiedBytes
3122
+ };
3123
+ }
3124
+ /** `RssAnon:` / `RssFile:` out of `/proc/self/status` text; null when absent. */
3125
+ function parseProcStatus(text) {
3126
+ const anon = /^RssAnon:\s+(\d+)\s*kB/m.exec(text);
3127
+ const file = /^RssFile:\s+(\d+)\s*kB/m.exec(text);
3128
+ if (anon === null || file === null) return null;
3129
+ return {
3130
+ rssAnonKb: Number(anon[1]),
3131
+ rssFileKb: Number(file[1])
3132
+ };
3133
+ }
3134
+ /**
3135
+ * Summarise `/proc/self/maps`. Cheaper than `smaps_rollup`'s kernel page walk:
3136
+ * the file is one line per mapping and needs no per-page accounting. The
3137
+ * `[heap]` figure is the brk extent (virtual), which is the number glibc's
3138
+ * main arena grows when fragmented.
3139
+ *
3140
+ * Line shape: `start-end perms offset dev inode [path]` — an anonymous mapping
3141
+ * has no sixth field.
3142
+ */
3143
+ function parseProcMaps(text) {
3144
+ let heapBytes = 0;
3145
+ let anonMappings = 0;
3146
+ let anonBytes = 0;
3147
+ let v8PageMaps = 0;
3148
+ let from = 0;
3149
+ while (from < text.length) {
3150
+ let to = text.indexOf("\n", from);
3151
+ if (to === -1) to = text.length;
3152
+ const line = text.slice(from, to);
3153
+ from = to + 1;
3154
+ const dash = line.indexOf("-");
3155
+ const space = line.indexOf(" ");
3156
+ if (dash <= 0 || space <= dash) continue;
3157
+ const size = Number.parseInt(line.slice(dash + 1, space), 16) - Number.parseInt(line.slice(0, dash), 16);
3158
+ if (!Number.isFinite(size)) continue;
3159
+ const path = line.split(/\s+/, 6)[5];
3160
+ if (path === void 0 || path === "") {
3161
+ anonMappings += 1;
3162
+ anonBytes += size;
3163
+ if (size === 262144) v8PageMaps += 1;
3164
+ } else if (path === "[heap]") heapBytes += size;
3165
+ }
3166
+ return {
3167
+ heapBytes,
3168
+ anonMappings,
3169
+ anonBytes,
3170
+ v8PageMaps
3171
+ };
3172
+ }
3173
+ /** Read the main isolate. Probe tick only: `getHeapSpaceStatistics` walks the spaces. */
3174
+ function readV8Heap() {
3175
+ const h = (0, node_v8.getHeapStatistics)();
3176
+ return {
3177
+ totalHeapBytes: h.total_heap_size,
3178
+ physicalBytes: h.total_physical_size,
3179
+ mallocedBytes: h.malloced_memory,
3180
+ peakMallocedBytes: h.peak_malloced_memory,
3181
+ externalBytes: h.external_memory,
3182
+ spaces: (0, node_v8.getHeapSpaceStatistics)().map((x) => ({
3183
+ name: x.space_name,
3184
+ committedBytes: x.space_size
3185
+ }))
3186
+ };
3187
+ }
3188
+ var SPACE_ABBREVIATIONS = {
3189
+ read_only_space: "ro",
3190
+ old_space: "old",
3191
+ new_space: "new",
3192
+ large_object_space: "lo",
3193
+ new_large_object_space: "nlo",
3194
+ code_space: "code",
3195
+ code_large_object_space: "clo",
3196
+ map_space: "map",
3197
+ shared_space: "shared",
3198
+ shared_large_object_space: "slo",
3199
+ trusted_space: "trusted",
3200
+ trusted_large_object_space: "tlo"
3201
+ };
3202
+ /** `old:120,new:24,lo:8` — committed MB per space, empty spaces left out. */
3203
+ function formatHeapSpaces(spaces) {
3204
+ const parts = spaces.filter((x) => x.committedBytes > 0).map((x) => `${SPACE_ABBREVIATIONS[x.name] ?? x.name}:${Math.round(x.committedBytes / MB)}`);
3205
+ return parts.length === 0 ? "none" : parts.join(",");
3206
+ }
3207
+ /** Opened/closed consumers since the last reading; `drain()` resets. */
3208
+ var ConsumerChurn = class {
3209
+ opened = 0;
3210
+ closed = 0;
3211
+ openedByKind = /* @__PURE__ */ new Map();
3212
+ closedByKind = /* @__PURE__ */ new Map();
3213
+ recordOpen(kind) {
3214
+ this.opened += 1;
3215
+ this.openedByKind.set(kind, (this.openedByKind.get(kind) ?? 0) + 1);
3216
+ }
3217
+ recordClose(kind) {
3218
+ this.closed += 1;
3219
+ this.closedByKind.set(kind, (this.closedByKind.get(kind) ?? 0) + 1);
3220
+ }
3221
+ drain() {
3222
+ const reading = {
3223
+ opened: this.opened,
3224
+ closed: this.closed,
3225
+ openedByKind: Object.fromEntries(this.openedByKind),
3226
+ closedByKind: Object.fromEntries(this.closedByKind)
3227
+ };
3228
+ this.opened = 0;
3229
+ this.closed = 0;
3230
+ this.openedByKind = /* @__PURE__ */ new Map();
3231
+ this.closedByKind = /* @__PURE__ */ new Map();
3232
+ return reading;
3233
+ }
3234
+ };
3235
+ /** The one churn counter of this process (the broker runner). */
3236
+ var consumerChurn = new ConsumerChurn();
3237
+ /**
3238
+ * Sliding-window limiter for the attach/detach lines. `admit(now)` answers
3239
+ * whether this line may be written; the number of lines swallowed since the
3240
+ * last admitted one is returned with it so the next line can say so (a branch
3241
+ * that drops work names it).
3242
+ */
3243
+ function createLineLimiter(maxPerWindow, windowMs) {
3244
+ let windowStart = 0;
3245
+ let inWindow = 0;
3246
+ let suppressed = 0;
3247
+ return (now) => {
3248
+ if (now - windowStart >= windowMs) {
3249
+ windowStart = now;
3250
+ inWindow = 0;
3251
+ }
3252
+ if (inWindow >= maxPerWindow) {
3253
+ suppressed += 1;
3254
+ return {
3255
+ admit: false,
3256
+ suppressedBefore: 0
3257
+ };
3258
+ }
3259
+ inWindow += 1;
3260
+ const suppressedBefore = suppressed;
3261
+ suppressed = 0;
3262
+ return {
3263
+ admit: true,
3264
+ suppressedBefore
3265
+ };
3266
+ };
3267
+ }
3268
+ var consumerLineLimiter = createLineLimiter(40, 6e4);
3269
+ /**
3270
+ * One consumer attached to / detached from a broker: counted always (the
3271
+ * `[broker-mem]` line reports opened/closed since the last line), written at
3272
+ * info under a rate limit so a step in RSS can be matched to an event. The
3273
+ * line after a suppressed burst says how many were swallowed.
3274
+ */
3275
+ function reportConsumerEvent(logger, event, kind, deviceId, total, now = Date.now()) {
3276
+ if (event === "attach") consumerChurn.recordOpen(kind);
3277
+ else consumerChurn.recordClose(kind);
3278
+ if (logger === void 0 || logger === null) return;
3279
+ const verdict = consumerLineLimiter(now);
3280
+ if (!verdict.admit) return;
3281
+ const suppressed = verdict.suppressedBefore > 0 ? ` suppressedBefore=${verdict.suppressedBefore}` : "";
3282
+ logger.info(`consumer ${event} kind=${kind} total=${total}${suppressed}`, { ...deviceId !== void 0 ? { tags: { deviceId } } : {} });
3283
+ }
3284
+ function ringBytesOf(s) {
3285
+ return s.preBufferBytes + s.clipBytes + s.rtpRingBytes + s.keyframeCacheBytes;
3286
+ }
3287
+ function mb(bytes) {
3288
+ return (bytes / MB).toFixed(1);
3289
+ }
3290
+ function kindList(counts) {
3291
+ const keys = Object.keys(counts).sort();
3292
+ return keys.length === 0 ? "none" : keys.map((k) => `${k}:${counts[k]}`).join(",");
3293
+ }
3294
+ /** One greppable `key=value` line; no per-device breakdown beyond the top N. */
3295
+ function formatBrokerMem(input) {
3296
+ const { samples, proc, churn, copied } = input;
3297
+ const consumers = {};
3298
+ let rtsp = 0;
3299
+ let audio = 0;
3300
+ let pipe = 0;
3301
+ let ingesting = 0;
3302
+ let planes = 0;
3303
+ let pre = 0;
3304
+ let clip = 0;
3305
+ let rtp = 0;
3306
+ let kf = 0;
3307
+ let qSum = 0;
3308
+ let qMax = 0;
3309
+ for (const s of samples) {
3310
+ if (s.ingesting) ingesting += 1;
3311
+ if (s.annexBPlaneActive) planes += 1;
3312
+ rtsp += s.rtspSessions;
3313
+ audio += s.audioSubscribers;
3314
+ pipe += s.pipeClients;
3315
+ pre += s.preBufferBytes;
3316
+ clip += s.clipBytes;
3317
+ rtp += s.rtpRingBytes;
3318
+ kf += s.keyframeCacheBytes;
3319
+ qSum += s.writeQueueSumBytes;
3320
+ if (s.writeQueueMaxBytes > qMax) qMax = s.writeQueueMaxBytes;
3321
+ for (const [kind, n] of Object.entries(s.encodedByKind)) consumers[kind] = (consumers[kind] ?? 0) + n;
3322
+ }
3323
+ const top = [...samples].filter((s) => s.deviceId !== void 0).sort((a, b) => ringBytesOf(b) - ringBytesOf(a)).slice(0, 5).map((s) => `${s.deviceId}:${mb(ringBytesOf(s))}`).join(",");
3324
+ const parts = ["[broker-mem]"];
3325
+ if (proc.status !== null) parts.push(`rssAnonMb=${(proc.status.rssAnonKb / 1024).toFixed(0)}`, `rssFileMb=${(proc.status.rssFileKb / 1024).toFixed(0)}`);
3326
+ if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`, `v8PageMaps=${proc.maps.v8PageMaps}`);
3327
+ const v8 = input.v8;
3328
+ if (v8 !== void 0 && v8 !== null) parts.push(`v8TotalHeapMb=${mb(v8.totalHeapBytes)}`, `v8PhysicalMb=${mb(v8.physicalBytes)}`, `v8MallocedMb=${mb(v8.mallocedBytes)}`, `v8PeakMallocedMb=${mb(v8.peakMallocedBytes)}`, `v8ExternalMb=${mb(v8.externalBytes)}`, `spaces=${formatHeapSpaces(v8.spaces)}`);
3329
+ const w = input.worker;
3330
+ 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
+ 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}`);
3332
+ return parts.join(" ");
3333
+ }
3334
+ /** Count entries named `name` in `process.getActiveResourcesInfo()`. */
3335
+ function countActiveResources(resources, name) {
3336
+ let n = 0;
3337
+ for (const r of resources) if (r === name) n += 1;
3338
+ return n;
3339
+ }
3340
+ //#endregion
3088
3341
  //#region src/stream-broker/stream-broker/owned-buffer.ts
3089
3342
  /**
3090
3343
  * Buffers that enter a LONG-LIVED ring own their backing store.
@@ -3144,6 +3397,7 @@ function detachFromPool(buf) {
3144
3397
  function copyOwned(src, start, end) {
3145
3398
  const owned = Buffer.allocUnsafeSlow(end - start);
3146
3399
  src.copy(owned, 0, start, end);
3400
+ recordCopyOwned(end - start);
3147
3401
  return owned;
3148
3402
  }
3149
3403
  //#endregion
@@ -3756,6 +4010,26 @@ var RtspRestreamer = class {
3756
4010
  for (const session of this.sessions.values()) if (session.isPrestreamIntent()) n += 1;
3757
4011
  return n;
3758
4012
  }
4013
+ /**
4014
+ * What this restreamer holds, for the `[broker-mem]` probe: live sessions,
4015
+ * their socket write queues (sum and max) and the keyframe caches. One pass
4016
+ * over the sessions, no allocation beyond the result.
4017
+ */
4018
+ getMemProbe() {
4019
+ let sum = 0;
4020
+ let max = 0;
4021
+ for (const s of this.sessions.values()) {
4022
+ const q = s.getWriteQueueBytes();
4023
+ sum += q;
4024
+ if (q > max) max = q;
4025
+ }
4026
+ return {
4027
+ sessions: this.sessions.size,
4028
+ writeQueueSumBytes: sum,
4029
+ writeQueueMaxBytes: max,
4030
+ keyframeCacheBytes: this.lastKeyframeNalBytes + this.lastKeyframeRtpBytes
4031
+ };
4032
+ }
3759
4033
  /** Per-session diagnostic snapshots consumed by `StreamBroker.listClients`. */
3760
4034
  listSessionInfos() {
3761
4035
  return [...this.sessions.values()].map((s) => s.getInfo());
@@ -3767,6 +4041,7 @@ var RtspRestreamer = class {
3767
4041
  streamName: this.streamName,
3768
4042
  total: this.sessions.size
3769
4043
  } });
4044
+ reportConsumerEvent(this.logger, "attach", "rtsp", this.deviceId, this.sessions.size);
3770
4045
  this._onSessionCountChanged?.();
3771
4046
  this.reconcileSentryAudio();
3772
4047
  if (this._rtpPassthroughMode) setTimeout(() => this.servePendingSessionsRtp(), 0);
@@ -4231,6 +4506,7 @@ var RtspRestreamer = class {
4231
4506
  streamName: this.streamName,
4232
4507
  total: this.sessions.size
4233
4508
  } });
4509
+ reportConsumerEvent(this.logger, "detach", "rtsp", this.deviceId, this.sessions.size);
4234
4510
  this._onSessionCountChanged?.();
4235
4511
  this.reconcileSentryAudio();
4236
4512
  }
@@ -4500,6 +4776,10 @@ var RtspSession = class {
4500
4776
  this.onClose?.();
4501
4777
  });
4502
4778
  }
4779
+ /** Bytes the socket has accepted but not yet flushed (corked bytes included). */
4780
+ getWriteQueueBytes() {
4781
+ return this.socket.writableLength;
4782
+ }
4503
4783
  getSessionId() {
4504
4784
  return this.sessionId;
4505
4785
  }
@@ -4867,6 +5147,17 @@ var RtspSession = class {
4867
5147
  //#endregion
4868
5148
  //#region src/stream-broker/rtsp/rtsp-listen-server.ts
4869
5149
  /**
5150
+ * Attribution of the synthetic consumer a {@link BrokerWarmUp} subscribes:
5151
+ * the RTSP listen server holding a cold broker open for a DESCRIBE. Named
5152
+ * after that real consumer — it used to subscribe with no attribution and
5153
+ * surfaced as `unknown` (45 attach/detach pairs per 5 min on the hub).
5154
+ */
5155
+ var RTSP_DESCRIBE_WARMUP_ATTRIBUTION = {
5156
+ kind: "rtsp-listen",
5157
+ label: "describe-warmup",
5158
+ media: "both"
5159
+ };
5160
+ /**
4870
5161
  * Single TCP server for all RTSP restream connections.
4871
5162
  * Routes clients to the correct RtspRestreamer by a random token path
4872
5163
  * (not the predictable brokerId) for security.
@@ -6453,6 +6744,10 @@ var EgressTranscodeManager = class {
6453
6744
  /** In-flight starts, keyed by sharing key — concurrent acquires share one. */
6454
6745
  pending = /* @__PURE__ */ new Map();
6455
6746
  releaseGraceMs;
6747
+ /** Live egress transcodes (ffmpeg children), for the `[broker-mem]` probe. */
6748
+ activeCount() {
6749
+ return this.entries.size;
6750
+ }
6456
6751
  constructor(opts) {
6457
6752
  this.opts = opts;
6458
6753
  this.releaseGraceMs = opts.releaseGraceMs ?? DEFAULT_RELEASE_GRACE_MS;
@@ -12816,6 +13111,8 @@ var StreamBroker = class StreamBroker {
12816
13111
  * timeout was chosen against.
12817
13112
  */
12818
13113
  static RTP_ACTIVITY_SWEEP_MS = 1e3;
13114
+ /** A source counts as ingesting when its last video packet is younger than this. */
13115
+ static INGESTING_WITHIN_MS = 1e4;
12819
13116
  manualStop = false;
12820
13117
  stopping = false;
12821
13118
  pipeServer;
@@ -13090,6 +13387,33 @@ var StreamBroker = class StreamBroker {
13090
13387
  return this.lastPacketAt;
13091
13388
  }
13092
13389
  /**
13390
+ * What this broker holds, for the `[broker-mem]` probe (`broker-mem-probe.ts`).
13391
+ * Ring sizes are the rings' own counters (backing-store bytes), so this is
13392
+ * O(sessions + subscribers) and allocates only the result.
13393
+ */
13394
+ getMemSample(now) {
13395
+ const encodedByKind = {};
13396
+ for (const entry of this.encodedCallbacks.values()) encodedByKind[entry.attribution.kind] = (encodedByKind[entry.attribution.kind] ?? 0) + 1;
13397
+ for (const entry of this.rtpVideoCallbacks.values()) encodedByKind[entry.attribution.kind] = (encodedByKind[entry.attribution.kind] ?? 0) + 1;
13398
+ const rtsp = this.rtspRestreamer.getMemProbe();
13399
+ return {
13400
+ deviceId: this.numericDeviceId,
13401
+ brokerId: this.deviceId,
13402
+ ingesting: this.lastPacketAt > 0 && now - this.lastPacketAt < StreamBroker.INGESTING_WITHIN_MS,
13403
+ preBufferBytes: this.preBuffer.getBufferedBytes(),
13404
+ clipBytes: this.clipBuffer?.getBufferedBytes() ?? 0,
13405
+ rtpRingBytes: this.rtpRingBytes,
13406
+ keyframeCacheBytes: rtsp.keyframeCacheBytes,
13407
+ rtspSessions: rtsp.sessions,
13408
+ writeQueueSumBytes: rtsp.writeQueueSumBytes,
13409
+ writeQueueMaxBytes: rtsp.writeQueueMaxBytes,
13410
+ encodedByKind,
13411
+ audioSubscribers: this.audioChunkPlane.subscriberCount,
13412
+ pipeClients: this.pipeServer.getClientCount(),
13413
+ annexBPlaneActive: this.annexBPlaneActive
13414
+ };
13415
+ }
13416
+ /**
13093
13417
  * Whether a LAZY publisher is being built right now.
13094
13418
  *
13095
13419
  * A consumer deciding whether it is worth waiting for this source needs to
@@ -14052,6 +14376,7 @@ var StreamBroker = class StreamBroker {
14052
14376
  packetsDelivered: 0
14053
14377
  };
14054
14378
  this.encodedCallbacks.set(callback, entry);
14379
+ reportConsumerEvent(this.logger, "attach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14055
14380
  if (this.streamingDebug) this.logger?.info("encoded subscriber added", { meta: {
14056
14381
  total: this.encodedCallbacks.size,
14057
14382
  subscriberId: entry.id,
@@ -14062,6 +14387,7 @@ var StreamBroker = class StreamBroker {
14062
14387
  this.checkDemand();
14063
14388
  return () => {
14064
14389
  this.encodedCallbacks.delete(callback);
14390
+ reportConsumerEvent(this.logger, "detach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14065
14391
  if (this.streamingDebug) this.logger?.info("encoded subscriber removed", { meta: {
14066
14392
  total: this.encodedCallbacks.size,
14067
14393
  subscriberId: entry.id
@@ -14156,6 +14482,7 @@ var StreamBroker = class StreamBroker {
14156
14482
  packetsDelivered: 0
14157
14483
  };
14158
14484
  this.rtpVideoCallbacks.set(callback, entry);
14485
+ reportConsumerEvent(this.logger, "attach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14159
14486
  if (this.streamingDebug || CH_RTP.on) this.logger?.info("rtp video subscriber added", { meta: {
14160
14487
  total: this.rtpVideoCallbacks.size,
14161
14488
  subscriberId: entry.id,
@@ -14165,6 +14492,7 @@ var StreamBroker = class StreamBroker {
14165
14492
  this.checkDemand();
14166
14493
  return () => {
14167
14494
  this.rtpVideoCallbacks.delete(callback);
14495
+ reportConsumerEvent(this.logger, "detach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14168
14496
  if (this.streamingDebug || CH_RTP.on) this.logger?.info("rtp video subscriber removed", { meta: {
14169
14497
  total: this.rtpVideoCallbacks.size,
14170
14498
  subscriberId: entry.id
@@ -14565,6 +14893,7 @@ var StreamBroker = class StreamBroker {
14565
14893
  tag,
14566
14894
  accept: options?.accept
14567
14895
  });
14896
+ reportConsumerEvent(this.logger, "attach", "audio-chunks", this.numericDeviceId, this.audioChunkPlane.subscriberCount);
14568
14897
  this.checkDemand();
14569
14898
  return result;
14570
14899
  }
@@ -14575,7 +14904,10 @@ var StreamBroker = class StreamBroker {
14575
14904
  /** Release an audio-chunk subscription. Returns `true` when it existed. */
14576
14905
  unsubscribeAudioChunks(subscriptionId) {
14577
14906
  const released = this.audioChunkPlane.unsubscribe(subscriptionId);
14578
- if (released) this.checkDemand();
14907
+ if (released) {
14908
+ reportConsumerEvent(this.logger, "detach", "audio-chunks", this.numericDeviceId, this.audioChunkPlane.subscriberCount);
14909
+ this.checkDemand();
14910
+ }
14579
14911
  return released;
14580
14912
  }
14581
14913
  getStats() {
@@ -19402,6 +19734,10 @@ var StreamBrokerManager = class StreamBrokerManager {
19402
19734
  return { killed: broker.killClient(input.channel, input.handle) };
19403
19735
  }
19404
19736
  /** Raw broker instances — internal use only. */
19737
+ /** Live egress transcodes (ffmpeg children) — the `[broker-mem]` probe. */
19738
+ getEgressTranscodeCount() {
19739
+ return this.egressTranscodeManager?.activeCount() ?? 0;
19740
+ }
19405
19741
  getBrokerInstances() {
19406
19742
  return [...this.brokers.values()];
19407
19743
  }
@@ -19668,7 +20004,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19668
20004
  const sourceBroker = this.brokers.get(sourceBrokerId);
19669
20005
  if (!sourceBroker) continue;
19670
20006
  const existingToken = this.persistedTokens.get(profileBrokerId);
19671
- const warmUp = () => sourceBroker.onEncodedData(() => {});
20007
+ const warmUp = () => sourceBroker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
19672
20008
  const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19673
20009
  this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19674
20010
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
@@ -21327,7 +21663,7 @@ var StreamBrokerManager = class StreamBrokerManager {
21327
21663
  this.brokers.set(brokerId, broker);
21328
21664
  this.applyClipRetention(deviceId);
21329
21665
  const existingToken = this.persistedTokens.get(brokerId);
21330
- const warmUp = () => broker.onEncodedData(() => {});
21666
+ const warmUp = () => broker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
21331
21667
  const openPrestreamWindow = () => broker.openPrestreamWindow();
21332
21668
  this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
21333
21669
  if (!existingToken) this.rtspProvider.persistTokens();
@@ -33656,14 +33992,11 @@ function parseWorkerMessage(message) {
33656
33992
  function defaultWorkerFilename() {
33657
33993
  return (0, node_url.fileURLToPath)(new URL("./demux-worker-child.js", require("url").pathToFileURL(__filename).href));
33658
33994
  }
33659
- /**
33660
- * JS parse that posts each fragment to one process-wide Worker. Concurrent
33661
- * callers are matched by request id; the worker's own event loop serializes
33662
- * mp4box. Live RTP on the parent thread is not waiting on box parsing.
33663
- */
33664
- function createOffThreadJsParse(deps) {
33995
+ /** {@link createOffThreadJsParse} plus the heap accessor the memory probe reads. */
33996
+ function createOffThreadDemuxClient(deps) {
33665
33997
  const Ctor = deps.WorkerCtor;
33666
33998
  let worker = null;
33999
+ let spawnedAt = 0;
33667
34000
  let nextId = 1;
33668
34001
  const pending = /* @__PURE__ */ new Map();
33669
34002
  const failAll = () => {
@@ -33696,12 +34029,13 @@ function createOffThreadJsParse(deps) {
33696
34029
  const spawned = new Ctor(deps.workerFilename);
33697
34030
  attach(spawned);
33698
34031
  worker = spawned;
34032
+ spawnedAt = Date.now();
33699
34033
  return spawned;
33700
34034
  } catch {
33701
34035
  return null;
33702
34036
  }
33703
34037
  };
33704
- return async (bytes) => {
34038
+ const parse = async (bytes) => {
33705
34039
  if (!deps.existsSync(deps.workerFilename)) return deps.parseInProcess(bytes);
33706
34040
  const w = ensureWorker();
33707
34041
  if (!w) return deps.parseInProcess(bytes);
@@ -33717,9 +34051,36 @@ function createOffThreadJsParse(deps) {
33717
34051
  w.postMessage(request, [copy.buffer]);
33718
34052
  });
33719
34053
  };
34054
+ const readHeaps = async (now = Date.now()) => {
34055
+ const live = worker;
34056
+ const empty = {
34057
+ workers: 0,
34058
+ heapBytes: 0,
34059
+ physicalBytes: 0,
34060
+ usedBytes: 0,
34061
+ maxAgeMs: 0,
34062
+ idleTerminates: false
34063
+ };
34064
+ if (live === null) return empty;
34065
+ const stats = await live.getHeapStatistics?.().catch(() => null);
34066
+ return {
34067
+ ...empty,
34068
+ workers: 1,
34069
+ maxAgeMs: Math.max(0, now - spawnedAt),
34070
+ ...stats ? {
34071
+ heapBytes: stats.total_heap_size,
34072
+ physicalBytes: stats.total_physical_size,
34073
+ usedBytes: stats.used_heap_size
34074
+ } : {}
34075
+ };
34076
+ };
34077
+ return {
34078
+ parse,
34079
+ readHeaps
34080
+ };
33720
34081
  }
33721
- function defaultOffThreadJsParse() {
33722
- return createOffThreadJsParse({
34082
+ function defaultOffThreadClient() {
34083
+ return createOffThreadDemuxClient({
33723
34084
  workerFilename: defaultWorkerFilename(),
33724
34085
  existsSync: node_fs.existsSync,
33725
34086
  parseInProcess: require_segment_demux_js.demuxSegmentFullJs,
@@ -33729,9 +34090,20 @@ function defaultOffThreadJsParse() {
33729
34090
  var shared;
33730
34091
  /** Process-wide JS parse: one Worker, shared by every WebRTC session. */
33731
34092
  var offThreadJsParse = (bytes) => {
33732
- shared ??= defaultOffThreadJsParse();
33733
- return shared(bytes);
34093
+ shared ??= defaultOffThreadClient();
34094
+ return shared.parse(bytes);
33734
34095
  };
34096
+ /** Heap of the shared demux worker, for the `[broker-mem]` probe (tick only). */
34097
+ async function readDemuxWorkerHeaps(now) {
34098
+ return shared ? shared.readHeaps(now) : {
34099
+ workers: 0,
34100
+ heapBytes: 0,
34101
+ physicalBytes: 0,
34102
+ usedBytes: 0,
34103
+ maxAgeMs: 0,
34104
+ idleTerminates: false
34105
+ };
34106
+ }
33735
34107
  /**
33736
34108
  * Drop-in for the session `demuxVideo` hook: JS parse off the live loop,
33737
34109
  * ffmpeg subprocess fallback unchanged.
@@ -35274,6 +35646,7 @@ var BrokerWebrtcServer = class {
35274
35646
  }
35275
35647
  };
35276
35648
  this.sessions.set(sessionId, entry);
35649
+ reportConsumerEvent(this.logger, "attach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
35277
35650
  session.onClosed = () => this.cleanupSession(sessionId);
35278
35651
  const locateRecorded = this.locateRecordedSegment;
35279
35652
  const readRecorded = this.readRecordedSegmentBytes;
@@ -36491,6 +36864,7 @@ var BrokerWebrtcServer = class {
36491
36864
  const entry = this.sessions.get(sessionId);
36492
36865
  if (!entry) return;
36493
36866
  this.sessions.delete(sessionId);
36867
+ reportConsumerEvent(this.logger, "detach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
36494
36868
  entry.pendingSwitch?.cancel();
36495
36869
  entry.pendingSwitch = void 0;
36496
36870
  let brokerStillServed = false;
@@ -37212,6 +37586,47 @@ function dialAnswerFromError(err) {
37212
37586
  };
37213
37587
  }
37214
37588
  //#endregion
37589
+ //#region src/stream-broker/broker-mem-report.ts
37590
+ /** Linux only: elsewhere (or on a read error) the native view is simply absent. */
37591
+ async function readProc() {
37592
+ if (process.platform !== "linux") return {
37593
+ status: null,
37594
+ maps: null
37595
+ };
37596
+ const [status, maps] = await Promise.all([(0, node_fs_promises.readFile)("/proc/self/status", "utf8").then(parseProcStatus, () => null), (0, node_fs_promises.readFile)("/proc/self/maps", "utf8").then(parseProcMaps, () => null)]);
37597
+ return {
37598
+ status,
37599
+ maps
37600
+ };
37601
+ }
37602
+ async function emitOnce(deps) {
37603
+ const now = Date.now();
37604
+ const [proc, worker] = await Promise.all([readProc(), readDemuxWorkerHeaps(now)]);
37605
+ const resources = process.getActiveResourcesInfo();
37606
+ const line = formatBrokerMem({
37607
+ samples: deps.manager.getBrokerInstances().map((b) => b.getMemSample(now)),
37608
+ webrtcSessions: deps.webrtcSessions(),
37609
+ egressTranscodes: deps.manager.getEgressTranscodeCount(),
37610
+ childProcesses: countActiveResources(resources, "ProcessWrap"),
37611
+ proc,
37612
+ churn: consumerChurn.drain(),
37613
+ copied: readCopyOwnedTotals(),
37614
+ v8: readV8Heap(),
37615
+ worker
37616
+ });
37617
+ deps.logger.info(line);
37618
+ }
37619
+ /** Start the five-minute `[broker-mem]` heartbeat; returns its stop function. */
37620
+ function startBrokerMemReport(deps) {
37621
+ const timer = setInterval(() => {
37622
+ emitOnce(deps).catch((err) => {
37623
+ deps.logger.warn("broker-mem probe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
37624
+ });
37625
+ }, BROKER_MEM_INTERVAL_MS);
37626
+ timer.unref();
37627
+ return () => clearInterval(timer);
37628
+ }
37629
+ //#endregion
37215
37630
  //#region src/stream-broker/event-loop-stall-monitor.ts
37216
37631
  var DEFAULT_INTERVAL_MS = 100;
37217
37632
  var DEFAULT_THRESHOLD_MS = 250;
@@ -37386,6 +37801,8 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
37386
37801
  * leaks those sockets and wedges the next runner's werift ICE agent
37387
37802
  * (DTLS never connects until a full hub restart). */
37388
37803
  webrtcServer = null;
37804
+ /** Stops the five-minute `[broker-mem]` attribution heartbeat. */
37805
+ stopBrokerMemReport = null;
37389
37806
  /** Names this process's event-loop stalls — see onInitialize. */
37390
37807
  loopStallMonitor = null;
37391
37808
  /** The `log-channels` provider for this process. Stopped on shutdown so a
@@ -37743,6 +38160,11 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
37743
38160
  this.webrtcServer = webrtcServer;
37744
38161
  const webrtcSessionProvider = new WebrtcSessionProvider(webrtcServer, this.brokerManager);
37745
38162
  this.metricsSnapshotTimer = setInterval(() => this.emitBrokerMetricsSnapshot(), BROKER_METRICS_SNAPSHOT_INTERVAL_MS);
38163
+ this.stopBrokerMemReport = startBrokerMemReport({
38164
+ logger: this.ctx.logger,
38165
+ manager: this.brokerManager,
38166
+ webrtcSessions: () => webrtcServer.getSessionCount()
38167
+ });
37746
38168
  const widgetsProvider = { listWidgets: async () => [{
37747
38169
  tab: "device-tab",
37748
38170
  label: "Stream Brokers",
@@ -37847,6 +38269,8 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
37847
38269
  async onShutdown() {
37848
38270
  this.logChannels?.stop();
37849
38271
  this.logChannels = null;
38272
+ this.stopBrokerMemReport?.();
38273
+ this.stopBrokerMemReport = null;
37850
38274
  this.loopStallMonitor?.stop();
37851
38275
  this.loopStallMonitor = null;
37852
38276
  this.flushProbeSnapshots(Date.now(), true);