@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
@@ -1,10 +1,10 @@
1
1
  import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
2
- import { $n as discriminatedUnion, An as BaseAddon, D as G711_SCALE_CORRECTION_DB, Dn as DeviceType, Dt as gifDeriveSpeed, En as DeviceFeature, Gn as maskUrlCredentials, Hn as isEvent, I as RATE_CONTROL_RELAXED, In as createEvent, J as addonWidgetsSourceCapability, Kn as nodePin, L as RATE_CONTROL_TIGHT, Mn as CamProfileSchema, Nt as loadContributionCapability, On as errMsg, Ot as gifFromMp4Args, Pt as logChannelsCapability, Qn as boolean, Rt as memoizePinnedFfmpeg, Sn as buildFfmpegArgs, Tn as isSoftwareDecode, Tt as ffmpegRate, Un as makeProfileBrokerId, W as RingBuffer, Wn as makeSourceBrokerId, Xn as _enum, Xt as rankStreams, Zn as array, _t as egressTranscodeSharingKey, ar as record, at as clipExportFailureOf, c as CLIP_STILL_REASON_CODE_HEADER, cr as EventCategory, ct as createHwAccelCache, et as batteryCapability, ft as deriveBatteryPresence, jn as CAM_PROFILE_ORDER, kn as BOOT_RECOVERY_BACKOFF_MS, lt as createLogChannelsProvider, mn as streamBrokerCapability, nr as number, o as BatteryStatusSchema, or as string, qn as parseProfileBrokerId, rn as resolveEgressDecodeHwAccel, rr as object, rt as cameraStreamsCapability, s as CLIP_BROKER_PACED_RATES, sr as union, tr as literal, ut as declareLogChannel, vn as webrtcSessionCapability, vt as egressTransportFromRequest, w as EncodeProfileSchema, wn as invocationFromEncodeProfile, yn as AUDIO_PRESETS, zn as expandAudioChunkToF32le } from "../dist-BshC0NZR.mjs";
3
- import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-Dqmrb990.mjs";
2
+ import { $n as discriminatedUnion, An as BaseAddon, D as G711_SCALE_CORRECTION_DB, Dn as DeviceType, Dt as gifDeriveSpeed, En as DeviceFeature, Gn as maskUrlCredentials, Hn as isEvent, I as RATE_CONTROL_RELAXED, In as createEvent, J as addonWidgetsSourceCapability, Kn as nodePin, L as RATE_CONTROL_TIGHT, Mn as CamProfileSchema, Nt as loadContributionCapability, On as errMsg, Ot as gifFromMp4Args, Pt as logChannelsCapability, Qn as boolean, Rt as memoizePinnedFfmpeg, Sn as buildFfmpegArgs, Tn as isSoftwareDecode, Tt as ffmpegRate, Un as makeProfileBrokerId, W as RingBuffer, Wn as makeSourceBrokerId, Xn as _enum, Xt as rankStreams, Zn as array, _t as egressTranscodeSharingKey, ar as record, at as clipExportFailureOf, c as CLIP_STILL_REASON_CODE_HEADER, cr as EventCategory, ct as createHwAccelCache, et as batteryCapability, ft as deriveBatteryPresence, jn as CAM_PROFILE_ORDER, kn as BOOT_RECOVERY_BACKOFF_MS, lt as createLogChannelsProvider, mn as streamBrokerCapability, nr as number, o as BatteryStatusSchema, or as string, qn as parseProfileBrokerId, rn as resolveEgressDecodeHwAccel, rr as object, rt as cameraStreamsCapability, s as CLIP_BROKER_PACED_RATES, sr as union, tr as literal, ut as declareLogChannel, vn as webrtcSessionCapability, vt as egressTransportFromRequest, w as EncodeProfileSchema, wn as invocationFromEncodeProfile, yn as AUDIO_PRESETS, zn as expandAudioChunkToF32le } from "../dist-ClKIJKn0.mjs";
3
+ import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-DlpWXqvO.mjs";
4
4
  import { a as parseRestreamPath, d as PREBUFFER_MAX_MS, f as capPreRollRequestMs, i as isLiveEdgeOnlyDial, m as resolvePrebuffer, n as NO_DECLARATION, p as prebufferRetentionMs, s as sdpVariantFor, u as withSentryIntent } from "../restream-intent-DyGmLAU9.mjs";
5
5
  import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
6
6
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
7
- import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-COoPqx93.mjs";
7
+ import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-Du_eAog1.mjs";
8
8
  import { createRequire } from "node:module";
9
9
  import * as os$1 from "node:os";
10
10
  import { networkInterfaces, tmpdir } from "node:os";
@@ -18,6 +18,7 @@ import { spawn } from "node:child_process";
18
18
  import { readFile } from "node:fs/promises";
19
19
  import { fileURLToPath, pathToFileURL } from "node:url";
20
20
  import { renderCourtesyJpeg, renderCourtesyOverlayJpeg } from "@camstack/system/addon-utils";
21
+ import { getHeapSpaceStatistics, getHeapStatistics } from "node:v8";
21
22
  import * as net$1 from "node:net";
22
23
  import net from "node:net";
23
24
  import { createSocket } from "node:dgram";
@@ -3081,6 +3082,258 @@ var RtpPacketizer = class {
3081
3082
  }
3082
3083
  };
3083
3084
  //#endregion
3085
+ //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3086
+ /**
3087
+ * broker-mem-probe — what owns the broker runner's NATIVE memory.
3088
+ *
3089
+ * The runner's `[mem]` heartbeat (`kernel/heap-watch.ts`) says how big the
3090
+ * process is; it cannot say who holds it. On the hub, `stream-broker` grew
3091
+ * 250 -> 1015 MB in steps while `heapUsed` stayed at ~90 MB and `arrayBuffers`
3092
+ * at ~150 MB, so ~600 MB sat outside everything the heartbeat reports. This
3093
+ * module is the attribution half: one `[broker-mem]` line every five minutes
3094
+ * carrying the kernel's view of the process (RssAnon / RssFile, the `[heap]`
3095
+ * brk mapping, the count of anonymous mappings) NEXT TO the broker's own
3096
+ * structures (rings, write queues, consumers by kind, egress children), so a
3097
+ * step in RSS can be laid against what changed.
3098
+ *
3099
+ * It is a probe, not a fix: no caps, no allocator tuning, no snapshot. Cost is
3100
+ * O(brokers + sessions) per line and O(1) per packet (two integer adds in
3101
+ * `copyOwned`). Everything here is a pure function over plain data so the
3102
+ * aggregation and the `/proc` parsers are testable on fixture text.
3103
+ */
3104
+ var MB = 1024 * 1024;
3105
+ /** Cadence of the `[broker-mem]` line — aligned with the runner's `[mem]`. */
3106
+ var BROKER_MEM_INTERVAL_MS = 5 * 6e4;
3107
+ var copiedPackets = 0;
3108
+ var copiedBytes = 0;
3109
+ /** O(1) per packet: called by `copyOwned`. No allocation. */
3110
+ function recordCopyOwned(bytes) {
3111
+ copiedPackets += 1;
3112
+ copiedBytes += bytes;
3113
+ }
3114
+ function readCopyOwnedTotals() {
3115
+ return {
3116
+ packets: copiedPackets,
3117
+ bytes: copiedBytes
3118
+ };
3119
+ }
3120
+ /** `RssAnon:` / `RssFile:` out of `/proc/self/status` text; null when absent. */
3121
+ function parseProcStatus(text) {
3122
+ const anon = /^RssAnon:\s+(\d+)\s*kB/m.exec(text);
3123
+ const file = /^RssFile:\s+(\d+)\s*kB/m.exec(text);
3124
+ if (anon === null || file === null) return null;
3125
+ return {
3126
+ rssAnonKb: Number(anon[1]),
3127
+ rssFileKb: Number(file[1])
3128
+ };
3129
+ }
3130
+ /**
3131
+ * Summarise `/proc/self/maps`. Cheaper than `smaps_rollup`'s kernel page walk:
3132
+ * the file is one line per mapping and needs no per-page accounting. The
3133
+ * `[heap]` figure is the brk extent (virtual), which is the number glibc's
3134
+ * main arena grows when fragmented.
3135
+ *
3136
+ * Line shape: `start-end perms offset dev inode [path]` — an anonymous mapping
3137
+ * has no sixth field.
3138
+ */
3139
+ function parseProcMaps(text) {
3140
+ let heapBytes = 0;
3141
+ let anonMappings = 0;
3142
+ let anonBytes = 0;
3143
+ let v8PageMaps = 0;
3144
+ let from = 0;
3145
+ while (from < text.length) {
3146
+ let to = text.indexOf("\n", from);
3147
+ if (to === -1) to = text.length;
3148
+ const line = text.slice(from, to);
3149
+ from = to + 1;
3150
+ const dash = line.indexOf("-");
3151
+ const space = line.indexOf(" ");
3152
+ if (dash <= 0 || space <= dash) continue;
3153
+ const size = Number.parseInt(line.slice(dash + 1, space), 16) - Number.parseInt(line.slice(0, dash), 16);
3154
+ if (!Number.isFinite(size)) continue;
3155
+ const path = line.split(/\s+/, 6)[5];
3156
+ if (path === void 0 || path === "") {
3157
+ anonMappings += 1;
3158
+ anonBytes += size;
3159
+ if (size === 262144) v8PageMaps += 1;
3160
+ } else if (path === "[heap]") heapBytes += size;
3161
+ }
3162
+ return {
3163
+ heapBytes,
3164
+ anonMappings,
3165
+ anonBytes,
3166
+ v8PageMaps
3167
+ };
3168
+ }
3169
+ /** Read the main isolate. Probe tick only: `getHeapSpaceStatistics` walks the spaces. */
3170
+ function readV8Heap() {
3171
+ const h = getHeapStatistics();
3172
+ return {
3173
+ totalHeapBytes: h.total_heap_size,
3174
+ physicalBytes: h.total_physical_size,
3175
+ mallocedBytes: h.malloced_memory,
3176
+ peakMallocedBytes: h.peak_malloced_memory,
3177
+ externalBytes: h.external_memory,
3178
+ spaces: getHeapSpaceStatistics().map((x) => ({
3179
+ name: x.space_name,
3180
+ committedBytes: x.space_size
3181
+ }))
3182
+ };
3183
+ }
3184
+ var SPACE_ABBREVIATIONS = {
3185
+ read_only_space: "ro",
3186
+ old_space: "old",
3187
+ new_space: "new",
3188
+ large_object_space: "lo",
3189
+ new_large_object_space: "nlo",
3190
+ code_space: "code",
3191
+ code_large_object_space: "clo",
3192
+ map_space: "map",
3193
+ shared_space: "shared",
3194
+ shared_large_object_space: "slo",
3195
+ trusted_space: "trusted",
3196
+ trusted_large_object_space: "tlo"
3197
+ };
3198
+ /** `old:120,new:24,lo:8` — committed MB per space, empty spaces left out. */
3199
+ function formatHeapSpaces(spaces) {
3200
+ const parts = spaces.filter((x) => x.committedBytes > 0).map((x) => `${SPACE_ABBREVIATIONS[x.name] ?? x.name}:${Math.round(x.committedBytes / MB)}`);
3201
+ return parts.length === 0 ? "none" : parts.join(",");
3202
+ }
3203
+ /** Opened/closed consumers since the last reading; `drain()` resets. */
3204
+ var ConsumerChurn = class {
3205
+ opened = 0;
3206
+ closed = 0;
3207
+ openedByKind = /* @__PURE__ */ new Map();
3208
+ closedByKind = /* @__PURE__ */ new Map();
3209
+ recordOpen(kind) {
3210
+ this.opened += 1;
3211
+ this.openedByKind.set(kind, (this.openedByKind.get(kind) ?? 0) + 1);
3212
+ }
3213
+ recordClose(kind) {
3214
+ this.closed += 1;
3215
+ this.closedByKind.set(kind, (this.closedByKind.get(kind) ?? 0) + 1);
3216
+ }
3217
+ drain() {
3218
+ const reading = {
3219
+ opened: this.opened,
3220
+ closed: this.closed,
3221
+ openedByKind: Object.fromEntries(this.openedByKind),
3222
+ closedByKind: Object.fromEntries(this.closedByKind)
3223
+ };
3224
+ this.opened = 0;
3225
+ this.closed = 0;
3226
+ this.openedByKind = /* @__PURE__ */ new Map();
3227
+ this.closedByKind = /* @__PURE__ */ new Map();
3228
+ return reading;
3229
+ }
3230
+ };
3231
+ /** The one churn counter of this process (the broker runner). */
3232
+ var consumerChurn = new ConsumerChurn();
3233
+ /**
3234
+ * Sliding-window limiter for the attach/detach lines. `admit(now)` answers
3235
+ * whether this line may be written; the number of lines swallowed since the
3236
+ * last admitted one is returned with it so the next line can say so (a branch
3237
+ * that drops work names it).
3238
+ */
3239
+ function createLineLimiter(maxPerWindow, windowMs) {
3240
+ let windowStart = 0;
3241
+ let inWindow = 0;
3242
+ let suppressed = 0;
3243
+ return (now) => {
3244
+ if (now - windowStart >= windowMs) {
3245
+ windowStart = now;
3246
+ inWindow = 0;
3247
+ }
3248
+ if (inWindow >= maxPerWindow) {
3249
+ suppressed += 1;
3250
+ return {
3251
+ admit: false,
3252
+ suppressedBefore: 0
3253
+ };
3254
+ }
3255
+ inWindow += 1;
3256
+ const suppressedBefore = suppressed;
3257
+ suppressed = 0;
3258
+ return {
3259
+ admit: true,
3260
+ suppressedBefore
3261
+ };
3262
+ };
3263
+ }
3264
+ var consumerLineLimiter = createLineLimiter(40, 6e4);
3265
+ /**
3266
+ * One consumer attached to / detached from a broker: counted always (the
3267
+ * `[broker-mem]` line reports opened/closed since the last line), written at
3268
+ * info under a rate limit so a step in RSS can be matched to an event. The
3269
+ * line after a suppressed burst says how many were swallowed.
3270
+ */
3271
+ function reportConsumerEvent(logger, event, kind, deviceId, total, now = Date.now()) {
3272
+ if (event === "attach") consumerChurn.recordOpen(kind);
3273
+ else consumerChurn.recordClose(kind);
3274
+ if (logger === void 0 || logger === null) return;
3275
+ const verdict = consumerLineLimiter(now);
3276
+ if (!verdict.admit) return;
3277
+ const suppressed = verdict.suppressedBefore > 0 ? ` suppressedBefore=${verdict.suppressedBefore}` : "";
3278
+ logger.info(`consumer ${event} kind=${kind} total=${total}${suppressed}`, { ...deviceId !== void 0 ? { tags: { deviceId } } : {} });
3279
+ }
3280
+ function ringBytesOf(s) {
3281
+ return s.preBufferBytes + s.clipBytes + s.rtpRingBytes + s.keyframeCacheBytes;
3282
+ }
3283
+ function mb(bytes) {
3284
+ return (bytes / MB).toFixed(1);
3285
+ }
3286
+ function kindList(counts) {
3287
+ const keys = Object.keys(counts).sort();
3288
+ return keys.length === 0 ? "none" : keys.map((k) => `${k}:${counts[k]}`).join(",");
3289
+ }
3290
+ /** One greppable `key=value` line; no per-device breakdown beyond the top N. */
3291
+ function formatBrokerMem(input) {
3292
+ const { samples, proc, churn, copied } = input;
3293
+ const consumers = {};
3294
+ let rtsp = 0;
3295
+ let audio = 0;
3296
+ let pipe = 0;
3297
+ let ingesting = 0;
3298
+ let planes = 0;
3299
+ let pre = 0;
3300
+ let clip = 0;
3301
+ let rtp = 0;
3302
+ let kf = 0;
3303
+ let qSum = 0;
3304
+ let qMax = 0;
3305
+ for (const s of samples) {
3306
+ if (s.ingesting) ingesting += 1;
3307
+ if (s.annexBPlaneActive) planes += 1;
3308
+ rtsp += s.rtspSessions;
3309
+ audio += s.audioSubscribers;
3310
+ pipe += s.pipeClients;
3311
+ pre += s.preBufferBytes;
3312
+ clip += s.clipBytes;
3313
+ rtp += s.rtpRingBytes;
3314
+ kf += s.keyframeCacheBytes;
3315
+ qSum += s.writeQueueSumBytes;
3316
+ if (s.writeQueueMaxBytes > qMax) qMax = s.writeQueueMaxBytes;
3317
+ for (const [kind, n] of Object.entries(s.encodedByKind)) consumers[kind] = (consumers[kind] ?? 0) + n;
3318
+ }
3319
+ 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(",");
3320
+ const parts = ["[broker-mem]"];
3321
+ if (proc.status !== null) parts.push(`rssAnonMb=${(proc.status.rssAnonKb / 1024).toFixed(0)}`, `rssFileMb=${(proc.status.rssFileKb / 1024).toFixed(0)}`);
3322
+ if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`, `v8PageMaps=${proc.maps.v8PageMaps}`);
3323
+ const v8 = input.v8;
3324
+ 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)}`);
3325
+ const w = input.worker;
3326
+ 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"}`);
3327
+ 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}`);
3328
+ return parts.join(" ");
3329
+ }
3330
+ /** Count entries named `name` in `process.getActiveResourcesInfo()`. */
3331
+ function countActiveResources(resources, name) {
3332
+ let n = 0;
3333
+ for (const r of resources) if (r === name) n += 1;
3334
+ return n;
3335
+ }
3336
+ //#endregion
3084
3337
  //#region src/stream-broker/stream-broker/owned-buffer.ts
3085
3338
  /**
3086
3339
  * Buffers that enter a LONG-LIVED ring own their backing store.
@@ -3140,6 +3393,7 @@ function detachFromPool(buf) {
3140
3393
  function copyOwned(src, start, end) {
3141
3394
  const owned = Buffer.allocUnsafeSlow(end - start);
3142
3395
  src.copy(owned, 0, start, end);
3396
+ recordCopyOwned(end - start);
3143
3397
  return owned;
3144
3398
  }
3145
3399
  //#endregion
@@ -3752,6 +4006,26 @@ var RtspRestreamer = class {
3752
4006
  for (const session of this.sessions.values()) if (session.isPrestreamIntent()) n += 1;
3753
4007
  return n;
3754
4008
  }
4009
+ /**
4010
+ * What this restreamer holds, for the `[broker-mem]` probe: live sessions,
4011
+ * their socket write queues (sum and max) and the keyframe caches. One pass
4012
+ * over the sessions, no allocation beyond the result.
4013
+ */
4014
+ getMemProbe() {
4015
+ let sum = 0;
4016
+ let max = 0;
4017
+ for (const s of this.sessions.values()) {
4018
+ const q = s.getWriteQueueBytes();
4019
+ sum += q;
4020
+ if (q > max) max = q;
4021
+ }
4022
+ return {
4023
+ sessions: this.sessions.size,
4024
+ writeQueueSumBytes: sum,
4025
+ writeQueueMaxBytes: max,
4026
+ keyframeCacheBytes: this.lastKeyframeNalBytes + this.lastKeyframeRtpBytes
4027
+ };
4028
+ }
3755
4029
  /** Per-session diagnostic snapshots consumed by `StreamBroker.listClients`. */
3756
4030
  listSessionInfos() {
3757
4031
  return [...this.sessions.values()].map((s) => s.getInfo());
@@ -3763,6 +4037,7 @@ var RtspRestreamer = class {
3763
4037
  streamName: this.streamName,
3764
4038
  total: this.sessions.size
3765
4039
  } });
4040
+ reportConsumerEvent(this.logger, "attach", "rtsp", this.deviceId, this.sessions.size);
3766
4041
  this._onSessionCountChanged?.();
3767
4042
  this.reconcileSentryAudio();
3768
4043
  if (this._rtpPassthroughMode) setTimeout(() => this.servePendingSessionsRtp(), 0);
@@ -4227,6 +4502,7 @@ var RtspRestreamer = class {
4227
4502
  streamName: this.streamName,
4228
4503
  total: this.sessions.size
4229
4504
  } });
4505
+ reportConsumerEvent(this.logger, "detach", "rtsp", this.deviceId, this.sessions.size);
4230
4506
  this._onSessionCountChanged?.();
4231
4507
  this.reconcileSentryAudio();
4232
4508
  }
@@ -4496,6 +4772,10 @@ var RtspSession = class {
4496
4772
  this.onClose?.();
4497
4773
  });
4498
4774
  }
4775
+ /** Bytes the socket has accepted but not yet flushed (corked bytes included). */
4776
+ getWriteQueueBytes() {
4777
+ return this.socket.writableLength;
4778
+ }
4499
4779
  getSessionId() {
4500
4780
  return this.sessionId;
4501
4781
  }
@@ -4863,6 +5143,17 @@ var RtspSession = class {
4863
5143
  //#endregion
4864
5144
  //#region src/stream-broker/rtsp/rtsp-listen-server.ts
4865
5145
  /**
5146
+ * Attribution of the synthetic consumer a {@link BrokerWarmUp} subscribes:
5147
+ * the RTSP listen server holding a cold broker open for a DESCRIBE. Named
5148
+ * after that real consumer — it used to subscribe with no attribution and
5149
+ * surfaced as `unknown` (45 attach/detach pairs per 5 min on the hub).
5150
+ */
5151
+ var RTSP_DESCRIBE_WARMUP_ATTRIBUTION = {
5152
+ kind: "rtsp-listen",
5153
+ label: "describe-warmup",
5154
+ media: "both"
5155
+ };
5156
+ /**
4866
5157
  * Single TCP server for all RTSP restream connections.
4867
5158
  * Routes clients to the correct RtspRestreamer by a random token path
4868
5159
  * (not the predictable brokerId) for security.
@@ -6449,6 +6740,10 @@ var EgressTranscodeManager = class {
6449
6740
  /** In-flight starts, keyed by sharing key — concurrent acquires share one. */
6450
6741
  pending = /* @__PURE__ */ new Map();
6451
6742
  releaseGraceMs;
6743
+ /** Live egress transcodes (ffmpeg children), for the `[broker-mem]` probe. */
6744
+ activeCount() {
6745
+ return this.entries.size;
6746
+ }
6452
6747
  constructor(opts) {
6453
6748
  this.opts = opts;
6454
6749
  this.releaseGraceMs = opts.releaseGraceMs ?? DEFAULT_RELEASE_GRACE_MS;
@@ -12812,6 +13107,8 @@ var StreamBroker = class StreamBroker {
12812
13107
  * timeout was chosen against.
12813
13108
  */
12814
13109
  static RTP_ACTIVITY_SWEEP_MS = 1e3;
13110
+ /** A source counts as ingesting when its last video packet is younger than this. */
13111
+ static INGESTING_WITHIN_MS = 1e4;
12815
13112
  manualStop = false;
12816
13113
  stopping = false;
12817
13114
  pipeServer;
@@ -13086,6 +13383,33 @@ var StreamBroker = class StreamBroker {
13086
13383
  return this.lastPacketAt;
13087
13384
  }
13088
13385
  /**
13386
+ * What this broker holds, for the `[broker-mem]` probe (`broker-mem-probe.ts`).
13387
+ * Ring sizes are the rings' own counters (backing-store bytes), so this is
13388
+ * O(sessions + subscribers) and allocates only the result.
13389
+ */
13390
+ getMemSample(now) {
13391
+ const encodedByKind = {};
13392
+ for (const entry of this.encodedCallbacks.values()) encodedByKind[entry.attribution.kind] = (encodedByKind[entry.attribution.kind] ?? 0) + 1;
13393
+ for (const entry of this.rtpVideoCallbacks.values()) encodedByKind[entry.attribution.kind] = (encodedByKind[entry.attribution.kind] ?? 0) + 1;
13394
+ const rtsp = this.rtspRestreamer.getMemProbe();
13395
+ return {
13396
+ deviceId: this.numericDeviceId,
13397
+ brokerId: this.deviceId,
13398
+ ingesting: this.lastPacketAt > 0 && now - this.lastPacketAt < StreamBroker.INGESTING_WITHIN_MS,
13399
+ preBufferBytes: this.preBuffer.getBufferedBytes(),
13400
+ clipBytes: this.clipBuffer?.getBufferedBytes() ?? 0,
13401
+ rtpRingBytes: this.rtpRingBytes,
13402
+ keyframeCacheBytes: rtsp.keyframeCacheBytes,
13403
+ rtspSessions: rtsp.sessions,
13404
+ writeQueueSumBytes: rtsp.writeQueueSumBytes,
13405
+ writeQueueMaxBytes: rtsp.writeQueueMaxBytes,
13406
+ encodedByKind,
13407
+ audioSubscribers: this.audioChunkPlane.subscriberCount,
13408
+ pipeClients: this.pipeServer.getClientCount(),
13409
+ annexBPlaneActive: this.annexBPlaneActive
13410
+ };
13411
+ }
13412
+ /**
13089
13413
  * Whether a LAZY publisher is being built right now.
13090
13414
  *
13091
13415
  * A consumer deciding whether it is worth waiting for this source needs to
@@ -14048,6 +14372,7 @@ var StreamBroker = class StreamBroker {
14048
14372
  packetsDelivered: 0
14049
14373
  };
14050
14374
  this.encodedCallbacks.set(callback, entry);
14375
+ reportConsumerEvent(this.logger, "attach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14051
14376
  if (this.streamingDebug) this.logger?.info("encoded subscriber added", { meta: {
14052
14377
  total: this.encodedCallbacks.size,
14053
14378
  subscriberId: entry.id,
@@ -14058,6 +14383,7 @@ var StreamBroker = class StreamBroker {
14058
14383
  this.checkDemand();
14059
14384
  return () => {
14060
14385
  this.encodedCallbacks.delete(callback);
14386
+ reportConsumerEvent(this.logger, "detach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14061
14387
  if (this.streamingDebug) this.logger?.info("encoded subscriber removed", { meta: {
14062
14388
  total: this.encodedCallbacks.size,
14063
14389
  subscriberId: entry.id
@@ -14152,6 +14478,7 @@ var StreamBroker = class StreamBroker {
14152
14478
  packetsDelivered: 0
14153
14479
  };
14154
14480
  this.rtpVideoCallbacks.set(callback, entry);
14481
+ reportConsumerEvent(this.logger, "attach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14155
14482
  if (this.streamingDebug || CH_RTP.on) this.logger?.info("rtp video subscriber added", { meta: {
14156
14483
  total: this.rtpVideoCallbacks.size,
14157
14484
  subscriberId: entry.id,
@@ -14161,6 +14488,7 @@ var StreamBroker = class StreamBroker {
14161
14488
  this.checkDemand();
14162
14489
  return () => {
14163
14490
  this.rtpVideoCallbacks.delete(callback);
14491
+ reportConsumerEvent(this.logger, "detach", attribution.kind, this.numericDeviceId, this.encodedCallbacks.size + this.rtpVideoCallbacks.size);
14164
14492
  if (this.streamingDebug || CH_RTP.on) this.logger?.info("rtp video subscriber removed", { meta: {
14165
14493
  total: this.rtpVideoCallbacks.size,
14166
14494
  subscriberId: entry.id
@@ -14561,6 +14889,7 @@ var StreamBroker = class StreamBroker {
14561
14889
  tag,
14562
14890
  accept: options?.accept
14563
14891
  });
14892
+ reportConsumerEvent(this.logger, "attach", "audio-chunks", this.numericDeviceId, this.audioChunkPlane.subscriberCount);
14564
14893
  this.checkDemand();
14565
14894
  return result;
14566
14895
  }
@@ -14571,7 +14900,10 @@ var StreamBroker = class StreamBroker {
14571
14900
  /** Release an audio-chunk subscription. Returns `true` when it existed. */
14572
14901
  unsubscribeAudioChunks(subscriptionId) {
14573
14902
  const released = this.audioChunkPlane.unsubscribe(subscriptionId);
14574
- if (released) this.checkDemand();
14903
+ if (released) {
14904
+ reportConsumerEvent(this.logger, "detach", "audio-chunks", this.numericDeviceId, this.audioChunkPlane.subscriberCount);
14905
+ this.checkDemand();
14906
+ }
14575
14907
  return released;
14576
14908
  }
14577
14909
  getStats() {
@@ -19398,6 +19730,10 @@ var StreamBrokerManager = class StreamBrokerManager {
19398
19730
  return { killed: broker.killClient(input.channel, input.handle) };
19399
19731
  }
19400
19732
  /** Raw broker instances — internal use only. */
19733
+ /** Live egress transcodes (ffmpeg children) — the `[broker-mem]` probe. */
19734
+ getEgressTranscodeCount() {
19735
+ return this.egressTranscodeManager?.activeCount() ?? 0;
19736
+ }
19401
19737
  getBrokerInstances() {
19402
19738
  return [...this.brokers.values()];
19403
19739
  }
@@ -19664,7 +20000,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19664
20000
  const sourceBroker = this.brokers.get(sourceBrokerId);
19665
20001
  if (!sourceBroker) continue;
19666
20002
  const existingToken = this.persistedTokens.get(profileBrokerId);
19667
- const warmUp = () => sourceBroker.onEncodedData(() => {});
20003
+ const warmUp = () => sourceBroker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
19668
20004
  const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19669
20005
  this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19670
20006
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
@@ -21323,7 +21659,7 @@ var StreamBrokerManager = class StreamBrokerManager {
21323
21659
  this.brokers.set(brokerId, broker);
21324
21660
  this.applyClipRetention(deviceId);
21325
21661
  const existingToken = this.persistedTokens.get(brokerId);
21326
- const warmUp = () => broker.onEncodedData(() => {});
21662
+ const warmUp = () => broker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
21327
21663
  const openPrestreamWindow = () => broker.openPrestreamWindow();
21328
21664
  this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
21329
21665
  if (!existingToken) this.rtspProvider.persistTokens();
@@ -33652,14 +33988,11 @@ function parseWorkerMessage(message) {
33652
33988
  function defaultWorkerFilename() {
33653
33989
  return fileURLToPath(new URL("./demux-worker-child.js", import.meta.url));
33654
33990
  }
33655
- /**
33656
- * JS parse that posts each fragment to one process-wide Worker. Concurrent
33657
- * callers are matched by request id; the worker's own event loop serializes
33658
- * mp4box. Live RTP on the parent thread is not waiting on box parsing.
33659
- */
33660
- function createOffThreadJsParse(deps) {
33991
+ /** {@link createOffThreadJsParse} plus the heap accessor the memory probe reads. */
33992
+ function createOffThreadDemuxClient(deps) {
33661
33993
  const Ctor = deps.WorkerCtor;
33662
33994
  let worker = null;
33995
+ let spawnedAt = 0;
33663
33996
  let nextId = 1;
33664
33997
  const pending = /* @__PURE__ */ new Map();
33665
33998
  const failAll = () => {
@@ -33692,12 +34025,13 @@ function createOffThreadJsParse(deps) {
33692
34025
  const spawned = new Ctor(deps.workerFilename);
33693
34026
  attach(spawned);
33694
34027
  worker = spawned;
34028
+ spawnedAt = Date.now();
33695
34029
  return spawned;
33696
34030
  } catch {
33697
34031
  return null;
33698
34032
  }
33699
34033
  };
33700
- return async (bytes) => {
34034
+ const parse = async (bytes) => {
33701
34035
  if (!deps.existsSync(deps.workerFilename)) return deps.parseInProcess(bytes);
33702
34036
  const w = ensureWorker();
33703
34037
  if (!w) return deps.parseInProcess(bytes);
@@ -33713,9 +34047,36 @@ function createOffThreadJsParse(deps) {
33713
34047
  w.postMessage(request, [copy.buffer]);
33714
34048
  });
33715
34049
  };
34050
+ const readHeaps = async (now = Date.now()) => {
34051
+ const live = worker;
34052
+ const empty = {
34053
+ workers: 0,
34054
+ heapBytes: 0,
34055
+ physicalBytes: 0,
34056
+ usedBytes: 0,
34057
+ maxAgeMs: 0,
34058
+ idleTerminates: false
34059
+ };
34060
+ if (live === null) return empty;
34061
+ const stats = await live.getHeapStatistics?.().catch(() => null);
34062
+ return {
34063
+ ...empty,
34064
+ workers: 1,
34065
+ maxAgeMs: Math.max(0, now - spawnedAt),
34066
+ ...stats ? {
34067
+ heapBytes: stats.total_heap_size,
34068
+ physicalBytes: stats.total_physical_size,
34069
+ usedBytes: stats.used_heap_size
34070
+ } : {}
34071
+ };
34072
+ };
34073
+ return {
34074
+ parse,
34075
+ readHeaps
34076
+ };
33716
34077
  }
33717
- function defaultOffThreadJsParse() {
33718
- return createOffThreadJsParse({
34078
+ function defaultOffThreadClient() {
34079
+ return createOffThreadDemuxClient({
33719
34080
  workerFilename: defaultWorkerFilename(),
33720
34081
  existsSync,
33721
34082
  parseInProcess: demuxSegmentFullJs,
@@ -33725,9 +34086,20 @@ function defaultOffThreadJsParse() {
33725
34086
  var shared;
33726
34087
  /** Process-wide JS parse: one Worker, shared by every WebRTC session. */
33727
34088
  var offThreadJsParse = (bytes) => {
33728
- shared ??= defaultOffThreadJsParse();
33729
- return shared(bytes);
34089
+ shared ??= defaultOffThreadClient();
34090
+ return shared.parse(bytes);
33730
34091
  };
34092
+ /** Heap of the shared demux worker, for the `[broker-mem]` probe (tick only). */
34093
+ async function readDemuxWorkerHeaps(now) {
34094
+ return shared ? shared.readHeaps(now) : {
34095
+ workers: 0,
34096
+ heapBytes: 0,
34097
+ physicalBytes: 0,
34098
+ usedBytes: 0,
34099
+ maxAgeMs: 0,
34100
+ idleTerminates: false
34101
+ };
34102
+ }
33731
34103
  /**
33732
34104
  * Drop-in for the session `demuxVideo` hook: JS parse off the live loop,
33733
34105
  * ffmpeg subprocess fallback unchanged.
@@ -35270,6 +35642,7 @@ var BrokerWebrtcServer = class {
35270
35642
  }
35271
35643
  };
35272
35644
  this.sessions.set(sessionId, entry);
35645
+ reportConsumerEvent(this.logger, "attach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
35273
35646
  session.onClosed = () => this.cleanupSession(sessionId);
35274
35647
  const locateRecorded = this.locateRecordedSegment;
35275
35648
  const readRecorded = this.readRecordedSegmentBytes;
@@ -36487,6 +36860,7 @@ var BrokerWebrtcServer = class {
36487
36860
  const entry = this.sessions.get(sessionId);
36488
36861
  if (!entry) return;
36489
36862
  this.sessions.delete(sessionId);
36863
+ reportConsumerEvent(this.logger, "detach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
36490
36864
  entry.pendingSwitch?.cancel();
36491
36865
  entry.pendingSwitch = void 0;
36492
36866
  let brokerStillServed = false;
@@ -37208,6 +37582,47 @@ function dialAnswerFromError(err) {
37208
37582
  };
37209
37583
  }
37210
37584
  //#endregion
37585
+ //#region src/stream-broker/broker-mem-report.ts
37586
+ /** Linux only: elsewhere (or on a read error) the native view is simply absent. */
37587
+ async function readProc() {
37588
+ if (process.platform !== "linux") return {
37589
+ status: null,
37590
+ maps: null
37591
+ };
37592
+ const [status, maps] = await Promise.all([readFile("/proc/self/status", "utf8").then(parseProcStatus, () => null), readFile("/proc/self/maps", "utf8").then(parseProcMaps, () => null)]);
37593
+ return {
37594
+ status,
37595
+ maps
37596
+ };
37597
+ }
37598
+ async function emitOnce(deps) {
37599
+ const now = Date.now();
37600
+ const [proc, worker] = await Promise.all([readProc(), readDemuxWorkerHeaps(now)]);
37601
+ const resources = process.getActiveResourcesInfo();
37602
+ const line = formatBrokerMem({
37603
+ samples: deps.manager.getBrokerInstances().map((b) => b.getMemSample(now)),
37604
+ webrtcSessions: deps.webrtcSessions(),
37605
+ egressTranscodes: deps.manager.getEgressTranscodeCount(),
37606
+ childProcesses: countActiveResources(resources, "ProcessWrap"),
37607
+ proc,
37608
+ churn: consumerChurn.drain(),
37609
+ copied: readCopyOwnedTotals(),
37610
+ v8: readV8Heap(),
37611
+ worker
37612
+ });
37613
+ deps.logger.info(line);
37614
+ }
37615
+ /** Start the five-minute `[broker-mem]` heartbeat; returns its stop function. */
37616
+ function startBrokerMemReport(deps) {
37617
+ const timer = setInterval(() => {
37618
+ emitOnce(deps).catch((err) => {
37619
+ deps.logger.warn("broker-mem probe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
37620
+ });
37621
+ }, BROKER_MEM_INTERVAL_MS);
37622
+ timer.unref();
37623
+ return () => clearInterval(timer);
37624
+ }
37625
+ //#endregion
37211
37626
  //#region src/stream-broker/event-loop-stall-monitor.ts
37212
37627
  var DEFAULT_INTERVAL_MS = 100;
37213
37628
  var DEFAULT_THRESHOLD_MS = 250;
@@ -37382,6 +37797,8 @@ var StreamBrokerAddon = class extends BaseAddon {
37382
37797
  * leaks those sockets and wedges the next runner's werift ICE agent
37383
37798
  * (DTLS never connects until a full hub restart). */
37384
37799
  webrtcServer = null;
37800
+ /** Stops the five-minute `[broker-mem]` attribution heartbeat. */
37801
+ stopBrokerMemReport = null;
37385
37802
  /** Names this process's event-loop stalls — see onInitialize. */
37386
37803
  loopStallMonitor = null;
37387
37804
  /** The `log-channels` provider for this process. Stopped on shutdown so a
@@ -37739,6 +38156,11 @@ var StreamBrokerAddon = class extends BaseAddon {
37739
38156
  this.webrtcServer = webrtcServer;
37740
38157
  const webrtcSessionProvider = new WebrtcSessionProvider(webrtcServer, this.brokerManager);
37741
38158
  this.metricsSnapshotTimer = setInterval(() => this.emitBrokerMetricsSnapshot(), BROKER_METRICS_SNAPSHOT_INTERVAL_MS);
38159
+ this.stopBrokerMemReport = startBrokerMemReport({
38160
+ logger: this.ctx.logger,
38161
+ manager: this.brokerManager,
38162
+ webrtcSessions: () => webrtcServer.getSessionCount()
38163
+ });
37742
38164
  const widgetsProvider = { listWidgets: async () => [{
37743
38165
  tab: "device-tab",
37744
38166
  label: "Stream Brokers",
@@ -37843,6 +38265,8 @@ var StreamBrokerAddon = class extends BaseAddon {
37843
38265
  async onShutdown() {
37844
38266
  this.logChannels?.stop();
37845
38267
  this.logChannels = null;
38268
+ this.stopBrokerMemReport?.();
38269
+ this.stopBrokerMemReport = null;
37846
38270
  this.loopStallMonitor?.stop();
37847
38271
  this.loopStallMonitor = null;
37848
38272
  this.flushProbeSnapshots(Date.now(), true);
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVgnss_e.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CRxJwkgE.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }