@camstack/addon-pipeline 1.2.90 → 1.2.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{addon-utils-yc-chXuC.js → addon-utils-DnUCCZVx.js} +1 -1
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-n4Nbnxx5.js → dist-BbRv3bM2.js} +139 -22
- package/dist/{dist-BJYwWEZ9.mjs → dist-XbrYiMV3.mjs} +134 -23
- package/dist/{event-loop-stall-monitor-BiPffk0D.mjs → event-loop-stall-monitor-CFWrOZ6G.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-DG7QY00k.js → event-loop-stall-monitor-D9hqbc68.js} +1 -1
- package/dist/{lazy-sharp-EoSOHgoj.js → lazy-sharp-U0EtN7_C.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +195 -14
- package/dist/pipeline-runner/index.mjs +194 -13
- package/dist/recorder/index.js +655 -84
- package/dist/recorder/index.mjs +654 -83
- package/dist/session-decode/decode-worker-child.js +4 -4
- package/dist/session-decode/decode-worker-child.mjs +3 -3
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CsrMZYLB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BNOPhQ-y.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DMyOrQHq.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DgivrPsH.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DsIi4G5Q.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DoFovQmc.mjs → hostInit-DCV2-mSi.mjs} +2 -2
- package/dist/stream-broker/index.js +194 -30
- package/dist/stream-broker/index.mjs +194 -30
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DjuxhGo3.js → worker-protocol-CNC-ZcU3.js} +1 -1
- package/dist/{worker-protocol-XZ1e36rM.mjs → worker-protocol-D0ewr67U.mjs} +1 -1
- package/package.json +3 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DNSELPGi.mjs +0 -26
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
7
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
5
|
+
const require_dist = require("../dist-BbRv3bM2.js");
|
|
6
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-D9hqbc68.js");
|
|
7
|
+
const require_worker_protocol = require("../worker-protocol-CNC-ZcU3.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
9
|
+
const require_lazy_sharp = require("../lazy-sharp-U0EtN7_C.js");
|
|
10
10
|
const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
|
|
11
11
|
let sharp = require("sharp");
|
|
12
12
|
sharp = require_dist.__toESM(sharp);
|
|
@@ -134,6 +134,8 @@ function startGuardedSessionDecode(deps) {
|
|
|
134
134
|
let stopPump = null;
|
|
135
135
|
let release = null;
|
|
136
136
|
let retryTimer = null;
|
|
137
|
+
/** The live-worker slot held by the RUNNING session, released on teardown. */
|
|
138
|
+
let workerLease = null;
|
|
137
139
|
const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
|
|
138
140
|
/** Resolves after the backoff for `attempt`, or immediately once aborted. */
|
|
139
141
|
const waitBackoff = (attempt) => new Promise((resolve) => {
|
|
@@ -146,10 +148,19 @@ function startGuardedSessionDecode(deps) {
|
|
|
146
148
|
});
|
|
147
149
|
(async () => {
|
|
148
150
|
for (let attempt = 0; !aborted; attempt++) {
|
|
151
|
+
let lease = null;
|
|
152
|
+
if (deps.admitWorker) {
|
|
153
|
+
lease = deps.admitWorker();
|
|
154
|
+
if (!lease) {
|
|
155
|
+
await waitBackoff(attempt);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
149
159
|
let acquired = null;
|
|
150
160
|
try {
|
|
151
161
|
acquired = await deps.acquire();
|
|
152
162
|
} catch (err) {
|
|
163
|
+
lease?.release();
|
|
153
164
|
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
|
|
154
165
|
attempt: attempt + 1,
|
|
155
166
|
error: require_dist.errMsg(err)
|
|
@@ -158,16 +169,19 @@ function startGuardedSessionDecode(deps) {
|
|
|
158
169
|
continue;
|
|
159
170
|
}
|
|
160
171
|
if (aborted) {
|
|
172
|
+
lease?.release();
|
|
161
173
|
if (acquired) acquired.release();
|
|
162
174
|
return;
|
|
163
175
|
}
|
|
164
176
|
if (!acquired) {
|
|
177
|
+
lease?.release();
|
|
165
178
|
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
|
|
166
179
|
await waitBackoff(attempt);
|
|
167
180
|
continue;
|
|
168
181
|
}
|
|
169
182
|
if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
|
|
170
183
|
release = acquired.release;
|
|
184
|
+
workerLease = lease;
|
|
171
185
|
stopPump = deps.startPump(acquired.source);
|
|
172
186
|
return;
|
|
173
187
|
}
|
|
@@ -181,6 +195,8 @@ function startGuardedSessionDecode(deps) {
|
|
|
181
195
|
retryTimer = null;
|
|
182
196
|
}
|
|
183
197
|
stopPump?.();
|
|
198
|
+
workerLease?.release();
|
|
199
|
+
workerLease = null;
|
|
184
200
|
if (release) release();
|
|
185
201
|
};
|
|
186
202
|
}
|
|
@@ -1394,7 +1410,9 @@ var RunSyntheticBenchInputSchema = require_dist.object({
|
|
|
1394
1410
|
sessionId: require_dist.string().optional(),
|
|
1395
1411
|
simulatePipeline: require_dist.boolean().optional(),
|
|
1396
1412
|
nodeId: require_dist.string().optional(),
|
|
1397
|
-
engine: BenchEngineChoiceSchema.optional()
|
|
1413
|
+
engine: BenchEngineChoiceSchema.optional(),
|
|
1414
|
+
deviceKey: require_dist.string().optional(),
|
|
1415
|
+
plane: require_dist._enum(["full", "frame"]).optional()
|
|
1398
1416
|
});
|
|
1399
1417
|
var TimingSplitSchema = require_dist.object({
|
|
1400
1418
|
mean: require_dist.number(),
|
|
@@ -1449,6 +1467,48 @@ var pipelineRunnerBenchActions = require_dist.defineCustomActions({
|
|
|
1449
1467
|
abortCapacityRamp: require_dist.customAction(AbortCapacityRampInputSchema, AbortCapacityRampResultSchema, { kind: "mutation" })
|
|
1450
1468
|
});
|
|
1451
1469
|
//#endregion
|
|
1470
|
+
//#region src/pipeline-runner/bench-dispatch.ts
|
|
1471
|
+
/**
|
|
1472
|
+
* Why the cached fast path is unavailable, or `null` if nothing blocks it.
|
|
1473
|
+
*
|
|
1474
|
+
* The fast path is `cacheFrameInPool` once + `inferCached` per call, and
|
|
1475
|
+
* NEITHER cap method takes a `deviceKey` or a `plane` — only `runPipeline`
|
|
1476
|
+
* does. Forwarding a device selector into it would scope the WARMUP
|
|
1477
|
+
* `runPipeline` to the chosen accelerator and leave every TIMED call on the
|
|
1478
|
+
* node's default pool: a measurement of the wrong device, reported as the
|
|
1479
|
+
* right one. Refusing the fast path costs a slower bench and buys a true
|
|
1480
|
+
* number.
|
|
1481
|
+
*/
|
|
1482
|
+
function fastPathRefusalReason(input) {
|
|
1483
|
+
if (input.deviceKey) return `deviceKey '${input.deviceKey}' requested — inferCached has no device selector, so the timed calls would land on the node's default pool; using the runPipeline path instead`;
|
|
1484
|
+
if (input.plane) return `plane '${input.plane}' requested — inferCached has no plane selector; using the runPipeline path instead`;
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* The cached fast path is only for a plain, single-step, default-device,
|
|
1489
|
+
* default-plane inference bench.
|
|
1490
|
+
*/
|
|
1491
|
+
function canUseCachedFastPath(isSingleStep, input) {
|
|
1492
|
+
return isSingleStep && !input.simulatePipeline && fastPathRefusalReason(input) === null;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Build the `pipelineExecutor.runPipeline` argument for one bench call.
|
|
1496
|
+
*
|
|
1497
|
+
* Every optional is ABSENT when unset rather than present-and-undefined: an
|
|
1498
|
+
* omitted `deviceKey` means "the runner's default device" and an omitted
|
|
1499
|
+
* `plane` means the cap's `'full'` default, and the two are not the same as
|
|
1500
|
+
* an explicit `undefined` on a `.loose()` router input.
|
|
1501
|
+
*/
|
|
1502
|
+
function buildBenchRunPipelineInput(req) {
|
|
1503
|
+
return {
|
|
1504
|
+
steps: [...req.steps],
|
|
1505
|
+
frame: req.frame,
|
|
1506
|
+
...req.engine ? { engine: req.engine } : {},
|
|
1507
|
+
...req.deviceKey ? { deviceKey: req.deviceKey } : {},
|
|
1508
|
+
...req.plane ? { plane: req.plane } : {}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
//#endregion
|
|
1452
1512
|
//#region src/pipeline-runner/capacity/capacity-runner.ts
|
|
1453
1513
|
/**
|
|
1454
1514
|
* Run ONE ramp step: `cameras` independent open-loop pacers for
|
|
@@ -3870,6 +3930,76 @@ var PipelineRunner = class {
|
|
|
3870
3930
|
}
|
|
3871
3931
|
};
|
|
3872
3932
|
//#endregion
|
|
3933
|
+
//#region src/session-decode/live-worker-admission.ts
|
|
3934
|
+
/**
|
|
3935
|
+
* The node's live-decode-worker budget.
|
|
3936
|
+
*
|
|
3937
|
+
* One instance per runner addon. Not a semaphore: a semaphore's contract is
|
|
3938
|
+
* "wait", and waiting is the thing this deliberately does not do — see
|
|
3939
|
+
* {@link DEFAULT_MAX_LIVE_DECODE_WORKERS}.
|
|
3940
|
+
*/
|
|
3941
|
+
var LiveDecodeWorkerAdmission = class {
|
|
3942
|
+
limit;
|
|
3943
|
+
logger;
|
|
3944
|
+
live = 0;
|
|
3945
|
+
constructor(options) {
|
|
3946
|
+
this.limit = Math.max(1, options.limit);
|
|
3947
|
+
this.logger = options.logger;
|
|
3948
|
+
}
|
|
3949
|
+
/** Workers currently holding a slot. */
|
|
3950
|
+
get inFlight() {
|
|
3951
|
+
return this.live;
|
|
3952
|
+
}
|
|
3953
|
+
get ceiling() {
|
|
3954
|
+
return this.limit;
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Apply an operator-changed ceiling.
|
|
3958
|
+
*
|
|
3959
|
+
* Lowering it never kills a running session — a decode worker torn down
|
|
3960
|
+
* mid-motion loses the event it was started for. It only stops the next
|
|
3961
|
+
* admission, so the count drains to the new ceiling as sessions end.
|
|
3962
|
+
*/
|
|
3963
|
+
resize(limit) {
|
|
3964
|
+
this.limit = Math.max(1, limit);
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* Take a slot, or refuse.
|
|
3968
|
+
*
|
|
3969
|
+
* A refusal is a WARN and carries `tags.deviceId`, because the only useful
|
|
3970
|
+
* form of this question is per-camera ("why did 617 not run when 615 did?").
|
|
3971
|
+
* A branch that drops work silently reads as "it never happened".
|
|
3972
|
+
*/
|
|
3973
|
+
admit(deviceId, label) {
|
|
3974
|
+
if (this.live >= this.limit) {
|
|
3975
|
+
this.logger.warn("session-decode: REFUSED — node live decode-worker ceiling reached, will retry", {
|
|
3976
|
+
tags: { deviceId },
|
|
3977
|
+
meta: {
|
|
3978
|
+
label,
|
|
3979
|
+
live: this.live,
|
|
3980
|
+
ceiling: this.limit
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
return null;
|
|
3984
|
+
}
|
|
3985
|
+
this.live += 1;
|
|
3986
|
+
this.logger.debug("session-decode: live decode-worker slot taken", {
|
|
3987
|
+
tags: { deviceId },
|
|
3988
|
+
meta: {
|
|
3989
|
+
label,
|
|
3990
|
+
live: this.live,
|
|
3991
|
+
ceiling: this.limit
|
|
3992
|
+
}
|
|
3993
|
+
});
|
|
3994
|
+
let released = false;
|
|
3995
|
+
return { release: () => {
|
|
3996
|
+
if (released) return;
|
|
3997
|
+
released = true;
|
|
3998
|
+
this.live -= 1;
|
|
3999
|
+
} };
|
|
4000
|
+
}
|
|
4001
|
+
};
|
|
4002
|
+
//#endregion
|
|
3873
4003
|
//#region src/pipeline-runner/stateless-step.ts
|
|
3874
4004
|
function modelServability(step, modelId, format) {
|
|
3875
4005
|
const entry = step.models.find((m) => m.id === modelId);
|
|
@@ -4008,7 +4138,8 @@ var DEFAULT_CONFIG = {
|
|
|
4008
4138
|
maxConcurrentInferences: 16,
|
|
4009
4139
|
targetLoadPercent: 80,
|
|
4010
4140
|
minThrottledFps: 1,
|
|
4011
|
-
maxConcurrentDecodeSpawns: 3
|
|
4141
|
+
maxConcurrentDecodeSpawns: 3,
|
|
4142
|
+
maxLiveDecodeWorkers: 8
|
|
4012
4143
|
};
|
|
4013
4144
|
/**
|
|
4014
4145
|
* Camera attachment record kept by the runner addon. Holds the original
|
|
@@ -4311,6 +4442,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4311
4442
|
*/
|
|
4312
4443
|
spawnGate = null;
|
|
4313
4444
|
/**
|
|
4445
|
+
* Per-node ceiling on decode workers ALIVE at once, sized to
|
|
4446
|
+
* `config.maxLiveDecodeWorkers`. The spawn gate above bounds how fast workers
|
|
4447
|
+
* START; this bounds how many EXIST — measured live at 1 -> 8 workers and a
|
|
4448
|
+
* ~4 GiB move in 8 seconds, with nothing stopping the 9th. `resize()`d live in
|
|
4449
|
+
* `onConfigChanged`. Null until `onInitialize`.
|
|
4450
|
+
*/
|
|
4451
|
+
liveWorkers = null;
|
|
4452
|
+
/**
|
|
4314
4453
|
* Node-level map from a retention-ring `FrameHandle` to the decode worker
|
|
4315
4454
|
* still holding that frame's NATIVE surface — backs the `getNativeCrop` cap
|
|
4316
4455
|
* so post-analysis can cut native-res crops (best-effort; a miss falls back
|
|
@@ -4534,6 +4673,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4534
4673
|
}
|
|
4535
4674
|
});
|
|
4536
4675
|
this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
|
|
4676
|
+
this.liveWorkers = new LiveDecodeWorkerAdmission({
|
|
4677
|
+
limit: this.config.maxLiveDecodeWorkers,
|
|
4678
|
+
logger: this.ctx.logger
|
|
4679
|
+
});
|
|
4537
4680
|
this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
|
|
4538
4681
|
this.runner.onDetectionStreamChange((deviceId, action) => {
|
|
4539
4682
|
this.handleDetectionStreamChange(deviceId, action);
|
|
@@ -4694,8 +4837,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4694
4837
|
children: s.children ?? []
|
|
4695
4838
|
}));
|
|
4696
4839
|
const enabledSteps = stepsToRun.filter((s) => s.enabled);
|
|
4697
|
-
const
|
|
4840
|
+
const isSingleStep = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0);
|
|
4841
|
+
const useFastPath = canUseCachedFastPath(isSingleStep, input);
|
|
4698
4842
|
const rootStep = enabledSteps[0];
|
|
4843
|
+
const fastPathRefusal = isSingleStep ? fastPathRefusalReason(input) : null;
|
|
4844
|
+
if (fastPathRefusal) ctx.logger.info("synthetic bench: cached fast path declined", { meta: {
|
|
4845
|
+
reason: fastPathRefusal,
|
|
4846
|
+
node: benchNode
|
|
4847
|
+
} });
|
|
4699
4848
|
const sharedFrame = {
|
|
4700
4849
|
data: cached.data,
|
|
4701
4850
|
format: cached.format,
|
|
@@ -4715,11 +4864,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4715
4864
|
height: cached.height,
|
|
4716
4865
|
format: cached.format
|
|
4717
4866
|
}, pin)).frameId;
|
|
4718
|
-
await api.pipelineExecutor.runPipeline.mutate({
|
|
4867
|
+
await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
4719
4868
|
steps: stepsToRun,
|
|
4720
4869
|
frame: sharedFrame,
|
|
4721
4870
|
...input.engine ? { engine: input.engine } : {}
|
|
4722
|
-
}, pin);
|
|
4871
|
+
}) }, pin);
|
|
4723
4872
|
const warmupCount = input.warmup ?? 1;
|
|
4724
4873
|
for (let w = 0; w < warmupCount; w++) await api.pipelineExecutor.inferCached.mutate({
|
|
4725
4874
|
stepId: rootStep.addonId,
|
|
@@ -4812,16 +4961,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4812
4961
|
}
|
|
4813
4962
|
ctx.logger.info("synthetic bench: using full runPipeline path", { meta: {
|
|
4814
4963
|
steps: enabledSteps.length,
|
|
4815
|
-
simulatePipeline: !!input.simulatePipeline
|
|
4964
|
+
simulatePipeline: !!input.simulatePipeline,
|
|
4965
|
+
deviceKey: input.deviceKey ?? null,
|
|
4966
|
+
plane: input.plane ?? null
|
|
4816
4967
|
} });
|
|
4817
4968
|
let _callCount = 0;
|
|
4818
4969
|
const callOnce = async () => {
|
|
4819
4970
|
const t0 = performance.now();
|
|
4820
|
-
const result = await api.pipelineExecutor.runPipeline.mutate({
|
|
4971
|
+
const result = await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
4821
4972
|
steps: stepsToRun,
|
|
4822
4973
|
frame: sharedFrame,
|
|
4823
|
-
...input.engine ? { engine: input.engine } : {}
|
|
4824
|
-
|
|
4974
|
+
...input.engine ? { engine: input.engine } : {},
|
|
4975
|
+
...input.deviceKey ? { deviceKey: input.deviceKey } : {},
|
|
4976
|
+
...input.plane ? { plane: input.plane } : {}
|
|
4977
|
+
}) }, pin);
|
|
4825
4978
|
const wallMs = performance.now() - t0;
|
|
4826
4979
|
const n = ++_callCount;
|
|
4827
4980
|
if (n <= 20) ctx.logger.info("bench call trace", { meta: {
|
|
@@ -6198,6 +6351,18 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6198
6351
|
* blocked by a missing gate. `label` distinguishes detect vs motion forks in
|
|
6199
6352
|
* the logs; the device id is already tagged on `log`.
|
|
6200
6353
|
*/
|
|
6354
|
+
/**
|
|
6355
|
+
* The node's live-decode-worker admission, bound to one camera.
|
|
6356
|
+
*
|
|
6357
|
+
* `undefined` when the ceiling is not configured (pre-`onInitialize`), which
|
|
6358
|
+
* `startGuardedSessionDecode` reads as "no ceiling" — the historical
|
|
6359
|
+
* behaviour. Never a queue: see {@link LiveDecodeWorkerAdmission}.
|
|
6360
|
+
*/
|
|
6361
|
+
buildLiveWorkerAdmit(deviceId, label) {
|
|
6362
|
+
const admission = this.liveWorkers;
|
|
6363
|
+
if (!admission) return void 0;
|
|
6364
|
+
return () => admission.admit(deviceId, label);
|
|
6365
|
+
}
|
|
6201
6366
|
buildDecodeSpawnAcquire(log, label) {
|
|
6202
6367
|
return async () => {
|
|
6203
6368
|
const gate = this.spawnGate;
|
|
@@ -6225,6 +6390,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6225
6390
|
const requestedAt = Date.now();
|
|
6226
6391
|
const teardown = startGuardedSessionDecode({
|
|
6227
6392
|
logger: log,
|
|
6393
|
+
admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "detect"),
|
|
6228
6394
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config), true),
|
|
6229
6395
|
startPump: (source) => {
|
|
6230
6396
|
const dialMs = Date.now() - requestedAt;
|
|
@@ -6297,6 +6463,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6297
6463
|
if (!runner) return () => {};
|
|
6298
6464
|
return startGuardedSessionDecode({
|
|
6299
6465
|
logger: log,
|
|
6466
|
+
admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "motion"),
|
|
6300
6467
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
6301
6468
|
startPump: (source) => {
|
|
6302
6469
|
log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
|
|
@@ -6853,6 +7020,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6853
7020
|
}, OCCUPANCY_BURST_SAFETY_TIMEOUT_MS);
|
|
6854
7021
|
teardown = startGuardedSessionDecode({
|
|
6855
7022
|
logger: log,
|
|
7023
|
+
admitWorker: this.buildLiveWorkerAdmit(deviceId, "occupancy"),
|
|
6856
7024
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:occupancy:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
6857
7025
|
startPump: (source) => startSessionDecodePump({
|
|
6858
7026
|
fork: this.forkDecodeWorkerWithLease,
|
|
@@ -7026,6 +7194,17 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
7026
7194
|
step: 1,
|
|
7027
7195
|
default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
|
|
7028
7196
|
showValue: true
|
|
7197
|
+
},
|
|
7198
|
+
{
|
|
7199
|
+
type: "slider",
|
|
7200
|
+
key: "maxLiveDecodeWorkers",
|
|
7201
|
+
label: "Max live decode workers",
|
|
7202
|
+
description: "Hard ceiling on decode workers ALIVE at once on this node (~320 MB of RAM each). Unlike the spawn gate above, which only limits how fast workers START, this limits how many EXIST. A camera that does not fit is REFUSED and logged, then retried automatically as soon as a slot frees — it is never queued, because a queued live session would decode a moment that has already passed. Raise it if the node has RAM to spare; lower it if the host is memory-constrained.",
|
|
7203
|
+
min: 1,
|
|
7204
|
+
max: 32,
|
|
7205
|
+
step: 1,
|
|
7206
|
+
default: DEFAULT_CONFIG.maxLiveDecodeWorkers,
|
|
7207
|
+
showValue: true
|
|
7029
7208
|
}
|
|
7030
7209
|
]
|
|
7031
7210
|
}] });
|
|
@@ -7033,12 +7212,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
7033
7212
|
async onConfigChanged() {
|
|
7034
7213
|
this.runner?.updateLimits(this.config);
|
|
7035
7214
|
this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
|
|
7215
|
+
this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
|
|
7036
7216
|
this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
|
|
7037
7217
|
maxQueueDepth: this.config.maxQueueDepth,
|
|
7038
7218
|
maxConcurrentInferences: this.config.maxConcurrentInferences,
|
|
7039
7219
|
targetLoadPercent: this.config.targetLoadPercent,
|
|
7040
7220
|
minThrottledFps: this.config.minThrottledFps,
|
|
7041
|
-
maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns
|
|
7221
|
+
maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns,
|
|
7222
|
+
maxLiveDecodeWorkers: this.config.maxLiveDecodeWorkers
|
|
7042
7223
|
} });
|
|
7043
7224
|
}
|
|
7044
7225
|
};
|