@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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { At as
|
|
2
|
-
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-
|
|
3
|
-
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-
|
|
1
|
+
import { At as literal, Dt as boolean, Et as array, F as deriveDetailCropRect, It as EventCategory, K as pickDetailCropConvention, Mt as object, N as defineCustomActions, Pt as string, Tt as _enum, Y as pipelineRunnerCapability, at as errMsg, bt as nodePin, c as DEFAULT_DETAIL_CROP_CONVENTION, d as DEVICE_BACKEND_TO_FORMAT, dt as BaseAddon, ht as createEvent, j as customAction, jt as number, kt as lazy, q as pickNativeLeaseOverride } from "../dist-XbrYiMV3.mjs";
|
|
2
|
+
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-CFWrOZ6G.mjs";
|
|
3
|
+
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-D0ewr67U.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
6
6
|
import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
|
|
@@ -128,6 +128,8 @@ function startGuardedSessionDecode(deps) {
|
|
|
128
128
|
let stopPump = null;
|
|
129
129
|
let release = null;
|
|
130
130
|
let retryTimer = null;
|
|
131
|
+
/** The live-worker slot held by the RUNNING session, released on teardown. */
|
|
132
|
+
let workerLease = null;
|
|
131
133
|
const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
|
|
132
134
|
/** Resolves after the backoff for `attempt`, or immediately once aborted. */
|
|
133
135
|
const waitBackoff = (attempt) => new Promise((resolve) => {
|
|
@@ -140,10 +142,19 @@ function startGuardedSessionDecode(deps) {
|
|
|
140
142
|
});
|
|
141
143
|
(async () => {
|
|
142
144
|
for (let attempt = 0; !aborted; attempt++) {
|
|
145
|
+
let lease = null;
|
|
146
|
+
if (deps.admitWorker) {
|
|
147
|
+
lease = deps.admitWorker();
|
|
148
|
+
if (!lease) {
|
|
149
|
+
await waitBackoff(attempt);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
143
153
|
let acquired = null;
|
|
144
154
|
try {
|
|
145
155
|
acquired = await deps.acquire();
|
|
146
156
|
} catch (err) {
|
|
157
|
+
lease?.release();
|
|
147
158
|
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
|
|
148
159
|
attempt: attempt + 1,
|
|
149
160
|
error: errMsg(err)
|
|
@@ -152,16 +163,19 @@ function startGuardedSessionDecode(deps) {
|
|
|
152
163
|
continue;
|
|
153
164
|
}
|
|
154
165
|
if (aborted) {
|
|
166
|
+
lease?.release();
|
|
155
167
|
if (acquired) acquired.release();
|
|
156
168
|
return;
|
|
157
169
|
}
|
|
158
170
|
if (!acquired) {
|
|
171
|
+
lease?.release();
|
|
159
172
|
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
|
|
160
173
|
await waitBackoff(attempt);
|
|
161
174
|
continue;
|
|
162
175
|
}
|
|
163
176
|
if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
|
|
164
177
|
release = acquired.release;
|
|
178
|
+
workerLease = lease;
|
|
165
179
|
stopPump = deps.startPump(acquired.source);
|
|
166
180
|
return;
|
|
167
181
|
}
|
|
@@ -175,6 +189,8 @@ function startGuardedSessionDecode(deps) {
|
|
|
175
189
|
retryTimer = null;
|
|
176
190
|
}
|
|
177
191
|
stopPump?.();
|
|
192
|
+
workerLease?.release();
|
|
193
|
+
workerLease = null;
|
|
178
194
|
if (release) release();
|
|
179
195
|
};
|
|
180
196
|
}
|
|
@@ -1387,7 +1403,9 @@ var RunSyntheticBenchInputSchema = object({
|
|
|
1387
1403
|
sessionId: string().optional(),
|
|
1388
1404
|
simulatePipeline: boolean().optional(),
|
|
1389
1405
|
nodeId: string().optional(),
|
|
1390
|
-
engine: BenchEngineChoiceSchema.optional()
|
|
1406
|
+
engine: BenchEngineChoiceSchema.optional(),
|
|
1407
|
+
deviceKey: string().optional(),
|
|
1408
|
+
plane: _enum(["full", "frame"]).optional()
|
|
1391
1409
|
});
|
|
1392
1410
|
var TimingSplitSchema = object({
|
|
1393
1411
|
mean: number(),
|
|
@@ -1442,6 +1460,48 @@ var pipelineRunnerBenchActions = defineCustomActions({
|
|
|
1442
1460
|
abortCapacityRamp: customAction(AbortCapacityRampInputSchema, AbortCapacityRampResultSchema, { kind: "mutation" })
|
|
1443
1461
|
});
|
|
1444
1462
|
//#endregion
|
|
1463
|
+
//#region src/pipeline-runner/bench-dispatch.ts
|
|
1464
|
+
/**
|
|
1465
|
+
* Why the cached fast path is unavailable, or `null` if nothing blocks it.
|
|
1466
|
+
*
|
|
1467
|
+
* The fast path is `cacheFrameInPool` once + `inferCached` per call, and
|
|
1468
|
+
* NEITHER cap method takes a `deviceKey` or a `plane` — only `runPipeline`
|
|
1469
|
+
* does. Forwarding a device selector into it would scope the WARMUP
|
|
1470
|
+
* `runPipeline` to the chosen accelerator and leave every TIMED call on the
|
|
1471
|
+
* node's default pool: a measurement of the wrong device, reported as the
|
|
1472
|
+
* right one. Refusing the fast path costs a slower bench and buys a true
|
|
1473
|
+
* number.
|
|
1474
|
+
*/
|
|
1475
|
+
function fastPathRefusalReason(input) {
|
|
1476
|
+
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`;
|
|
1477
|
+
if (input.plane) return `plane '${input.plane}' requested — inferCached has no plane selector; using the runPipeline path instead`;
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* The cached fast path is only for a plain, single-step, default-device,
|
|
1482
|
+
* default-plane inference bench.
|
|
1483
|
+
*/
|
|
1484
|
+
function canUseCachedFastPath(isSingleStep, input) {
|
|
1485
|
+
return isSingleStep && !input.simulatePipeline && fastPathRefusalReason(input) === null;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Build the `pipelineExecutor.runPipeline` argument for one bench call.
|
|
1489
|
+
*
|
|
1490
|
+
* Every optional is ABSENT when unset rather than present-and-undefined: an
|
|
1491
|
+
* omitted `deviceKey` means "the runner's default device" and an omitted
|
|
1492
|
+
* `plane` means the cap's `'full'` default, and the two are not the same as
|
|
1493
|
+
* an explicit `undefined` on a `.loose()` router input.
|
|
1494
|
+
*/
|
|
1495
|
+
function buildBenchRunPipelineInput(req) {
|
|
1496
|
+
return {
|
|
1497
|
+
steps: [...req.steps],
|
|
1498
|
+
frame: req.frame,
|
|
1499
|
+
...req.engine ? { engine: req.engine } : {},
|
|
1500
|
+
...req.deviceKey ? { deviceKey: req.deviceKey } : {},
|
|
1501
|
+
...req.plane ? { plane: req.plane } : {}
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
//#endregion
|
|
1445
1505
|
//#region src/pipeline-runner/capacity/capacity-runner.ts
|
|
1446
1506
|
/**
|
|
1447
1507
|
* Run ONE ramp step: `cameras` independent open-loop pacers for
|
|
@@ -3863,6 +3923,76 @@ var PipelineRunner = class {
|
|
|
3863
3923
|
}
|
|
3864
3924
|
};
|
|
3865
3925
|
//#endregion
|
|
3926
|
+
//#region src/session-decode/live-worker-admission.ts
|
|
3927
|
+
/**
|
|
3928
|
+
* The node's live-decode-worker budget.
|
|
3929
|
+
*
|
|
3930
|
+
* One instance per runner addon. Not a semaphore: a semaphore's contract is
|
|
3931
|
+
* "wait", and waiting is the thing this deliberately does not do — see
|
|
3932
|
+
* {@link DEFAULT_MAX_LIVE_DECODE_WORKERS}.
|
|
3933
|
+
*/
|
|
3934
|
+
var LiveDecodeWorkerAdmission = class {
|
|
3935
|
+
limit;
|
|
3936
|
+
logger;
|
|
3937
|
+
live = 0;
|
|
3938
|
+
constructor(options) {
|
|
3939
|
+
this.limit = Math.max(1, options.limit);
|
|
3940
|
+
this.logger = options.logger;
|
|
3941
|
+
}
|
|
3942
|
+
/** Workers currently holding a slot. */
|
|
3943
|
+
get inFlight() {
|
|
3944
|
+
return this.live;
|
|
3945
|
+
}
|
|
3946
|
+
get ceiling() {
|
|
3947
|
+
return this.limit;
|
|
3948
|
+
}
|
|
3949
|
+
/**
|
|
3950
|
+
* Apply an operator-changed ceiling.
|
|
3951
|
+
*
|
|
3952
|
+
* Lowering it never kills a running session — a decode worker torn down
|
|
3953
|
+
* mid-motion loses the event it was started for. It only stops the next
|
|
3954
|
+
* admission, so the count drains to the new ceiling as sessions end.
|
|
3955
|
+
*/
|
|
3956
|
+
resize(limit) {
|
|
3957
|
+
this.limit = Math.max(1, limit);
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Take a slot, or refuse.
|
|
3961
|
+
*
|
|
3962
|
+
* A refusal is a WARN and carries `tags.deviceId`, because the only useful
|
|
3963
|
+
* form of this question is per-camera ("why did 617 not run when 615 did?").
|
|
3964
|
+
* A branch that drops work silently reads as "it never happened".
|
|
3965
|
+
*/
|
|
3966
|
+
admit(deviceId, label) {
|
|
3967
|
+
if (this.live >= this.limit) {
|
|
3968
|
+
this.logger.warn("session-decode: REFUSED — node live decode-worker ceiling reached, will retry", {
|
|
3969
|
+
tags: { deviceId },
|
|
3970
|
+
meta: {
|
|
3971
|
+
label,
|
|
3972
|
+
live: this.live,
|
|
3973
|
+
ceiling: this.limit
|
|
3974
|
+
}
|
|
3975
|
+
});
|
|
3976
|
+
return null;
|
|
3977
|
+
}
|
|
3978
|
+
this.live += 1;
|
|
3979
|
+
this.logger.debug("session-decode: live decode-worker slot taken", {
|
|
3980
|
+
tags: { deviceId },
|
|
3981
|
+
meta: {
|
|
3982
|
+
label,
|
|
3983
|
+
live: this.live,
|
|
3984
|
+
ceiling: this.limit
|
|
3985
|
+
}
|
|
3986
|
+
});
|
|
3987
|
+
let released = false;
|
|
3988
|
+
return { release: () => {
|
|
3989
|
+
if (released) return;
|
|
3990
|
+
released = true;
|
|
3991
|
+
this.live -= 1;
|
|
3992
|
+
} };
|
|
3993
|
+
}
|
|
3994
|
+
};
|
|
3995
|
+
//#endregion
|
|
3866
3996
|
//#region src/pipeline-runner/stateless-step.ts
|
|
3867
3997
|
function modelServability(step, modelId, format) {
|
|
3868
3998
|
const entry = step.models.find((m) => m.id === modelId);
|
|
@@ -4001,7 +4131,8 @@ var DEFAULT_CONFIG = {
|
|
|
4001
4131
|
maxConcurrentInferences: 16,
|
|
4002
4132
|
targetLoadPercent: 80,
|
|
4003
4133
|
minThrottledFps: 1,
|
|
4004
|
-
maxConcurrentDecodeSpawns: 3
|
|
4134
|
+
maxConcurrentDecodeSpawns: 3,
|
|
4135
|
+
maxLiveDecodeWorkers: 8
|
|
4005
4136
|
};
|
|
4006
4137
|
/**
|
|
4007
4138
|
* Camera attachment record kept by the runner addon. Holds the original
|
|
@@ -4304,6 +4435,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4304
4435
|
*/
|
|
4305
4436
|
spawnGate = null;
|
|
4306
4437
|
/**
|
|
4438
|
+
* Per-node ceiling on decode workers ALIVE at once, sized to
|
|
4439
|
+
* `config.maxLiveDecodeWorkers`. The spawn gate above bounds how fast workers
|
|
4440
|
+
* START; this bounds how many EXIST — measured live at 1 -> 8 workers and a
|
|
4441
|
+
* ~4 GiB move in 8 seconds, with nothing stopping the 9th. `resize()`d live in
|
|
4442
|
+
* `onConfigChanged`. Null until `onInitialize`.
|
|
4443
|
+
*/
|
|
4444
|
+
liveWorkers = null;
|
|
4445
|
+
/**
|
|
4307
4446
|
* Node-level map from a retention-ring `FrameHandle` to the decode worker
|
|
4308
4447
|
* still holding that frame's NATIVE surface — backs the `getNativeCrop` cap
|
|
4309
4448
|
* so post-analysis can cut native-res crops (best-effort; a miss falls back
|
|
@@ -4527,6 +4666,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4527
4666
|
}
|
|
4528
4667
|
});
|
|
4529
4668
|
this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
|
|
4669
|
+
this.liveWorkers = new LiveDecodeWorkerAdmission({
|
|
4670
|
+
limit: this.config.maxLiveDecodeWorkers,
|
|
4671
|
+
logger: this.ctx.logger
|
|
4672
|
+
});
|
|
4530
4673
|
this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
|
|
4531
4674
|
this.runner.onDetectionStreamChange((deviceId, action) => {
|
|
4532
4675
|
this.handleDetectionStreamChange(deviceId, action);
|
|
@@ -4687,8 +4830,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4687
4830
|
children: s.children ?? []
|
|
4688
4831
|
}));
|
|
4689
4832
|
const enabledSteps = stepsToRun.filter((s) => s.enabled);
|
|
4690
|
-
const
|
|
4833
|
+
const isSingleStep = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0);
|
|
4834
|
+
const useFastPath = canUseCachedFastPath(isSingleStep, input);
|
|
4691
4835
|
const rootStep = enabledSteps[0];
|
|
4836
|
+
const fastPathRefusal = isSingleStep ? fastPathRefusalReason(input) : null;
|
|
4837
|
+
if (fastPathRefusal) ctx.logger.info("synthetic bench: cached fast path declined", { meta: {
|
|
4838
|
+
reason: fastPathRefusal,
|
|
4839
|
+
node: benchNode
|
|
4840
|
+
} });
|
|
4692
4841
|
const sharedFrame = {
|
|
4693
4842
|
data: cached.data,
|
|
4694
4843
|
format: cached.format,
|
|
@@ -4708,11 +4857,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4708
4857
|
height: cached.height,
|
|
4709
4858
|
format: cached.format
|
|
4710
4859
|
}, pin)).frameId;
|
|
4711
|
-
await api.pipelineExecutor.runPipeline.mutate({
|
|
4860
|
+
await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
4712
4861
|
steps: stepsToRun,
|
|
4713
4862
|
frame: sharedFrame,
|
|
4714
4863
|
...input.engine ? { engine: input.engine } : {}
|
|
4715
|
-
}, pin);
|
|
4864
|
+
}) }, pin);
|
|
4716
4865
|
const warmupCount = input.warmup ?? 1;
|
|
4717
4866
|
for (let w = 0; w < warmupCount; w++) await api.pipelineExecutor.inferCached.mutate({
|
|
4718
4867
|
stepId: rootStep.addonId,
|
|
@@ -4805,16 +4954,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4805
4954
|
}
|
|
4806
4955
|
ctx.logger.info("synthetic bench: using full runPipeline path", { meta: {
|
|
4807
4956
|
steps: enabledSteps.length,
|
|
4808
|
-
simulatePipeline: !!input.simulatePipeline
|
|
4957
|
+
simulatePipeline: !!input.simulatePipeline,
|
|
4958
|
+
deviceKey: input.deviceKey ?? null,
|
|
4959
|
+
plane: input.plane ?? null
|
|
4809
4960
|
} });
|
|
4810
4961
|
let _callCount = 0;
|
|
4811
4962
|
const callOnce = async () => {
|
|
4812
4963
|
const t0 = performance.now();
|
|
4813
|
-
const result = await api.pipelineExecutor.runPipeline.mutate({
|
|
4964
|
+
const result = await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
4814
4965
|
steps: stepsToRun,
|
|
4815
4966
|
frame: sharedFrame,
|
|
4816
|
-
...input.engine ? { engine: input.engine } : {}
|
|
4817
|
-
|
|
4967
|
+
...input.engine ? { engine: input.engine } : {},
|
|
4968
|
+
...input.deviceKey ? { deviceKey: input.deviceKey } : {},
|
|
4969
|
+
...input.plane ? { plane: input.plane } : {}
|
|
4970
|
+
}) }, pin);
|
|
4818
4971
|
const wallMs = performance.now() - t0;
|
|
4819
4972
|
const n = ++_callCount;
|
|
4820
4973
|
if (n <= 20) ctx.logger.info("bench call trace", { meta: {
|
|
@@ -6191,6 +6344,18 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6191
6344
|
* blocked by a missing gate. `label` distinguishes detect vs motion forks in
|
|
6192
6345
|
* the logs; the device id is already tagged on `log`.
|
|
6193
6346
|
*/
|
|
6347
|
+
/**
|
|
6348
|
+
* The node's live-decode-worker admission, bound to one camera.
|
|
6349
|
+
*
|
|
6350
|
+
* `undefined` when the ceiling is not configured (pre-`onInitialize`), which
|
|
6351
|
+
* `startGuardedSessionDecode` reads as "no ceiling" — the historical
|
|
6352
|
+
* behaviour. Never a queue: see {@link LiveDecodeWorkerAdmission}.
|
|
6353
|
+
*/
|
|
6354
|
+
buildLiveWorkerAdmit(deviceId, label) {
|
|
6355
|
+
const admission = this.liveWorkers;
|
|
6356
|
+
if (!admission) return void 0;
|
|
6357
|
+
return () => admission.admit(deviceId, label);
|
|
6358
|
+
}
|
|
6194
6359
|
buildDecodeSpawnAcquire(log, label) {
|
|
6195
6360
|
return async () => {
|
|
6196
6361
|
const gate = this.spawnGate;
|
|
@@ -6218,6 +6383,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6218
6383
|
const requestedAt = Date.now();
|
|
6219
6384
|
const teardown = startGuardedSessionDecode({
|
|
6220
6385
|
logger: log,
|
|
6386
|
+
admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "detect"),
|
|
6221
6387
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config), true),
|
|
6222
6388
|
startPump: (source) => {
|
|
6223
6389
|
const dialMs = Date.now() - requestedAt;
|
|
@@ -6290,6 +6456,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6290
6456
|
if (!runner) return () => {};
|
|
6291
6457
|
return startGuardedSessionDecode({
|
|
6292
6458
|
logger: log,
|
|
6459
|
+
admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "motion"),
|
|
6293
6460
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
6294
6461
|
startPump: (source) => {
|
|
6295
6462
|
log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
|
|
@@ -6846,6 +7013,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6846
7013
|
}, OCCUPANCY_BURST_SAFETY_TIMEOUT_MS);
|
|
6847
7014
|
teardown = startGuardedSessionDecode({
|
|
6848
7015
|
logger: log,
|
|
7016
|
+
admitWorker: this.buildLiveWorkerAdmit(deviceId, "occupancy"),
|
|
6849
7017
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:occupancy:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
6850
7018
|
startPump: (source) => startSessionDecodePump({
|
|
6851
7019
|
fork: this.forkDecodeWorkerWithLease,
|
|
@@ -7019,6 +7187,17 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
7019
7187
|
step: 1,
|
|
7020
7188
|
default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
|
|
7021
7189
|
showValue: true
|
|
7190
|
+
},
|
|
7191
|
+
{
|
|
7192
|
+
type: "slider",
|
|
7193
|
+
key: "maxLiveDecodeWorkers",
|
|
7194
|
+
label: "Max live decode workers",
|
|
7195
|
+
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.",
|
|
7196
|
+
min: 1,
|
|
7197
|
+
max: 32,
|
|
7198
|
+
step: 1,
|
|
7199
|
+
default: DEFAULT_CONFIG.maxLiveDecodeWorkers,
|
|
7200
|
+
showValue: true
|
|
7022
7201
|
}
|
|
7023
7202
|
]
|
|
7024
7203
|
}] });
|
|
@@ -7026,12 +7205,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
7026
7205
|
async onConfigChanged() {
|
|
7027
7206
|
this.runner?.updateLimits(this.config);
|
|
7028
7207
|
this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
|
|
7208
|
+
this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
|
|
7029
7209
|
this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
|
|
7030
7210
|
maxQueueDepth: this.config.maxQueueDepth,
|
|
7031
7211
|
maxConcurrentInferences: this.config.maxConcurrentInferences,
|
|
7032
7212
|
targetLoadPercent: this.config.targetLoadPercent,
|
|
7033
7213
|
minThrottledFps: this.config.minThrottledFps,
|
|
7034
|
-
maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns
|
|
7214
|
+
maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns,
|
|
7215
|
+
maxLiveDecodeWorkers: this.config.maxLiveDecodeWorkers
|
|
7035
7216
|
} });
|
|
7036
7217
|
}
|
|
7037
7218
|
};
|