@camstack/addon-pipeline 1.2.93 → 1.2.95

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 (36) hide show
  1. package/dist/{addon-utils-CTRpEiSd.js → addon-utils-UMWiMAxR.js} +1 -1
  2. package/dist/audio-analyzer/index.js +58 -4
  3. package/dist/audio-analyzer/index.mjs +56 -2
  4. package/dist/detection-pipeline/index.js +194 -6
  5. package/dist/detection-pipeline/index.mjs +191 -3
  6. package/dist/{dist-6POVIYmC.mjs → dist-C49o1k7u.mjs} +359 -21
  7. package/dist/{dist-D9To_r_d.js → dist-hyryByiw.js} +376 -20
  8. package/dist/{event-loop-stall-monitor-C-VvWOxS.mjs → event-loop-stall-monitor-CfxZBgNi.mjs} +1 -1
  9. package/dist/{event-loop-stall-monitor-CGacbzXq.js → event-loop-stall-monitor-DPNTzGOT.js} +1 -1
  10. package/dist/{lazy-sharp-C3zOnmiU.js → lazy-sharp-Dxg6UeW-.js} +1 -1
  11. package/dist/motion-wasm/index.js +2 -2
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +195 -14
  14. package/dist/pipeline-runner/index.mjs +194 -13
  15. package/dist/process-memory-B9-UD7Rp.js +78 -0
  16. package/dist/process-memory-DXOB6eHX.mjs +66 -0
  17. package/dist/recorder/index.js +943 -109
  18. package/dist/recorder/index.mjs +942 -108
  19. package/dist/session-decode/decode-worker-child.js +4 -4
  20. package/dist/session-decode/decode-worker-child.mjs +3 -3
  21. package/dist/stream-broker/_stub.js +1 -1
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2woB6bY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CF-c0cDZ.mjs} +3 -3
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BjvmJK_d.mjs +26 -0
  24. package/dist/stream-broker/{hostInit-VCURcZLt.mjs → hostInit-Cpcf2hDB.mjs} +3 -3
  25. package/dist/stream-broker/index.js +25 -7
  26. package/dist/stream-broker/index.mjs +25 -7
  27. package/dist/stream-broker/remoteEntry.js +1 -1
  28. package/dist/{worker-protocol-Bq28qobd.js → worker-protocol-DR_a77D5.js} +1 -1
  29. package/dist/{worker-protocol-PzAKg8mC.mjs → worker-protocol-DqQ2GI4M.mjs} +1 -1
  30. package/package.json +13 -7
  31. package/python/inference_pool.py +178 -3
  32. package/python/test_inference_pool_backpressure.py +7 -1
  33. package/python/test_inference_pool_memstats.py +146 -0
  34. package/dist/node-topology-platform-BkR_k6WT.mjs +0 -15
  35. package/dist/node-topology-platform-CFZ7F4xW.js +0 -20
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dut56EeQ.mjs +0 -26
@@ -1,8 +1,8 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { B as enumerateInferenceDevices, Et as array, Ft as union, It as EventCategory, J as pipelineExecutorCapability, L as detectionPipelineCapability, M as defaultDeviceFor$1, Mt as object, Pt as string, V as evaluateZoneRules, at as errMsg, bt as nodePin, d as DEVICE_BACKEND_TO_FORMAT, dt as BaseAddon, et as runtimeDevices$1, gt as hydrateSchema, ht as createEvent, rt as supportedRuntimes$1, t as APPLE_SA_TO_MACRO, w as YAMNET_TO_MACRO, wt as sleep, xt as parseJsonUnknown } from "../dist-6POVIYmC.mjs";
3
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-C-VvWOxS.mjs";
2
+ import { Ct as nodePin, Dt as sleep, Ft as object, H as evaluateZoneRules, Lt as string, N as defaultDeviceFor$1, R as detectionPipelineCapability, Rt as union, T as YAMNET_TO_MACRO, V as enumerateInferenceDevices, X as pipelineExecutorCapability, ct as errMsg, d as DEVICE_BACKEND_TO_FORMAT, kt as array, mt as BaseAddon, ot as supportedRuntimes$1, rt as runtimeDevices$1, t as APPLE_SA_TO_MACRO, tt as resolvePoolMemoryPolicy, vt as createEvent, wt as parseJsonUnknown, y as PoolMemoryWatchdog, yt as hydrateSchema, zt as EventCategory } from "../dist-C49o1k7u.mjs";
3
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-CfxZBgNi.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
5
- import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
5
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-DXOB6eHX.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-A2S9D7pu.mjs";
7
7
  import sharp from "sharp";
8
8
  import { spawn } from "node:child_process";
@@ -701,6 +701,14 @@ var PoolWorker = class {
701
701
  const payload = Buffer.from(JSON.stringify(cmd), "utf8");
702
702
  return await this.dispatch(MSG_COMMAND, payload);
703
703
  }
704
+ /** Command whose reply shape is NOT the load/unload/replace/status envelope
705
+ * (e.g. `mem_stats`) — returns the raw JSON record, so no cast is needed
706
+ * at the call site. */
707
+ async sendRawCommand(cmd) {
708
+ this.ensureReady();
709
+ const payload = Buffer.from(JSON.stringify(cmd), "utf8");
710
+ return this.dispatch(MSG_COMMAND, payload);
711
+ }
704
712
  async dispose() {
705
713
  const proc = this.process;
706
714
  if (!proc) return;
@@ -1065,6 +1073,46 @@ var SharedInferencePool = class {
1065
1073
  for (const resp of responses) if (resp.status !== "ok") throw new Error(`Failed to replace model at index ${index}: ${resp.error ?? "unknown"}`);
1066
1074
  return { loadMs: Math.max(...responses.map((r) => r.loadMs ?? 0)) };
1067
1075
  }
1076
+ /**
1077
+ * Per-worker Python-side memory diagnostics (`mem_stats` command):
1078
+ * RSS/VmSize/peak/threads from /proc/self, getrusage maxrss, glibc malloc
1079
+ * arena count, gc counts, served-request counters, per-model queue depth,
1080
+ * cache entry counts. Queried by the pool memory watchdog once per sweep —
1081
+ * the internals that distinguish "leak" from "working set" and that
1082
+ * /proc/<pid> alone cannot see. A worker that is not ready (or whose
1083
+ * command fails) contributes an `error` record instead of vanishing.
1084
+ */
1085
+ async getMemStats() {
1086
+ const out = [];
1087
+ for (const [i, w] of this.workers.entries()) {
1088
+ if (!w.isReady()) {
1089
+ out.push({
1090
+ worker: `w${i}`,
1091
+ pid: w.getPid(),
1092
+ stats: null,
1093
+ error: "worker not ready"
1094
+ });
1095
+ continue;
1096
+ }
1097
+ try {
1098
+ const stats = await w.sendRawCommand({ cmd: "mem_stats" });
1099
+ out.push({
1100
+ worker: `w${i}`,
1101
+ pid: w.getPid(),
1102
+ stats,
1103
+ error: null
1104
+ });
1105
+ } catch (err) {
1106
+ out.push({
1107
+ worker: `w${i}`,
1108
+ pid: w.getPid(),
1109
+ stats: null,
1110
+ error: err instanceof Error ? err.message : String(err)
1111
+ });
1112
+ }
1113
+ }
1114
+ return out;
1115
+ }
1068
1116
  async getStatus() {
1069
1117
  if (this.workers.length === 0) return [];
1070
1118
  return (await this.workers[0].sendCommand({ cmd: "status" })).models ?? [];
@@ -1783,6 +1831,29 @@ var EngineFactory = class {
1783
1831
  getPoolPid() {
1784
1832
  return this.pool?.getPid() ?? null;
1785
1833
  }
1834
+ /** All live worker pids of the underlying Python pool (memory watchdog). */
1835
+ getPoolPids() {
1836
+ return (this.pool?.getPids() ?? []).filter((pid) => pid !== null);
1837
+ }
1838
+ /** Summed backlog / shed / dropped across the pool's workers — the cheap
1839
+ * internals the memory watchdog attaches to its periodic `pool memory`
1840
+ * line so a leak (RSS up, work flat) is separable from load. */
1841
+ getPoolBacklog() {
1842
+ if (!this.pool) return {
1843
+ inFlight: 0,
1844
+ shed: 0,
1845
+ dropped: 0
1846
+ };
1847
+ return {
1848
+ ...this.pool.getBacklog(),
1849
+ dropped: this.pool.getDroppedResponseCount()
1850
+ };
1851
+ }
1852
+ /** Python-side per-worker memory diagnostics (see SharedInferencePool). */
1853
+ async getPoolMemStats() {
1854
+ if (!this.pool) return [];
1855
+ return this.pool.getMemStats();
1856
+ }
1786
1857
  /**
1787
1858
  * Whether this factory exposes the batched fast path (`batchInferRaw`).
1788
1859
  */
@@ -1992,6 +2063,65 @@ var IdlePoolReaper = class {
1992
2063
  }
1993
2064
  };
1994
2065
  //#endregion
2066
+ //#region src/detection-pipeline/engine/pool-memory-guard.ts
2067
+ function createDetectionPoolMemoryGuard(source, logger) {
2068
+ const log = logger.child("pool-memory");
2069
+ return new PoolMemoryWatchdog({
2070
+ policy: resolvePoolMemoryPolicy(process.env),
2071
+ log,
2072
+ restart: (key) => source.restartPool(key),
2073
+ sample: async () => {
2074
+ const out = [];
2075
+ for (const pool of source.listPools()) {
2076
+ const telemetry = await samplePool(pool, log);
2077
+ if (telemetry) out.push(telemetry);
2078
+ }
2079
+ return out;
2080
+ }
2081
+ });
2082
+ }
2083
+ async function samplePool(pool, log) {
2084
+ const pids = pool.factory.getPoolPids();
2085
+ if (pids.length === 0) return null;
2086
+ let maxRssBytes = 0;
2087
+ const workers = [];
2088
+ for (const pid of pids) {
2089
+ const mem = await readProcessMemory(pid);
2090
+ if (!mem) {
2091
+ log.warn("pool memory sample dropped for worker pid", { meta: {
2092
+ poolKey: pool.key,
2093
+ pid
2094
+ } });
2095
+ continue;
2096
+ }
2097
+ if (mem.rssBytes > maxRssBytes) maxRssBytes = mem.rssBytes;
2098
+ workers.push({
2099
+ pid,
2100
+ rssMb: Math.round(mem.rssBytes / (1024 * 1024)),
2101
+ vmMb: Math.round(mem.vmBytes / (1024 * 1024)),
2102
+ peakRssMb: Math.round(mem.hwmBytes / (1024 * 1024)),
2103
+ swapMb: Math.round(mem.swapBytes / (1024 * 1024)),
2104
+ threads: mem.threads
2105
+ });
2106
+ }
2107
+ if (workers.length === 0) return null;
2108
+ const backlog = pool.factory.getPoolBacklog();
2109
+ const memStats = await pool.factory.getPoolMemStats().catch((err) => err instanceof Error ? err.message : String(err));
2110
+ return {
2111
+ key: pool.key,
2112
+ pids,
2113
+ rssBytes: maxRssBytes,
2114
+ meta: {
2115
+ workers,
2116
+ inFlight: backlog.inFlight,
2117
+ shed: backlog.shed,
2118
+ dropped: backlog.dropped,
2119
+ modelsLoaded: pool.factory.listLoaded().length,
2120
+ memStats
2121
+ }
2122
+ };
2123
+ }
2124
+ //#endregion
1995
2125
  //#region src/detection-pipeline/engine-provisioner.ts
1996
2126
  /** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
1997
2127
  var BACKOFF_SCHEDULE_MS = [
@@ -4783,6 +4913,55 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4783
4913
  } })
4784
4914
  });
4785
4915
  static OVERRIDE_CACHE_TTL_MS = 6e4;
4916
+ /**
4917
+ * RSS bound + periodic memory telemetry for the Python inference pools
4918
+ * (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
4919
+ * driver — grow for hours, kernel-killed at 3.8/8.8 GB, respawn, repeat).
4920
+ * Policy, restart budget and the `pool memory` Loki line live in
4921
+ * `@camstack/types` (`PoolMemoryWatchdog`); this provider only enumerates
4922
+ * its pools and exposes its EXISTING dispose-and-recreate machinery as the
4923
+ * restart action. Started in `init()`, stopped in `shutdown()`.
4924
+ */
4925
+ poolMemoryGuard = null;
4926
+ /** Watchdog view of the live pools: the node-default pool plus every
4927
+ * per-device pool. Keys are stable per pool identity so the watchdog's
4928
+ * baseline tracks one pool across sweeps. */
4929
+ listGuardedPools() {
4930
+ const pools = [];
4931
+ if (this.engineFactory) pools.push({
4932
+ key: `default:${deviceKeyOf(this.currentEngine)}`,
4933
+ factory: this.engineFactory
4934
+ });
4935
+ for (const [deviceKey, factory] of this.factoriesByDevice) pools.push({
4936
+ key: deviceKey,
4937
+ factory
4938
+ });
4939
+ return pools;
4940
+ }
4941
+ /**
4942
+ * Watchdog restart action — the same dispose-then-lazy-recreate the idle
4943
+ * reaper and the tuning respawn already use, never a second lifecycle. The
4944
+ * map/field is cleared FIRST so a concurrent dispatch rebuilds fresh via
4945
+ * the single-flight guards instead of grabbing the disposing instance;
4946
+ * models reload on the next dispatch via `ensureModelsForSteps`. Frames
4947
+ * arriving during the seconds-long re-spin are dropped (and counted by the
4948
+ * existing shed/timeout paths), not crashed — a bounded gap, unlike the
4949
+ * kernel's alternative.
4950
+ */
4951
+ async restartGuardedPool(key) {
4952
+ if (key.startsWith("default:")) {
4953
+ const factory = this.engineFactory;
4954
+ if (!factory) return;
4955
+ this.engineFactory = null;
4956
+ await factory.dispose();
4957
+ return;
4958
+ }
4959
+ const factory = this.factoriesByDevice.get(key);
4960
+ if (!factory) return;
4961
+ this.factoriesByDevice.delete(key);
4962
+ this.deviceReaper.cancel(key);
4963
+ await factory.dispose();
4964
+ }
4786
4965
  /** Read the addon settings store (all keys). */
4787
4966
  readStore;
4788
4967
  /** Write a patch to the addon settings store. */
@@ -4818,6 +4997,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4818
4997
  async init() {
4819
4998
  this.needsAutoPick = true;
4820
4999
  this.log.info("Node-default engine derive deferred to setApi()");
5000
+ if (!this.poolMemoryGuard) {
5001
+ this.poolMemoryGuard = createDetectionPoolMemoryGuard({
5002
+ listPools: () => this.listGuardedPools(),
5003
+ restartPool: (key) => this.restartGuardedPool(key)
5004
+ }, this.log);
5005
+ this.poolMemoryGuard.start();
5006
+ }
4821
5007
  }
4822
5008
  /**
4823
5009
  * Lazy-install the pip requirements file matching `engine.backend` into
@@ -6648,6 +6834,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6648
6834
  };
6649
6835
  }
6650
6836
  async shutdown() {
6837
+ this.poolMemoryGuard?.stop();
6838
+ this.poolMemoryGuard = null;
6651
6839
  await this.evictOverrideCache("shutdown");
6652
6840
  if (this.engineFactory) {
6653
6841
  await this.engineFactory.dispose();