@camstack/addon-benchmark 1.2.150 → 1.2.151
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/MaskShapeCanvas-IMozTYd-.mjs +13806 -0
- package/dist/MotionZonesSettings-ZyajfPtv.mjs +310 -0
- package/dist/{PrivacyMaskSettings-Bw2433zS.mjs → PrivacyMaskSettings-Cg8NOBlj.mjs} +9 -9
- package/dist/{SceneMonitorEditor-CEq7UYYx.mjs → SceneMonitorEditor-DWlUo1ZM.mjs} +269 -269
- package/dist/_stub.js +13287 -25722
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-BMIcawh_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-Ca2co8UF.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BE5yZLei.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-wTD-CVC7.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-BxuRwc3x.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-CQ0VjvSn.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react__loadShare__.js-GeuxDuzQ.mjs → _virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react__loadShare__.js-DQXGYpip.mjs} +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Dh0XkFx5.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_2_dom__loadShare__.js-NtWzzhEn.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFJOcZEH.mjs +25 -0
- package/dist/addon-benchmark.css +1 -1
- package/dist/{hostInit-BG79Vuom.mjs → hostInit-gynkOlvE.mjs} +2 -2
- package/dist/index.js +1474 -1860
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1477 -1842
- package/dist/index.mjs.map +1 -1
- package/dist/player-overlays-CbKnZXwO.mjs +44 -0
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-DU_Gek6C.mjs → responsive-CGsBDbD0.mjs} +10 -14
- package/dist/{scene-monitor-copy-DwF-5VWK.mjs → scene-monitor-copy-T3Kfl9jG.mjs} +26 -29
- package/dist/{square-e4QuB7SC.mjs → square-Cj8CvK_2.mjs} +1 -1
- package/dist/{trash-2-CYiIi9m7.mjs → trash-2-nQzUhmWL.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_benchmark_page__remoteEntry_js-C4HwP7ml.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_benchmark_page__remoteEntry_js-CrQL4i7s.mjs} +390 -399
- package/package.json +4 -5
- package/assets/fixtures/camera-sim-1080p.h264 +0 -0
- package/assets/fixtures/camera-sim-1440p-h265.hevc +0 -0
- package/assets/fixtures/camera-sim-360p.h264 +0 -0
- package/dist/MaskShapeCanvas-BDvrLf-K.mjs +0 -686
- package/dist/MotionZonesSettings-Ca7gw3LT.mjs +0 -292
- package/dist/ReactKonva-DXzDAU1q.mjs +0 -5416
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DDbAl4wY.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-lv97RBhq.mjs +0 -89
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-DtWmo0vJ.mjs +0 -58
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-Bh7FS7ML.mjs +0 -62
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-SOU_VnTj.mjs +0 -34
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_2_dom__loadShare__.js-DQaf3kYb.mjs +0 -45
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-DUzuN6aS.mjs +0 -34
- package/dist/grid-2x2-BJ3lFTm4.mjs +0 -21
- package/dist/hls-R1zGmDT3.mjs +0 -18476
- package/dist/lib-CVGFTwe0.mjs +0 -7770
- package/dist/player-overlays-BrlDlS6o.mjs +0 -69
package/dist/index.mjs
CHANGED
|
@@ -5,10 +5,14 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/benchmark-addon.ts
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import {
|
|
9
|
+
BackgroundRunRegistry,
|
|
10
|
+
BaseAddon,
|
|
11
|
+
addonPagesSourceCapability,
|
|
12
|
+
errMsg,
|
|
13
|
+
nodePin,
|
|
14
|
+
parseJsonUnknown
|
|
15
|
+
} from "@camstack/types";
|
|
12
16
|
|
|
13
17
|
// ../../node_modules/zod/v4/classic/external.js
|
|
14
18
|
var external_exports = {};
|
|
@@ -776,10 +780,10 @@ function mergeDefs(...defs) {
|
|
|
776
780
|
function cloneDef(schema) {
|
|
777
781
|
return mergeDefs(schema._zod.def);
|
|
778
782
|
}
|
|
779
|
-
function getElementAtPath(obj,
|
|
780
|
-
if (!
|
|
783
|
+
function getElementAtPath(obj, path) {
|
|
784
|
+
if (!path)
|
|
781
785
|
return obj;
|
|
782
|
-
return
|
|
786
|
+
return path.reduce((acc, key) => acc?.[key], obj);
|
|
783
787
|
}
|
|
784
788
|
function promiseAllObject(promisesObj) {
|
|
785
789
|
const keys = Object.keys(promisesObj);
|
|
@@ -1188,11 +1192,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1188
1192
|
}
|
|
1189
1193
|
return false;
|
|
1190
1194
|
}
|
|
1191
|
-
function prefixIssues(
|
|
1195
|
+
function prefixIssues(path, issues) {
|
|
1192
1196
|
return issues.map((iss) => {
|
|
1193
1197
|
var _a3;
|
|
1194
1198
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1195
|
-
iss.path.unshift(
|
|
1199
|
+
iss.path.unshift(path);
|
|
1196
1200
|
return iss;
|
|
1197
1201
|
});
|
|
1198
1202
|
}
|
|
@@ -1339,16 +1343,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1339
1343
|
}
|
|
1340
1344
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
1341
1345
|
const fieldErrors = { _errors: [] };
|
|
1342
|
-
const processError = (error52,
|
|
1346
|
+
const processError = (error52, path = []) => {
|
|
1343
1347
|
for (const issue2 of error52.issues) {
|
|
1344
1348
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1345
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1349
|
+
issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
|
|
1346
1350
|
} else if (issue2.code === "invalid_key") {
|
|
1347
|
-
processError({ issues: issue2.issues }, [...
|
|
1351
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
1348
1352
|
} else if (issue2.code === "invalid_element") {
|
|
1349
|
-
processError({ issues: issue2.issues }, [...
|
|
1353
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
1350
1354
|
} else {
|
|
1351
|
-
const fullpath = [...
|
|
1355
|
+
const fullpath = [...path, ...issue2.path];
|
|
1352
1356
|
if (fullpath.length === 0) {
|
|
1353
1357
|
fieldErrors._errors.push(mapper(issue2));
|
|
1354
1358
|
} else {
|
|
@@ -1375,17 +1379,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1375
1379
|
}
|
|
1376
1380
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
1377
1381
|
const result = { errors: [] };
|
|
1378
|
-
const processError = (error52,
|
|
1382
|
+
const processError = (error52, path = []) => {
|
|
1379
1383
|
var _a3, _b;
|
|
1380
1384
|
for (const issue2 of error52.issues) {
|
|
1381
1385
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1382
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1386
|
+
issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
|
|
1383
1387
|
} else if (issue2.code === "invalid_key") {
|
|
1384
|
-
processError({ issues: issue2.issues }, [...
|
|
1388
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
1385
1389
|
} else if (issue2.code === "invalid_element") {
|
|
1386
|
-
processError({ issues: issue2.issues }, [...
|
|
1390
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
1387
1391
|
} else {
|
|
1388
|
-
const fullpath = [...
|
|
1392
|
+
const fullpath = [...path, ...issue2.path];
|
|
1389
1393
|
if (fullpath.length === 0) {
|
|
1390
1394
|
result.errors.push(mapper(issue2));
|
|
1391
1395
|
continue;
|
|
@@ -1417,8 +1421,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1417
1421
|
}
|
|
1418
1422
|
function toDotPath(_path) {
|
|
1419
1423
|
const segs = [];
|
|
1420
|
-
const
|
|
1421
|
-
for (const seg of
|
|
1424
|
+
const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1425
|
+
for (const seg of path) {
|
|
1422
1426
|
if (typeof seg === "number")
|
|
1423
1427
|
segs.push(`[${seg}]`);
|
|
1424
1428
|
else if (typeof seg === "symbol")
|
|
@@ -11500,42 +11504,42 @@ function extractDefs(ctx, schema) {
|
|
|
11500
11504
|
if (!root)
|
|
11501
11505
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
11502
11506
|
const idToSchema = /* @__PURE__ */ new Map();
|
|
11503
|
-
for (const
|
|
11504
|
-
const id = ctx.metadataRegistry.get(
|
|
11507
|
+
for (const entry2 of ctx.seen.entries()) {
|
|
11508
|
+
const id = ctx.metadataRegistry.get(entry2[0])?.id;
|
|
11505
11509
|
if (id) {
|
|
11506
11510
|
const existing = idToSchema.get(id);
|
|
11507
|
-
if (existing && existing !==
|
|
11511
|
+
if (existing && existing !== entry2[0]) {
|
|
11508
11512
|
throw new Error(`Duplicate schema id "${id}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);
|
|
11509
11513
|
}
|
|
11510
|
-
idToSchema.set(id,
|
|
11514
|
+
idToSchema.set(id, entry2[0]);
|
|
11511
11515
|
}
|
|
11512
11516
|
}
|
|
11513
|
-
const makeURI = (
|
|
11517
|
+
const makeURI = (entry2) => {
|
|
11514
11518
|
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
11515
11519
|
if (ctx.external) {
|
|
11516
|
-
const externalId = ctx.external.registry.get(
|
|
11520
|
+
const externalId = ctx.external.registry.get(entry2[0])?.id;
|
|
11517
11521
|
const uriGenerator = ctx.external.uri ?? ((id2) => id2);
|
|
11518
11522
|
if (externalId) {
|
|
11519
11523
|
return { ref: uriGenerator(externalId) };
|
|
11520
11524
|
}
|
|
11521
|
-
const id =
|
|
11522
|
-
|
|
11525
|
+
const id = entry2[1].defId ?? entry2[1].schema.id ?? `schema${ctx.counter++}`;
|
|
11526
|
+
entry2[1].defId = id;
|
|
11523
11527
|
return { defId: id, ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` };
|
|
11524
11528
|
}
|
|
11525
|
-
if (
|
|
11529
|
+
if (entry2[1] === root) {
|
|
11526
11530
|
return { ref: "#" };
|
|
11527
11531
|
}
|
|
11528
11532
|
const uriPrefix = `#`;
|
|
11529
11533
|
const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
|
|
11530
|
-
const defId =
|
|
11534
|
+
const defId = entry2[1].schema.id ?? `__schema${ctx.counter++}`;
|
|
11531
11535
|
return { defId, ref: defUriPrefix + defId };
|
|
11532
11536
|
};
|
|
11533
|
-
const extractToDef = (
|
|
11534
|
-
if (
|
|
11537
|
+
const extractToDef = (entry2) => {
|
|
11538
|
+
if (entry2[1].schema.$ref) {
|
|
11535
11539
|
return;
|
|
11536
11540
|
}
|
|
11537
|
-
const seen =
|
|
11538
|
-
const { ref, defId } = makeURI(
|
|
11541
|
+
const seen = entry2[1];
|
|
11542
|
+
const { ref, defId } = makeURI(entry2);
|
|
11539
11543
|
seen.def = { ...seen.schema };
|
|
11540
11544
|
if (defId)
|
|
11541
11545
|
seen.defId = defId;
|
|
@@ -11546,8 +11550,8 @@ function extractDefs(ctx, schema) {
|
|
|
11546
11550
|
schema2.$ref = ref;
|
|
11547
11551
|
};
|
|
11548
11552
|
if (ctx.cycles === "throw") {
|
|
11549
|
-
for (const
|
|
11550
|
-
const seen =
|
|
11553
|
+
for (const entry2 of ctx.seen.entries()) {
|
|
11554
|
+
const seen = entry2[1];
|
|
11551
11555
|
if (seen.cycle) {
|
|
11552
11556
|
throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/<root>
|
|
11553
11557
|
|
|
@@ -11555,31 +11559,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11555
11559
|
}
|
|
11556
11560
|
}
|
|
11557
11561
|
}
|
|
11558
|
-
for (const
|
|
11559
|
-
const seen =
|
|
11560
|
-
if (schema ===
|
|
11561
|
-
extractToDef(
|
|
11562
|
+
for (const entry2 of ctx.seen.entries()) {
|
|
11563
|
+
const seen = entry2[1];
|
|
11564
|
+
if (schema === entry2[0]) {
|
|
11565
|
+
extractToDef(entry2);
|
|
11562
11566
|
continue;
|
|
11563
11567
|
}
|
|
11564
11568
|
if (ctx.external) {
|
|
11565
|
-
const ext = ctx.external.registry.get(
|
|
11566
|
-
if (schema !==
|
|
11567
|
-
extractToDef(
|
|
11569
|
+
const ext = ctx.external.registry.get(entry2[0])?.id;
|
|
11570
|
+
if (schema !== entry2[0] && ext) {
|
|
11571
|
+
extractToDef(entry2);
|
|
11568
11572
|
continue;
|
|
11569
11573
|
}
|
|
11570
11574
|
}
|
|
11571
|
-
const id = ctx.metadataRegistry.get(
|
|
11575
|
+
const id = ctx.metadataRegistry.get(entry2[0])?.id;
|
|
11572
11576
|
if (id) {
|
|
11573
|
-
extractToDef(
|
|
11577
|
+
extractToDef(entry2);
|
|
11574
11578
|
continue;
|
|
11575
11579
|
}
|
|
11576
11580
|
if (seen.cycle) {
|
|
11577
|
-
extractToDef(
|
|
11581
|
+
extractToDef(entry2);
|
|
11578
11582
|
continue;
|
|
11579
11583
|
}
|
|
11580
11584
|
if (seen.count > 1) {
|
|
11581
11585
|
if (ctx.reused === "ref") {
|
|
11582
|
-
extractToDef(
|
|
11586
|
+
extractToDef(entry2);
|
|
11583
11587
|
continue;
|
|
11584
11588
|
}
|
|
11585
11589
|
}
|
|
@@ -11651,8 +11655,8 @@ function finalize(ctx, schema) {
|
|
|
11651
11655
|
path: seen.path ?? []
|
|
11652
11656
|
});
|
|
11653
11657
|
};
|
|
11654
|
-
for (const
|
|
11655
|
-
flattenRef(
|
|
11658
|
+
for (const entry2 of [...ctx.seen.entries()].reverse()) {
|
|
11659
|
+
flattenRef(entry2[0]);
|
|
11656
11660
|
}
|
|
11657
11661
|
const result = {};
|
|
11658
11662
|
if (ctx.target === "draft-2020-12") {
|
|
@@ -11675,8 +11679,8 @@ function finalize(ctx, schema) {
|
|
|
11675
11679
|
if (rootMetaId !== void 0 && result.id === rootMetaId)
|
|
11676
11680
|
delete result.id;
|
|
11677
11681
|
const defs = ctx.external?.defs ?? {};
|
|
11678
|
-
for (const
|
|
11679
|
-
const seen =
|
|
11682
|
+
for (const entry2 of ctx.seen.entries()) {
|
|
11683
|
+
const seen = entry2[1];
|
|
11680
11684
|
if (seen.def && seen.defId) {
|
|
11681
11685
|
if (seen.def.id === seen.defId)
|
|
11682
11686
|
delete seen.def.id;
|
|
@@ -12292,8 +12296,8 @@ function toJSONSchema(input, params) {
|
|
|
12292
12296
|
const registry2 = input;
|
|
12293
12297
|
const ctx2 = initializeContext({ ...params, processors: allProcessors });
|
|
12294
12298
|
const defs = {};
|
|
12295
|
-
for (const
|
|
12296
|
-
const [_, schema] =
|
|
12299
|
+
for (const entry2 of registry2._idmap.entries()) {
|
|
12300
|
+
const [_, schema] = entry2;
|
|
12297
12301
|
process2(schema, ctx2);
|
|
12298
12302
|
}
|
|
12299
12303
|
const schemas = {};
|
|
@@ -12303,8 +12307,8 @@ function toJSONSchema(input, params) {
|
|
|
12303
12307
|
defs
|
|
12304
12308
|
};
|
|
12305
12309
|
ctx2.external = external;
|
|
12306
|
-
for (const
|
|
12307
|
-
const [key, schema] =
|
|
12310
|
+
for (const entry2 of registry2._idmap.entries()) {
|
|
12311
|
+
const [key, schema] = entry2;
|
|
12308
12312
|
extractDefs(ctx2, schema);
|
|
12309
12313
|
schemas[key] = finalize(ctx2, schema);
|
|
12310
12314
|
}
|
|
@@ -14110,13 +14114,13 @@ function resolveRef(ref, ctx) {
|
|
|
14110
14114
|
if (!ref.startsWith("#")) {
|
|
14111
14115
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14112
14116
|
}
|
|
14113
|
-
const
|
|
14114
|
-
if (
|
|
14117
|
+
const path = ref.slice(1).split("/").filter(Boolean);
|
|
14118
|
+
if (path.length === 0) {
|
|
14115
14119
|
return ctx.rootSchema;
|
|
14116
14120
|
}
|
|
14117
14121
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14118
|
-
if (
|
|
14119
|
-
const key =
|
|
14122
|
+
if (path[0] === defsKey) {
|
|
14123
|
+
const key = path[1];
|
|
14120
14124
|
if (!key || !ctx.defs[key]) {
|
|
14121
14125
|
throw new Error(`Reference not found: ${ref}`);
|
|
14122
14126
|
}
|
|
@@ -14528,14 +14532,159 @@ config(en_default());
|
|
|
14528
14532
|
import { customAction, defineCustomActions, PipelineRunResultBridge } from "@camstack/types";
|
|
14529
14533
|
|
|
14530
14534
|
// src/benchmark-schemas.ts
|
|
14531
|
-
import {
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14535
|
+
import { PipelineStepInputSchema as PipelineStepInputSchema2 } from "@camstack/types";
|
|
14536
|
+
|
|
14537
|
+
// src/session-replay/schemas.ts
|
|
14538
|
+
import { PipelineStepInputSchema } from "@camstack/types";
|
|
14539
|
+
var NormBoxSchema = external_exports.object({
|
|
14540
|
+
x: external_exports.number(),
|
|
14541
|
+
y: external_exports.number(),
|
|
14542
|
+
w: external_exports.number(),
|
|
14543
|
+
h: external_exports.number()
|
|
14544
|
+
});
|
|
14545
|
+
var ReplayBoxSchema = external_exports.object({
|
|
14546
|
+
className: external_exports.string(),
|
|
14547
|
+
score: external_exports.number(),
|
|
14548
|
+
box: NormBoxSchema
|
|
14549
|
+
});
|
|
14550
|
+
var StoredPositionSchema = external_exports.object({
|
|
14551
|
+
trackId: external_exports.string(),
|
|
14552
|
+
className: external_exports.string(),
|
|
14553
|
+
t: external_exports.number(),
|
|
14554
|
+
box: NormBoxSchema,
|
|
14555
|
+
score: external_exports.number().nullable()
|
|
14556
|
+
});
|
|
14557
|
+
var ReferenceBasisSchema = external_exports.enum(["exact", "interpolated", "uncertain"]);
|
|
14558
|
+
var ReferenceEntrySchema = StoredPositionSchema.extend({ basis: ReferenceBasisSchema });
|
|
14559
|
+
var TimelineReplayBoxSchema = ReplayBoxSchema.extend({
|
|
14560
|
+
/** `unreferenced`: no stored track was alive at this instant — nothing to contradict. */
|
|
14561
|
+
outcome: external_exports.enum(["matched", "class-changed", "added", "unreferenced"])
|
|
14562
|
+
});
|
|
14563
|
+
var TimelineStoredBoxSchema = ReferenceEntrySchema.extend({
|
|
14564
|
+
outcome: external_exports.enum(["matched", "class-changed", "missed"])
|
|
14565
|
+
});
|
|
14566
|
+
var TimelineFrameSchema = external_exports.object({
|
|
14567
|
+
t: external_exports.number(),
|
|
14568
|
+
/** Some stored track was alive at `t` — the instant HAS a reference. */
|
|
14569
|
+
referenced: external_exports.boolean(),
|
|
14570
|
+
replay: external_exports.array(TimelineReplayBoxSchema).readonly(),
|
|
14571
|
+
stored: external_exports.array(TimelineStoredBoxSchema).readonly(),
|
|
14572
|
+
inferenceMs: external_exports.number().nullable()
|
|
14573
|
+
});
|
|
14574
|
+
var ClassCountsSchema = external_exports.object({
|
|
14575
|
+
replay: external_exports.number(),
|
|
14576
|
+
stored: external_exports.number(),
|
|
14577
|
+
matched: external_exports.number(),
|
|
14578
|
+
added: external_exports.number(),
|
|
14579
|
+
missed: external_exports.number(),
|
|
14580
|
+
unreferenced: external_exports.number(),
|
|
14581
|
+
/** Comparisons (matched / class-changed / missed) made on a non-exact stored box. */
|
|
14582
|
+
interpolated: external_exports.number()
|
|
14583
|
+
});
|
|
14584
|
+
var ReplaySummarySchema = external_exports.object({
|
|
14585
|
+
totals: external_exports.object({
|
|
14586
|
+
matched: external_exports.number(),
|
|
14587
|
+
classChanged: external_exports.number(),
|
|
14588
|
+
added: external_exports.number(),
|
|
14589
|
+
missed: external_exports.number(),
|
|
14590
|
+
unreferenced: external_exports.number(),
|
|
14591
|
+
interpolated: external_exports.number()
|
|
14592
|
+
}),
|
|
14593
|
+
perClass: external_exports.record(external_exports.string(), ClassCountsSchema),
|
|
14594
|
+
scoreDelta: external_exports.object({ mean: external_exports.number(), p10: external_exports.number(), p50: external_exports.number(), p90: external_exports.number() }).nullable(),
|
|
14595
|
+
inferenceMs: external_exports.object({ p50: external_exports.number(), p95: external_exports.number(), count: external_exports.number() }).nullable()
|
|
14596
|
+
});
|
|
14597
|
+
var CadenceSourceSchema = external_exports.enum(["operator", "stored-positions", "default"]);
|
|
14598
|
+
var ReplayCadenceSchema = external_exports.object({ fps: external_exports.number(), source: CadenceSourceSchema });
|
|
14599
|
+
var SessionReplayReportSchema = external_exports.object({
|
|
14600
|
+
deviceId: external_exports.number(),
|
|
14601
|
+
motionEventId: external_exports.number(),
|
|
14602
|
+
window: external_exports.object({
|
|
14603
|
+
startMs: external_exports.number(),
|
|
14604
|
+
endMs: external_exports.number(),
|
|
14605
|
+
motionStartMs: external_exports.number(),
|
|
14606
|
+
motionEndMs: external_exports.number(),
|
|
14607
|
+
/** Where the pre-roll / tail came from: the camera's own settings, or the fleet default. */
|
|
14608
|
+
prerollSource: external_exports.enum(["camera", "default"]),
|
|
14609
|
+
tailSource: external_exports.enum(["camera", "default"])
|
|
14610
|
+
}),
|
|
14611
|
+
pin: external_exports.object({
|
|
14612
|
+
nodeId: external_exports.string(),
|
|
14613
|
+
deviceKey: external_exports.string().nullable(),
|
|
14614
|
+
modelId: external_exports.string(),
|
|
14615
|
+
cameraModelId: external_exports.string().nullable()
|
|
14616
|
+
}),
|
|
14617
|
+
rootStep: PipelineStepInputSchema,
|
|
14618
|
+
cadence: ReplayCadenceSchema,
|
|
14619
|
+
frameSize: external_exports.object({ width: external_exports.number(), source: external_exports.enum(["stored-tracks", "default"]) }),
|
|
14620
|
+
frames: external_exports.object({
|
|
14621
|
+
planned: external_exports.number(),
|
|
14622
|
+
decoded: external_exports.number(),
|
|
14623
|
+
ran: external_exports.number(),
|
|
14624
|
+
failed: external_exports.number(),
|
|
14625
|
+
withoutFootage: external_exports.number(),
|
|
14626
|
+
notDecoded: external_exports.number(),
|
|
14627
|
+
truncated: external_exports.boolean(),
|
|
14628
|
+
truncatedBySafetyCap: external_exports.boolean(),
|
|
14629
|
+
aborted: external_exports.boolean()
|
|
14630
|
+
}),
|
|
14631
|
+
reference: external_exports.object({
|
|
14632
|
+
tracks: external_exports.number(),
|
|
14633
|
+
positionsCompared: external_exports.number(),
|
|
14634
|
+
recoveryExcluded: external_exports.number(),
|
|
14635
|
+
withoutGeometry: external_exports.number(),
|
|
14636
|
+
models: external_exports.record(external_exports.string(), external_exports.number()),
|
|
14637
|
+
/** The stored-track read: how many came back, and whether its limit cut it short. */
|
|
14638
|
+
read: external_exports.object({ tracks: external_exports.number(), truncated: external_exports.boolean() })
|
|
14639
|
+
}),
|
|
14640
|
+
confirmation: external_exports.object({
|
|
14641
|
+
liveVerdicts: external_exports.record(external_exports.string(), external_exports.number()),
|
|
14642
|
+
/** Added or unreferenced detections near a live SUPPRESSED birth of their class. */
|
|
14643
|
+
nearSuppressedBirth: external_exports.number()
|
|
14644
|
+
}),
|
|
14645
|
+
summary: ReplaySummarySchema,
|
|
14646
|
+
timeline: external_exports.array(TimelineFrameSchema).readonly(),
|
|
14647
|
+
failures: external_exports.array(external_exports.object({ t: external_exports.number(), error: external_exports.string() })).readonly()
|
|
14648
|
+
});
|
|
14649
|
+
var SessionReplayRefusalSchema = external_exports.enum([
|
|
14650
|
+
"session-not-found",
|
|
14651
|
+
"session-open",
|
|
14652
|
+
"window-too-long",
|
|
14653
|
+
"no-root-step",
|
|
14654
|
+
"model-unpinned",
|
|
14655
|
+
"model-not-servable",
|
|
14656
|
+
"device-not-servable",
|
|
14657
|
+
"no-recording",
|
|
14658
|
+
"no-footage",
|
|
14659
|
+
"session-length-unknown",
|
|
14660
|
+
"pool-failing"
|
|
14661
|
+
]);
|
|
14662
|
+
var SessionReplayOutcomeSchema = external_exports.discriminatedUnion("kind", [
|
|
14663
|
+
external_exports.object({
|
|
14664
|
+
kind: external_exports.literal("refused"),
|
|
14665
|
+
reason: SessionReplayRefusalSchema,
|
|
14666
|
+
detail: external_exports.string()
|
|
14667
|
+
}),
|
|
14668
|
+
external_exports.object({ kind: external_exports.literal("ran"), report: SessionReplayReportSchema })
|
|
14669
|
+
]);
|
|
14670
|
+
var SessionReplayProgressSchema = external_exports.object({
|
|
14671
|
+
phase: external_exports.enum(["preparing", "replaying", "done"]),
|
|
14672
|
+
framesPlanned: external_exports.number(),
|
|
14673
|
+
framesDone: external_exports.number()
|
|
14674
|
+
});
|
|
14675
|
+
|
|
14676
|
+
// src/benchmark-schemas.ts
|
|
14677
|
+
var BenchModeSchema = external_exports.enum(["inference", "full-tree", "live-frame"]);
|
|
14678
|
+
var BenchPoolTuningSchema = external_exports.object({
|
|
14679
|
+
runtime: external_exports.string().nullable(),
|
|
14680
|
+
numWorkers: external_exports.number().nullable(),
|
|
14681
|
+
concurrency: external_exports.number().nullable(),
|
|
14682
|
+
batchMode: external_exports.string().nullable(),
|
|
14683
|
+
maxBatchSize: external_exports.number().nullable()
|
|
14684
|
+
});
|
|
14685
|
+
var HistoryMeasurementFields = {
|
|
14536
14686
|
stepId: external_exports.string(),
|
|
14537
14687
|
modelId: external_exports.string(),
|
|
14538
|
-
runtime: external_exports.string(),
|
|
14539
14688
|
iterations: external_exports.number(),
|
|
14540
14689
|
warmup: external_exports.number(),
|
|
14541
14690
|
fps: external_exports.number(),
|
|
@@ -14544,377 +14693,163 @@ var PipelineBenchmarkHistoryEntrySchema = external_exports.object({
|
|
|
14544
14693
|
p95Ms: external_exports.number(),
|
|
14545
14694
|
p99Ms: external_exports.number(),
|
|
14546
14695
|
/**
|
|
14547
|
-
* OPTIONAL, and absent on every entry the page writes
|
|
14548
|
-
*
|
|
14549
|
-
*
|
|
14550
|
-
* in the page ever called `saveBenchmarkResult`, so nothing noticed; the
|
|
14551
|
-
* moment it does, a required field can only be satisfied by inventing a
|
|
14552
|
-
* number, and a fabricated latency in the history table is worse than a
|
|
14553
|
-
* blank one.
|
|
14696
|
+
* OPTIONAL, and absent on every entry the page writes: the runner reports
|
|
14697
|
+
* mean / p50 / p95 / p99 and measures no per-call min or max. A fabricated
|
|
14698
|
+
* latency in the history table is worse than a blank one.
|
|
14554
14699
|
*/
|
|
14555
14700
|
minMs: external_exports.number().optional(),
|
|
14556
14701
|
maxMs: external_exports.number().optional(),
|
|
14557
14702
|
avgDetections: external_exports.number(),
|
|
14558
14703
|
referenceImage: external_exports.string().optional(),
|
|
14559
|
-
/**
|
|
14704
|
+
/** Concurrent in-flight requests (simulated cameras) — NOT pool workers. */
|
|
14560
14705
|
parallelRuns: external_exports.number().min(1).default(1)
|
|
14561
|
-
}
|
|
14562
|
-
var
|
|
14563
|
-
kind:
|
|
14564
|
-
id: true,
|
|
14565
|
-
date: true
|
|
14566
|
-
});
|
|
14567
|
-
var DecoderFixtureCodecSchema = external_exports.enum(["h264", "h265"]);
|
|
14568
|
-
var DecoderFixtureSchema = external_exports.object({
|
|
14569
|
-
filename: external_exports.string(),
|
|
14570
|
-
codec: DecoderFixtureCodecSchema,
|
|
14571
|
-
resolutionLabel: external_exports.string(),
|
|
14572
|
-
// '360p', '1080p', '1440p'
|
|
14573
|
-
sizeBytes: external_exports.number(),
|
|
14574
|
-
description: external_exports.string().optional()
|
|
14575
|
-
});
|
|
14576
|
-
var CamProfileSchema = external_exports.enum(["high", "mid", "low"]);
|
|
14577
|
-
var DecoderPerfSourceSchema = external_exports.discriminatedUnion("kind", [
|
|
14578
|
-
external_exports.object({
|
|
14579
|
-
kind: external_exports.literal("sample"),
|
|
14580
|
-
fixtureFilename: external_exports.string()
|
|
14581
|
-
}),
|
|
14582
|
-
external_exports.object({
|
|
14583
|
-
kind: external_exports.literal("stream"),
|
|
14584
|
-
deviceId: external_exports.number().int().min(1),
|
|
14585
|
-
profile: CamProfileSchema
|
|
14586
|
-
})
|
|
14587
|
-
]);
|
|
14588
|
-
var DecoderPerfModeSchema = external_exports.discriminatedUnion("kind", [
|
|
14589
|
-
external_exports.object({ kind: external_exports.literal("duration"), durationSec: external_exports.number().min(5).max(1800) }),
|
|
14590
|
-
external_exports.object({ kind: external_exports.literal("iterations"), iterations: external_exports.number().min(10).max(1e6) })
|
|
14591
|
-
]);
|
|
14592
|
-
var DecoderPerfFormatSchema = external_exports.enum(["jpeg", "gray"]);
|
|
14593
|
-
var HWACCEL_VALUES = HWACCEL_OPTIONS.map((o) => o.value);
|
|
14594
|
-
var DecoderPerfHwaccelSchema = external_exports.enum(HWACCEL_VALUES);
|
|
14595
|
-
var DecoderPerfThresholdsSchema = external_exports.object({
|
|
14596
|
-
warnMBMin: external_exports.number().min(0).default(5),
|
|
14597
|
-
failMBMin: external_exports.number().min(0).default(20)
|
|
14598
|
-
});
|
|
14599
|
-
var DecoderPerfInputSchema = external_exports.object({
|
|
14600
|
-
sessionId: external_exports.string(),
|
|
14601
|
-
cameras: external_exports.number().int().min(1).max(16),
|
|
14602
|
-
mode: DecoderPerfModeSchema,
|
|
14603
|
-
source: DecoderPerfSourceSchema,
|
|
14604
|
-
format: DecoderPerfFormatSchema,
|
|
14605
|
-
fps: external_exports.number().min(1).max(120),
|
|
14606
|
-
toggleMs: external_exports.number().min(0).default(0),
|
|
14607
|
-
warmupSec: external_exports.number().min(0).default(15),
|
|
14608
|
-
sampleIntervalMs: external_exports.number().min(500).default(2e3),
|
|
14609
|
-
hwaccel: DecoderPerfHwaccelSchema.default("none"),
|
|
14610
|
-
thresholds: DecoderPerfThresholdsSchema.default({ warnMBMin: 5, failMBMin: 20 }),
|
|
14611
|
-
// Pin the decoder sessions to the selected node. createSession is the only
|
|
14612
|
-
// decoder call without a node selector (the rest route via the nodeId that
|
|
14613
|
-
// createSession returns), so without this the whole test runs on the
|
|
14614
|
-
// singleton's elected node (the hub) regardless of the UI selection.
|
|
14615
|
-
nodeId: external_exports.string().optional()
|
|
14616
|
-
});
|
|
14617
|
-
var DecoderPerfSampleSchema = external_exports.object({
|
|
14618
|
-
tSec: external_exports.number(),
|
|
14619
|
-
rssMB: external_exports.number(),
|
|
14620
|
-
heapMB: external_exports.number(),
|
|
14621
|
-
externalMB: external_exports.number(),
|
|
14622
|
-
arrayBuffersMB: external_exports.number(),
|
|
14623
|
-
cpuPct: external_exports.number(),
|
|
14624
|
-
framesTotal: external_exports.number()
|
|
14625
|
-
});
|
|
14626
|
-
var DecoderPerfVerdictSchema = external_exports.enum(["pass", "warn", "fail", "aborted", "error"]);
|
|
14627
|
-
var DecoderPerfResultSchema = external_exports.object({
|
|
14628
|
-
sessionId: external_exports.string(),
|
|
14629
|
-
verdict: DecoderPerfVerdictSchema,
|
|
14630
|
-
aborted: external_exports.boolean().default(false),
|
|
14631
|
-
errorMessage: external_exports.string().optional(),
|
|
14632
|
-
samples: external_exports.array(DecoderPerfSampleSchema).readonly(),
|
|
14633
|
-
slopes: external_exports.object({
|
|
14634
|
-
rssMBMin: external_exports.number(),
|
|
14635
|
-
heapMBMin: external_exports.number(),
|
|
14636
|
-
externalMBMin: external_exports.number()
|
|
14637
|
-
}),
|
|
14638
|
-
totals: external_exports.object({
|
|
14639
|
-
wallSec: external_exports.number(),
|
|
14640
|
-
framesEmitted: external_exports.number(),
|
|
14641
|
-
fpsPerCamera: external_exports.number(),
|
|
14642
|
-
cpuAvgPct: external_exports.number(),
|
|
14643
|
-
droppedFrames: external_exports.number()
|
|
14644
|
-
}),
|
|
14645
|
-
config: DecoderPerfInputSchema
|
|
14646
|
-
});
|
|
14647
|
-
var DecoderPerfProgressSchema = external_exports.object({
|
|
14648
|
-
kind: external_exports.literal("decoder-perf"),
|
|
14649
|
-
sessionId: external_exports.string(),
|
|
14650
|
-
phase: external_exports.enum(["init", "warmup", "running", "complete", "aborted", "error"]),
|
|
14651
|
-
tSec: external_exports.number().optional(),
|
|
14652
|
-
sample: DecoderPerfSampleSchema.optional(),
|
|
14653
|
-
message: external_exports.string().optional()
|
|
14654
|
-
});
|
|
14655
|
-
var DecoderPerfHistoryEntrySchema = external_exports.object({
|
|
14656
|
-
kind: external_exports.literal("decoder-perf"),
|
|
14706
|
+
};
|
|
14707
|
+
var PipelineBenchmarkHistoryEntrySchema = external_exports.object({
|
|
14708
|
+
kind: external_exports.literal("pipeline").default("pipeline"),
|
|
14657
14709
|
id: external_exports.string(),
|
|
14658
14710
|
date: external_exports.string(),
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
// 'duration:60s' | 'iterations:5000'
|
|
14667
|
-
rssSlopeMBMin: external_exports.number(),
|
|
14668
|
-
heapSlopeMBMin: external_exports.number(),
|
|
14669
|
-
externalSlopeMBMin: external_exports.number(),
|
|
14670
|
-
framesEmitted: external_exports.number(),
|
|
14671
|
-
fpsPerCamera: external_exports.number(),
|
|
14672
|
-
cpuAvgPct: external_exports.number(),
|
|
14673
|
-
droppedFrames: external_exports.number(),
|
|
14674
|
-
wallSec: external_exports.number(),
|
|
14675
|
-
hwaccel: DecoderPerfHwaccelSchema
|
|
14711
|
+
...HistoryMeasurementFields,
|
|
14712
|
+
nodeId: external_exports.string().optional(),
|
|
14713
|
+
deviceKey: external_exports.string().nullable().optional(),
|
|
14714
|
+
plane: external_exports.enum(["full", "frame"]).nullable().optional(),
|
|
14715
|
+
mode: BenchModeSchema.optional(),
|
|
14716
|
+
pool: BenchPoolTuningSchema.optional(),
|
|
14717
|
+
runtime: external_exports.string().optional()
|
|
14676
14718
|
});
|
|
14677
|
-
var
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14719
|
+
var PipelineBenchmarkHistoryEntryInputSchema = external_exports.object({
|
|
14720
|
+
...HistoryMeasurementFields,
|
|
14721
|
+
nodeId: external_exports.string(),
|
|
14722
|
+
deviceKey: external_exports.string().nullable(),
|
|
14723
|
+
plane: external_exports.enum(["full", "frame"]).nullable(),
|
|
14724
|
+
mode: BenchModeSchema,
|
|
14725
|
+
pool: BenchPoolTuningSchema
|
|
14681
14726
|
});
|
|
14682
|
-
var HistoryEntrySchema =
|
|
14683
|
-
PipelineBenchmarkHistoryEntrySchema,
|
|
14684
|
-
DecoderPerfHistoryEntrySchema
|
|
14685
|
-
]);
|
|
14727
|
+
var HistoryEntrySchema = PipelineBenchmarkHistoryEntrySchema;
|
|
14686
14728
|
var PipelineRunInputSchema = external_exports.object({
|
|
14687
|
-
steps: external_exports.array(
|
|
14729
|
+
steps: external_exports.array(PipelineStepInputSchema2).readonly(),
|
|
14688
14730
|
imageBase64: external_exports.string().optional(),
|
|
14689
14731
|
referenceImage: external_exports.string().optional(),
|
|
14690
|
-
|
|
14691
|
-
/**
|
|
14692
|
-
* Target node for the pipeline test. Forwarded to
|
|
14693
|
-
* `pipelineExecutor.runPipeline`. Omit to run on the hub.
|
|
14694
|
-
*/
|
|
14732
|
+
/** Target node. Forwarded to `pipelineExecutor.runPipeline`. Omit to run on the hub. */
|
|
14695
14733
|
nodeId: external_exports.string().optional(),
|
|
14696
|
-
/**
|
|
14697
|
-
* UI-provided correlation id. When set, every `pipeline.progress`
|
|
14698
|
-
* event emitted by the run is tagged with this value so the caller
|
|
14699
|
-
* can filter out concurrent camera-runtime dispatches that share the
|
|
14700
|
-
* same bus category.
|
|
14701
|
-
*/
|
|
14734
|
+
/** UI correlation id stamped on every `pipeline.progress` line of the run. */
|
|
14702
14735
|
sessionId: external_exports.string().optional(),
|
|
14703
|
-
engine: external_exports.object({
|
|
14704
|
-
runtime: external_exports.enum(["python", "node"]),
|
|
14705
|
-
backend: external_exports.string(),
|
|
14706
|
-
format: external_exports.enum(["onnx", "coreml", "openvino", "tflite"]),
|
|
14707
|
-
device: external_exports.string().optional()
|
|
14708
|
-
}).optional(),
|
|
14709
14736
|
deviceKey: external_exports.string().optional(),
|
|
14710
14737
|
plane: external_exports.enum(["full", "frame"]).optional()
|
|
14711
14738
|
});
|
|
14712
|
-
var
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
runtime: external_exports.enum(["python", "node"]),
|
|
14721
|
-
backend: external_exports.string(),
|
|
14722
|
-
format: external_exports.enum(["onnx", "coreml", "openvino", "tflite"]),
|
|
14723
|
-
device: external_exports.string().optional()
|
|
14724
|
-
}).optional()
|
|
14739
|
+
var SyntheticBenchPoolSchema = external_exports.object({
|
|
14740
|
+
nodeId: external_exports.string(),
|
|
14741
|
+
deviceKey: external_exports.string().nullable(),
|
|
14742
|
+
runtime: external_exports.string().nullable(),
|
|
14743
|
+
numWorkers: external_exports.number().nullable(),
|
|
14744
|
+
concurrency: external_exports.number().nullable(),
|
|
14745
|
+
batchMode: external_exports.string().nullable(),
|
|
14746
|
+
maxBatchSize: external_exports.number().nullable()
|
|
14725
14747
|
});
|
|
14726
|
-
var
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14748
|
+
var SyntheticBenchStatsSchema = external_exports.object({
|
|
14749
|
+
runs: external_exports.number(),
|
|
14750
|
+
wallSec: external_exports.number(),
|
|
14751
|
+
fps: external_exports.number(),
|
|
14752
|
+
detectionsPerSec: external_exports.number(),
|
|
14753
|
+
avgDetections: external_exports.number(),
|
|
14754
|
+
callMs: external_exports.object({ mean: external_exports.number(), p50: external_exports.number(), p95: external_exports.number(), p99: external_exports.number() }),
|
|
14755
|
+
inferMs: external_exports.number(),
|
|
14756
|
+
preprocessMs: external_exports.number(),
|
|
14757
|
+
predictMs: external_exports.number(),
|
|
14758
|
+
batchSizeMean: external_exports.number(),
|
|
14759
|
+
batchSizeMax: external_exports.number(),
|
|
14760
|
+
pool: SyntheticBenchPoolSchema,
|
|
14761
|
+
path: external_exports.string()
|
|
14730
14762
|
});
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14763
|
+
var SyntheticBenchStatusSchema = external_exports.object({
|
|
14764
|
+
sessionId: external_exports.string(),
|
|
14765
|
+
state: external_exports.enum(["running", "done", "failed", "aborted"]),
|
|
14766
|
+
startedAtMs: external_exports.number(),
|
|
14767
|
+
finishedAtMs: external_exports.number().nullable(),
|
|
14768
|
+
progress: external_exports.object({ runs: external_exports.number(), totalRuns: external_exports.number() }).nullable(),
|
|
14769
|
+
result: SyntheticBenchStatsSchema.nullable(),
|
|
14770
|
+
error: external_exports.string().nullable()
|
|
14771
|
+
}).nullable();
|
|
14772
|
+
var MotionSessionSchema = external_exports.object({
|
|
14773
|
+
motionEventId: external_exports.number(),
|
|
14774
|
+
deviceId: external_exports.number(),
|
|
14775
|
+
startMs: external_exports.number(),
|
|
14776
|
+
/** `null` while the session is still open. */
|
|
14777
|
+
durationMs: external_exports.number().nullable(),
|
|
14778
|
+
open: external_exports.boolean(),
|
|
14779
|
+
/** Why a replay of this session would be refused (`session-open`, `session-length-unknown`, …); `null` = replayable. */
|
|
14780
|
+
refusal: external_exports.string().nullable(),
|
|
14781
|
+
/** Stored tracks born inside the session window (slim read). */
|
|
14782
|
+
storedTracks: external_exports.number()
|
|
14744
14783
|
});
|
|
14745
|
-
var
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
callMsP50: external_exports.number(),
|
|
14752
|
-
callMsP95: external_exports.number(),
|
|
14753
|
-
callMsP99: external_exports.number(),
|
|
14754
|
-
inferMsMean: external_exports.number(),
|
|
14755
|
-
errors: external_exports.number()
|
|
14784
|
+
var ListMotionSessionsInputSchema = external_exports.object({
|
|
14785
|
+
deviceId: external_exports.number(),
|
|
14786
|
+
/** Default: the last 24 h. */
|
|
14787
|
+
sinceMs: external_exports.number().optional(),
|
|
14788
|
+
untilMs: external_exports.number().optional(),
|
|
14789
|
+
limit: external_exports.number().int().min(1).max(200).default(50)
|
|
14756
14790
|
});
|
|
14757
|
-
var
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
engine: SustainedEngineSchema,
|
|
14791
|
+
var SESSION_REPLAY_MAX_FRAMES = 1200;
|
|
14792
|
+
var StartSessionReplayInputSchema = external_exports.object({
|
|
14793
|
+
deviceId: external_exports.number(),
|
|
14794
|
+
motionEventId: external_exports.number(),
|
|
14795
|
+
/** The session's start (`getMotionEvents.timestamp`) — the lookup is windowed on it. */
|
|
14796
|
+
motionStartMs: external_exports.number(),
|
|
14797
|
+
nodeId: external_exports.string(),
|
|
14798
|
+
/** Absent ⇒ the node-default pool. */
|
|
14766
14799
|
deviceKey: external_exports.string().optional(),
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
imageBase64: external_exports.string().optional(),
|
|
14773
|
-
durationSec: external_exports.number().min(1).max(3600),
|
|
14774
|
-
batchSize: external_exports.number().int().min(1).max(255),
|
|
14775
|
-
concurrency: external_exports.number().int().min(1).max(64),
|
|
14776
|
-
targetSize: external_exports.number().int().min(64).max(2048).optional(),
|
|
14777
|
-
sessionId: external_exports.string(),
|
|
14778
|
-
nodes: external_exports.array(SustainedNodeTargetSchema).min(1).readonly()
|
|
14800
|
+
/** Absent ⇒ the model the camera's resolved tree names. */
|
|
14801
|
+
modelId: external_exports.string().optional(),
|
|
14802
|
+
/** Absent ⇒ measured from the stored positions (the live cadence). */
|
|
14803
|
+
fps: external_exports.number().min(1).max(30).optional(),
|
|
14804
|
+
maxFrames: external_exports.number().int().min(1).max(SESSION_REPLAY_MAX_FRAMES).default(600)
|
|
14779
14805
|
});
|
|
14780
|
-
var
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
p99: external_exports.number(),
|
|
14784
|
-
mean: external_exports.number(),
|
|
14785
|
-
min: external_exports.number(),
|
|
14786
|
-
max: external_exports.number()
|
|
14806
|
+
var StartSessionReplayAcceptedSchema = external_exports.object({
|
|
14807
|
+
accepted: external_exports.literal(true),
|
|
14808
|
+
runId: external_exports.string()
|
|
14787
14809
|
});
|
|
14788
|
-
var
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
errors: external_exports.number(),
|
|
14805
|
-
/** True when this node ended via the stop button rather than the deadline. */
|
|
14806
|
-
aborted: external_exports.boolean()
|
|
14810
|
+
var SessionReplayKeySchema = external_exports.object({ runId: external_exports.string() });
|
|
14811
|
+
var SessionReplayStatusSchema = external_exports.object({
|
|
14812
|
+
runId: external_exports.string(),
|
|
14813
|
+
state: external_exports.enum(["running", "done", "failed", "aborted"]),
|
|
14814
|
+
startedAtMs: external_exports.number(),
|
|
14815
|
+
finishedAtMs: external_exports.number().nullable(),
|
|
14816
|
+
progress: SessionReplayProgressSchema.nullable(),
|
|
14817
|
+
outcome: SessionReplayOutcomeSchema.nullable(),
|
|
14818
|
+
error: external_exports.string().nullable()
|
|
14819
|
+
}).nullable();
|
|
14820
|
+
var StoredSessionReplaySummarySchema = external_exports.object({
|
|
14821
|
+
id: external_exports.string(),
|
|
14822
|
+
deviceId: external_exports.number(),
|
|
14823
|
+
nodeId: external_exports.string(),
|
|
14824
|
+
createdAtMs: external_exports.number(),
|
|
14825
|
+
report: SessionReplayReportSchema.omit({ timeline: true })
|
|
14807
14826
|
});
|
|
14808
|
-
var
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14827
|
+
var ListSessionReplaysInputSchema = external_exports.object({ deviceId: external_exports.number() });
|
|
14828
|
+
var ListSessionReplaysResultSchema = external_exports.object({
|
|
14829
|
+
runs: external_exports.array(StoredSessionReplaySummarySchema).readonly(),
|
|
14830
|
+
/** Rows that did not read as a session replay (the removed clip replays). */
|
|
14831
|
+
skipped: external_exports.number()
|
|
14813
14832
|
});
|
|
14814
|
-
var
|
|
14815
|
-
var
|
|
14816
|
-
|
|
14817
|
-
var StoredReplayRunSchema = external_exports.custom();
|
|
14818
|
-
var ReplayDiffSchema = external_exports.custom();
|
|
14819
|
-
var RunReplayClipInputSchema = external_exports.object({
|
|
14833
|
+
var GetSessionReplayInputSchema = external_exports.object({ id: external_exports.string() });
|
|
14834
|
+
var StoredSessionReplaySchema = external_exports.object({
|
|
14835
|
+
id: external_exports.string(),
|
|
14820
14836
|
deviceId: external_exports.number(),
|
|
14821
14837
|
nodeId: external_exports.string(),
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
fps: external_exports.number().positive(),
|
|
14825
|
-
maxFrames: external_exports.number().int().positive(),
|
|
14826
|
-
maxWidth: external_exports.number().int().positive().default(1920),
|
|
14827
|
-
deviceKey: external_exports.string().optional()
|
|
14828
|
-
});
|
|
14829
|
-
var UploadReplayClipInputSchema = external_exports.object({
|
|
14830
|
-
dataBase64: external_exports.string(),
|
|
14831
|
-
durationMs: external_exports.number().positive()
|
|
14838
|
+
createdAtMs: external_exports.number(),
|
|
14839
|
+
report: SessionReplayReportSchema
|
|
14832
14840
|
});
|
|
14833
|
-
var UploadReplayClipResultSchema = external_exports.object({ clipId: external_exports.string() });
|
|
14834
|
-
var ListReplayRunsInputSchema = external_exports.object({ deviceId: external_exports.number() });
|
|
14835
|
-
var DiffReplayRunsInputSchema = external_exports.object({ runIdA: external_exports.string(), runIdB: external_exports.string() });
|
|
14836
14841
|
|
|
14837
14842
|
// src/benchmark-actions.ts
|
|
14838
14843
|
var benchmarkActions = defineCustomActions({
|
|
14839
|
-
// Pipeline
|
|
14844
|
+
// Detection Pipeline — one run.
|
|
14840
14845
|
runPipelineTest: customAction(PipelineRunInputSchema, PipelineRunResultBridge, {
|
|
14841
14846
|
kind: "mutation"
|
|
14842
14847
|
}),
|
|
14843
14848
|
/**
|
|
14844
|
-
*
|
|
14845
|
-
* buffer keyed by a returned `frameId`. Subsequent `runPipelineTest`
|
|
14846
|
-
* calls pass `frameId` instead of `imageBase64` — the addon ships the
|
|
14847
|
-
* cached raw frame to `pipelineExecutor.runPipeline` via the `frame`
|
|
14848
|
-
* field, mirroring the production camera path (stream-broker delivers
|
|
14849
|
-
* decoded RGB to pipeline-runner). Eliminates per-call JPEG decode +
|
|
14850
|
-
* 265KB base64 transfer that was dominating bench transport time.
|
|
14851
|
-
*/
|
|
14852
|
-
cacheBenchFrame: customAction(
|
|
14853
|
-
external_exports.object({ imageBase64: external_exports.string(), ttlSeconds: external_exports.number().int().positive().optional() }),
|
|
14854
|
-
external_exports.object({ frameId: external_exports.string(), width: external_exports.number(), height: external_exports.number(), expiresAt: external_exports.number() }),
|
|
14855
|
-
{ kind: "mutation" }
|
|
14856
|
-
),
|
|
14857
|
-
releaseBenchFrame: customAction(
|
|
14858
|
-
external_exports.object({ frameId: external_exports.string() }),
|
|
14859
|
-
external_exports.object({ released: external_exports.boolean() }),
|
|
14860
|
-
{ kind: "mutation" }
|
|
14861
|
-
),
|
|
14862
|
-
/**
|
|
14863
|
-
* Batched pipeline tester. Decodes the JPEG once into RGB on the
|
|
14864
|
-
* addon, then forwards N FrameInputs to the target node in a single
|
|
14865
|
-
* `pipelineExecutor.runPipelineBatch` cap call. Returns N
|
|
14866
|
-
* `FrameResult`s plus aggregate timing.
|
|
14867
|
-
*
|
|
14868
|
-
* NO UI CALLER — and it is NOT dead. `scripts/bench-pool-ceiling.mts` and
|
|
14869
|
-
* `scripts/bench-pipeline-investigation.mts` drive it directly. Do not
|
|
14870
|
-
* delete it on the strength of an in-package grep; those scripts break
|
|
14871
|
-
* silently and nothing in CI runs them.
|
|
14872
|
-
*/
|
|
14873
|
-
runPipelineBatchTest: customAction(PipelineRunBatchInputSchema, PipelineRunBatchResultSchema, {
|
|
14874
|
-
kind: "mutation"
|
|
14875
|
-
}),
|
|
14876
|
-
/**
|
|
14877
|
-
* Multi-camera stress tester — runs the full multi-camera workload
|
|
14878
|
-
* loop in-process (decode JPEG once, then N parallel "cameras" each
|
|
14879
|
-
* issuing a configurable mix of batched + single cap calls). Returns
|
|
14880
|
-
* only aggregate detection-rate stats. Used to measure the pool's
|
|
14881
|
-
* true throughput ceiling without per-call tRPC overhead.
|
|
14882
|
-
*
|
|
14883
|
-
* NO UI CALLER — and it is NOT dead. `scripts/bench-pool-inprocess.mts`
|
|
14884
|
-
* drives it directly. Same warning as `runPipelineBatchTest` above.
|
|
14885
|
-
*/
|
|
14886
|
-
runMultiCameraStressTest: customAction(
|
|
14887
|
-
MultiCameraStressInputSchema,
|
|
14888
|
-
MultiCameraStressResultSchema,
|
|
14889
|
-
{ kind: "mutation" }
|
|
14890
|
-
),
|
|
14891
|
-
/**
|
|
14892
|
-
* Sustained-throughput ROOT-detection e2e run. Pins ONE frame (decoded
|
|
14893
|
-
* once), then feeds the identical frame to root object-detection for
|
|
14894
|
-
* `durationSec` seconds — driven by BATCHES (`runPipelineBatch`,
|
|
14895
|
-
* `batchSize` frames/call) with `concurrency` calls in flight, fanned out
|
|
14896
|
-
* across `nodes`, each on its resolved runtime/model + root-only steps.
|
|
14897
|
-
* Returns per-node sustained fps / det/s + latency percentiles on the
|
|
14898
|
-
* identical pinned frame. Bounded by N + `abortSustainedThroughput`.
|
|
14899
|
-
*/
|
|
14900
|
-
runSustainedThroughput: customAction(
|
|
14901
|
-
SustainedThroughputInputSchema,
|
|
14902
|
-
SustainedThroughputResultSchema,
|
|
14903
|
-
{ kind: "mutation" }
|
|
14904
|
-
),
|
|
14905
|
-
abortSustainedThroughput: customAction(
|
|
14906
|
-
external_exports.object({ sessionId: external_exports.string() }),
|
|
14907
|
-
external_exports.object({ aborted: external_exports.boolean() }),
|
|
14908
|
-
{ kind: "mutation" }
|
|
14909
|
-
),
|
|
14910
|
-
/**
|
|
14911
|
-
* History (shared across pipeline + decoder-perf entries).
|
|
14849
|
+
* History.
|
|
14912
14850
|
*
|
|
14913
14851
|
* `z.object({})`, never `z.void()`: the `addons.custom` envelope insists
|
|
14914
|
-
* on an `input` object, and a `z.void()` schema REJECTS `{}
|
|
14915
|
-
* action resolved and could never actually be called, not from a settings
|
|
14916
|
-
* button and not from `hub.mjs`. The tabs only got away with it by
|
|
14917
|
-
* sending `input: undefined`.
|
|
14852
|
+
* on an `input` object, and a `z.void()` schema REJECTS `{}`.
|
|
14918
14853
|
*/
|
|
14919
14854
|
listBenchmarkHistory: customAction(external_exports.object({}), external_exports.array(HistoryEntrySchema).readonly()),
|
|
14920
14855
|
saveBenchmarkResult: customAction(
|
|
@@ -14922,11 +14857,6 @@ var benchmarkActions = defineCustomActions({
|
|
|
14922
14857
|
PipelineBenchmarkHistoryEntrySchema,
|
|
14923
14858
|
{ kind: "mutation" }
|
|
14924
14859
|
),
|
|
14925
|
-
saveDecoderPerformanceResult: customAction(
|
|
14926
|
-
DecoderPerfHistoryEntryInputSchema,
|
|
14927
|
-
DecoderPerfHistoryEntrySchema,
|
|
14928
|
-
{ kind: "mutation" }
|
|
14929
|
-
),
|
|
14930
14860
|
deleteBenchmarkResult: customAction(
|
|
14931
14861
|
external_exports.object({ id: external_exports.string() }),
|
|
14932
14862
|
external_exports.object({ success: external_exports.literal(true) }),
|
|
@@ -14935,332 +14865,41 @@ var benchmarkActions = defineCustomActions({
|
|
|
14935
14865
|
clearBenchmarkHistory: customAction(external_exports.object({}), external_exports.object({ success: external_exports.literal(true) }), {
|
|
14936
14866
|
kind: "mutation"
|
|
14937
14867
|
}),
|
|
14938
|
-
//
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
kind: "mutation"
|
|
14943
|
-
}),
|
|
14944
|
-
runReplayClip: customAction(RunReplayClipInputSchema, OrchestrateReplayRunResultSchema, {
|
|
14945
|
-
kind: "mutation"
|
|
14946
|
-
}),
|
|
14947
|
-
listReplayRuns: customAction(
|
|
14948
|
-
ListReplayRunsInputSchema,
|
|
14949
|
-
external_exports.array(StoredReplayRunSchema).readonly()
|
|
14868
|
+
// Session replay (D646) — the stored sessions to pick from, then start / poll / abort.
|
|
14869
|
+
listMotionSessions: customAction(
|
|
14870
|
+
ListMotionSessionsInputSchema,
|
|
14871
|
+
external_exports.array(MotionSessionSchema).readonly()
|
|
14950
14872
|
),
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14873
|
+
startSessionReplay: customAction(
|
|
14874
|
+
StartSessionReplayInputSchema,
|
|
14875
|
+
StartSessionReplayAcceptedSchema,
|
|
14876
|
+
{ kind: "mutation", auth: "admin" }
|
|
14877
|
+
),
|
|
14878
|
+
getSessionReplayStatus: customAction(SessionReplayKeySchema, SessionReplayStatusSchema),
|
|
14879
|
+
abortSessionReplay: customAction(SessionReplayKeySchema, external_exports.object({ aborted: external_exports.boolean() }), {
|
|
14880
|
+
kind: "mutation",
|
|
14881
|
+
auth: "admin"
|
|
14957
14882
|
}),
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
external_exports.object({ aborted: external_exports.boolean() }),
|
|
14961
|
-
{ kind: "mutation" }
|
|
14962
|
-
)
|
|
14883
|
+
listSessionReplays: customAction(ListSessionReplaysInputSchema, ListSessionReplaysResultSchema),
|
|
14884
|
+
getSessionReplay: customAction(GetSessionReplayInputSchema, StoredSessionReplaySchema.nullable())
|
|
14963
14885
|
});
|
|
14964
14886
|
|
|
14965
|
-
// src/
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
"benchmark.progress",
|
|
14980
|
-
{ type: "benchmark", id: "decoder-perf" },
|
|
14981
|
-
{
|
|
14982
|
-
kind: payload.kind,
|
|
14983
|
-
sessionId: payload.sessionId,
|
|
14984
|
-
phase: payload.phase,
|
|
14985
|
-
tSec: payload.tSec,
|
|
14986
|
-
sample: payload.sample ? { ...payload.sample } : void 0,
|
|
14987
|
-
message: payload.message
|
|
14988
|
-
}
|
|
14989
|
-
)
|
|
14990
|
-
);
|
|
14991
|
-
}
|
|
14992
|
-
function slope(xs, ys) {
|
|
14993
|
-
if (xs.length < 2) return 0;
|
|
14994
|
-
const n = xs.length;
|
|
14995
|
-
let sx = 0, sy = 0, sxx = 0, sxy = 0;
|
|
14996
|
-
for (let i = 0; i < n; i++) {
|
|
14997
|
-
sx += xs[i];
|
|
14998
|
-
sy += ys[i];
|
|
14999
|
-
sxx += xs[i] * xs[i];
|
|
15000
|
-
sxy += xs[i] * ys[i];
|
|
15001
|
-
}
|
|
15002
|
-
const denom = n * sxx - sx * sx;
|
|
15003
|
-
return denom === 0 ? 0 : (n * sxy - sx * sy) / denom;
|
|
15004
|
-
}
|
|
15005
|
-
function verdictFor(rssSlopeMBMin, warnMBMin, failMBMin) {
|
|
15006
|
-
if (rssSlopeMBMin >= failMBMin) return "fail";
|
|
15007
|
-
if (rssSlopeMBMin >= warnMBMin) return "warn";
|
|
15008
|
-
return "pass";
|
|
15009
|
-
}
|
|
15010
|
-
function resolveFixtureCodec(filename) {
|
|
15011
|
-
if (filename.endsWith(".hevc") || filename.endsWith(".h265")) return "h265";
|
|
15012
|
-
return "h264";
|
|
15013
|
-
}
|
|
15014
|
-
function readFixtureBytes(fixturesDir, filename) {
|
|
15015
|
-
const full = path.join(fixturesDir, filename);
|
|
15016
|
-
if (!fs.existsSync(full)) {
|
|
15017
|
-
throw new Error(`decoder fixture not found: ${filename}`);
|
|
15018
|
-
}
|
|
15019
|
-
return fs.readFileSync(full);
|
|
15020
|
-
}
|
|
15021
|
-
async function createSessions(api, input, codec2) {
|
|
15022
|
-
const handles = [];
|
|
15023
|
-
const pin = input.nodeId ? nodePin(input.nodeId) : void 0;
|
|
15024
|
-
for (let i = 0; i < input.cameras; i++) {
|
|
15025
|
-
const created = await api.decoder.createSession.query(
|
|
15026
|
-
{
|
|
15027
|
-
codec: codec2,
|
|
15028
|
-
maxFps: input.fps,
|
|
15029
|
-
outputFormat: input.format === "gray" ? "gray" : "jpeg",
|
|
15030
|
-
scale: 1
|
|
15031
|
-
},
|
|
15032
|
-
pin
|
|
15033
|
-
);
|
|
15034
|
-
handles.push({
|
|
15035
|
-
sessionId: created.sessionId,
|
|
15036
|
-
nodeId: created.nodeId,
|
|
15037
|
-
codec: codec2,
|
|
15038
|
-
offsetBytes: 0,
|
|
15039
|
-
framesPushed: 0
|
|
15040
|
-
});
|
|
15041
|
-
}
|
|
15042
|
-
return handles;
|
|
15043
|
-
}
|
|
15044
|
-
async function destroySessions(api, handles, logger) {
|
|
15045
|
-
for (const h of handles) {
|
|
15046
|
-
try {
|
|
15047
|
-
await api.decoder.destroySession.query({ sessionId: h.sessionId, nodeId: h.nodeId });
|
|
15048
|
-
} catch (err) {
|
|
15049
|
-
logger.warn("destroySession failed", {
|
|
15050
|
-
meta: { sessionId: h.sessionId, error: err instanceof Error ? err.message : String(err) }
|
|
15051
|
-
});
|
|
15052
|
-
}
|
|
15053
|
-
}
|
|
15054
|
-
}
|
|
15055
|
-
async function pushChunk(api, handle, fixture) {
|
|
15056
|
-
if (handle.offsetBytes >= fixture.length) handle.offsetBytes = 0;
|
|
15057
|
-
const end = Math.min(handle.offsetBytes + CHUNK_SIZE, fixture.length);
|
|
15058
|
-
const chunk = fixture.subarray(handle.offsetBytes, end);
|
|
15059
|
-
handle.offsetBytes = end;
|
|
15060
|
-
const now = Date.now();
|
|
15061
|
-
await api.decoder.pushPacket.query({
|
|
15062
|
-
sessionId: handle.sessionId,
|
|
15063
|
-
nodeId: handle.nodeId,
|
|
15064
|
-
packet: {
|
|
15065
|
-
type: "video",
|
|
15066
|
-
data: new Uint8Array(chunk),
|
|
15067
|
-
pts: now,
|
|
15068
|
-
dts: now,
|
|
15069
|
-
keyframe: handle.offsetBytes <= CHUNK_SIZE,
|
|
15070
|
-
codec: handle.codec
|
|
15071
|
-
}
|
|
15072
|
-
});
|
|
15073
|
-
handle.framesPushed += 1;
|
|
15074
|
-
}
|
|
15075
|
-
async function sumDroppedFrames(api, handles) {
|
|
15076
|
-
let dropped = 0;
|
|
15077
|
-
for (const h of handles) {
|
|
15078
|
-
try {
|
|
15079
|
-
const stats = await api.decoder.getStats.query({ sessionId: h.sessionId, nodeId: h.nodeId });
|
|
15080
|
-
dropped += stats.droppedFrames;
|
|
15081
|
-
} catch {
|
|
15082
|
-
}
|
|
15083
|
-
}
|
|
15084
|
-
return dropped;
|
|
15085
|
-
}
|
|
15086
|
-
async function runDecoderPerfTest(input, deps) {
|
|
15087
|
-
const { api, eventBus, logger, fixturesDir, abortFlag } = deps;
|
|
15088
|
-
const sessionId = input.sessionId;
|
|
15089
|
-
const samples = [];
|
|
15090
|
-
const startWall = performance.now();
|
|
15091
|
-
emitProgress(eventBus, {
|
|
15092
|
-
kind: "decoder-perf",
|
|
15093
|
-
sessionId,
|
|
15094
|
-
phase: "init",
|
|
15095
|
-
message: "initialising"
|
|
15096
|
-
});
|
|
15097
|
-
if (input.source.kind !== "sample") {
|
|
15098
|
-
const msg = "decoder perf: stream source not yet implemented \u2014 use a sample fixture";
|
|
15099
|
-
emitProgress(eventBus, { kind: "decoder-perf", sessionId, phase: "error", message: msg });
|
|
15100
|
-
return {
|
|
15101
|
-
sessionId,
|
|
15102
|
-
verdict: "error",
|
|
15103
|
-
aborted: false,
|
|
15104
|
-
errorMessage: msg,
|
|
15105
|
-
samples: [],
|
|
15106
|
-
slopes: { rssMBMin: 0, heapMBMin: 0, externalMBMin: 0 },
|
|
15107
|
-
totals: { wallSec: 0, framesEmitted: 0, fpsPerCamera: 0, cpuAvgPct: 0, droppedFrames: 0 },
|
|
15108
|
-
config: input
|
|
15109
|
-
};
|
|
15110
|
-
}
|
|
15111
|
-
const fixtureName = input.source.fixtureFilename;
|
|
15112
|
-
const codec2 = resolveFixtureCodec(fixtureName);
|
|
15113
|
-
const fixture = readFixtureBytes(fixturesDir, fixtureName);
|
|
15114
|
-
try {
|
|
15115
|
-
const active = await api.decoder.listActiveSessions.query();
|
|
15116
|
-
if (active.length > 0) {
|
|
15117
|
-
logger.warn("concurrent decoder sessions detected \u2014 measurement may be contaminated", {
|
|
15118
|
-
meta: { concurrent: active.length }
|
|
15119
|
-
});
|
|
15120
|
-
}
|
|
15121
|
-
} catch {
|
|
15122
|
-
}
|
|
15123
|
-
try {
|
|
15124
|
-
const instances = await api.metricsProvider.listAddonInstances.query();
|
|
15125
|
-
const workerInstances = instances.filter((i) => i.role === "worker");
|
|
15126
|
-
if (workerInstances.length === 0) {
|
|
15127
|
-
logger.info(
|
|
15128
|
-
"decoder perf: no isolated worker detected \u2014 hub-local measurement (process.memoryUsage) mixes decoder + hub workload"
|
|
15129
|
-
);
|
|
15130
|
-
} else {
|
|
15131
|
-
logger.info("decoder perf: worker instances visible", {
|
|
15132
|
-
meta: { workers: workerInstances.map((w) => w.addonId) }
|
|
15133
|
-
});
|
|
15134
|
-
}
|
|
15135
|
-
} catch {
|
|
15136
|
-
}
|
|
15137
|
-
let handles;
|
|
15138
|
-
try {
|
|
15139
|
-
handles = await createSessions(api, input, codec2);
|
|
15140
|
-
} catch (err) {
|
|
15141
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
15142
|
-
emitProgress(eventBus, {
|
|
15143
|
-
kind: "decoder-perf",
|
|
15144
|
-
sessionId,
|
|
15145
|
-
phase: "error",
|
|
15146
|
-
message: `createSession failed: ${msg}`
|
|
15147
|
-
});
|
|
15148
|
-
return {
|
|
15149
|
-
sessionId,
|
|
15150
|
-
verdict: "error",
|
|
15151
|
-
aborted: false,
|
|
15152
|
-
errorMessage: msg,
|
|
15153
|
-
samples: [],
|
|
15154
|
-
slopes: { rssMBMin: 0, heapMBMin: 0, externalMBMin: 0 },
|
|
15155
|
-
totals: { wallSec: 0, framesEmitted: 0, fpsPerCamera: 0, cpuAvgPct: 0, droppedFrames: 0 },
|
|
15156
|
-
config: input
|
|
15157
|
-
};
|
|
14887
|
+
// src/replay/mjpeg-split.ts
|
|
14888
|
+
var SOI = Buffer.from([255, 216]);
|
|
14889
|
+
var EOI = Buffer.from([255, 217]);
|
|
14890
|
+
function splitMjpegStream(bytes) {
|
|
14891
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
14892
|
+
const out = [];
|
|
14893
|
+
let pos = 0;
|
|
14894
|
+
for (; ; ) {
|
|
14895
|
+
const soi = buf.indexOf(SOI, pos);
|
|
14896
|
+
if (soi === -1) break;
|
|
14897
|
+
const eoi = buf.indexOf(EOI, soi + SOI.length);
|
|
14898
|
+
if (eoi === -1) break;
|
|
14899
|
+
out.push(buf.subarray(soi, eoi + EOI.length));
|
|
14900
|
+
pos = eoi + EOI.length;
|
|
15158
14901
|
}
|
|
15159
|
-
|
|
15160
|
-
const warmupMs = input.warmupSec * 1e3;
|
|
15161
|
-
const durationMs = input.mode.kind === "duration" ? input.mode.durationSec * 1e3 : Number.MAX_SAFE_INTEGER;
|
|
15162
|
-
const targetIterations = input.mode.kind === "iterations" ? input.mode.iterations : Number.MAX_SAFE_INTEGER;
|
|
15163
|
-
let cpuSnapshot = cpuUsage();
|
|
15164
|
-
let cpuWallMs = performance.now();
|
|
15165
|
-
const feedTickerStart = performance.now();
|
|
15166
|
-
let nextFeedAt = feedTickerStart;
|
|
15167
|
-
let nextSampleAt = feedTickerStart + warmupMs;
|
|
15168
|
-
let aborted2 = false;
|
|
15169
|
-
emitProgress(eventBus, {
|
|
15170
|
-
kind: "decoder-perf",
|
|
15171
|
-
sessionId,
|
|
15172
|
-
phase: "warmup",
|
|
15173
|
-
tSec: 0,
|
|
15174
|
-
message: `warming up ${input.warmupSec}s`
|
|
15175
|
-
});
|
|
15176
|
-
const loopUntil = feedTickerStart + warmupMs + durationMs;
|
|
15177
|
-
while (performance.now() < loopUntil) {
|
|
15178
|
-
if (abortFlag.aborted) {
|
|
15179
|
-
aborted2 = true;
|
|
15180
|
-
break;
|
|
15181
|
-
}
|
|
15182
|
-
const totalPushed = handles.reduce((s, h) => s + h.framesPushed, 0);
|
|
15183
|
-
if (totalPushed >= targetIterations * handles.length) break;
|
|
15184
|
-
const now = performance.now();
|
|
15185
|
-
if (now >= nextFeedAt) {
|
|
15186
|
-
for (const h of handles) {
|
|
15187
|
-
try {
|
|
15188
|
-
await pushChunk(api, h, fixture);
|
|
15189
|
-
} catch (err) {
|
|
15190
|
-
logger.warn("pushPacket failed", {
|
|
15191
|
-
meta: {
|
|
15192
|
-
sessionId: h.sessionId,
|
|
15193
|
-
error: err instanceof Error ? err.message : String(err)
|
|
15194
|
-
}
|
|
15195
|
-
});
|
|
15196
|
-
}
|
|
15197
|
-
}
|
|
15198
|
-
nextFeedAt += frameIntervalMs;
|
|
15199
|
-
}
|
|
15200
|
-
if (now >= nextSampleAt) {
|
|
15201
|
-
const tSec = (now - feedTickerStart) / 1e3;
|
|
15202
|
-
const mem = memoryUsage();
|
|
15203
|
-
const cpu = cpuUsage(cpuSnapshot);
|
|
15204
|
-
const windowMs = now - cpuWallMs;
|
|
15205
|
-
const cpuPct = windowMs > 0 ? (cpu.user + cpu.system) / 1e3 / windowMs * 100 : 0;
|
|
15206
|
-
cpuSnapshot = cpuUsage();
|
|
15207
|
-
cpuWallMs = now;
|
|
15208
|
-
const sample = {
|
|
15209
|
-
tSec,
|
|
15210
|
-
rssMB: mem.rss / 1048576,
|
|
15211
|
-
heapMB: mem.heapUsed / 1048576,
|
|
15212
|
-
externalMB: mem.external / 1048576,
|
|
15213
|
-
arrayBuffersMB: mem.arrayBuffers / 1048576,
|
|
15214
|
-
cpuPct,
|
|
15215
|
-
framesTotal: handles.reduce((s, h) => s + h.framesPushed, 0)
|
|
15216
|
-
};
|
|
15217
|
-
samples.push(sample);
|
|
15218
|
-
emitProgress(eventBus, {
|
|
15219
|
-
kind: "decoder-perf",
|
|
15220
|
-
sessionId,
|
|
15221
|
-
phase: tSec < 0 ? "warmup" : "running",
|
|
15222
|
-
tSec,
|
|
15223
|
-
sample
|
|
15224
|
-
});
|
|
15225
|
-
nextSampleAt += input.sampleIntervalMs;
|
|
15226
|
-
}
|
|
15227
|
-
const sleepMs = Math.max(0, Math.min(nextFeedAt, nextSampleAt) - performance.now());
|
|
15228
|
-
if (sleepMs > 0) await new Promise((r) => setTimeout(r, sleepMs));
|
|
15229
|
-
}
|
|
15230
|
-
const droppedFrames = await sumDroppedFrames(api, handles);
|
|
15231
|
-
await destroySessions(api, handles, logger);
|
|
15232
|
-
const postWarmup = samples.filter((s) => s.tSec * 1e3 >= warmupMs);
|
|
15233
|
-
const xs = postWarmup.map((s) => s.tSec);
|
|
15234
|
-
const rssYs = postWarmup.map((s) => s.rssMB);
|
|
15235
|
-
const heapYs = postWarmup.map((s) => s.heapMB);
|
|
15236
|
-
const extYs = postWarmup.map((s) => s.externalMB);
|
|
15237
|
-
const rssSlopeMBMin = slope(xs, rssYs) * 60;
|
|
15238
|
-
const heapSlopeMBMin = slope(xs, heapYs) * 60;
|
|
15239
|
-
const externalSlopeMBMin = slope(xs, extYs) * 60;
|
|
15240
|
-
const wallSec = (performance.now() - startWall) / 1e3;
|
|
15241
|
-
const framesEmitted = handles.reduce((s, h) => s + h.framesPushed, 0);
|
|
15242
|
-
const fpsPerCamera = wallSec > 0 ? framesEmitted / Math.max(1, handles.length) / wallSec : 0;
|
|
15243
|
-
const cpuAvgPct = samples.length > 0 ? samples.reduce((s, x) => s + x.cpuPct, 0) / samples.length : 0;
|
|
15244
|
-
const verdict = aborted2 ? "aborted" : verdictFor(rssSlopeMBMin, input.thresholds.warnMBMin, input.thresholds.failMBMin);
|
|
15245
|
-
emitProgress(eventBus, {
|
|
15246
|
-
kind: "decoder-perf",
|
|
15247
|
-
sessionId,
|
|
15248
|
-
phase: aborted2 ? "aborted" : "complete",
|
|
15249
|
-
message: `done \u2014 rss slope ${rssSlopeMBMin.toFixed(2)} MB/min`
|
|
15250
|
-
});
|
|
15251
|
-
return {
|
|
15252
|
-
sessionId,
|
|
15253
|
-
verdict,
|
|
15254
|
-
aborted: aborted2,
|
|
15255
|
-
samples,
|
|
15256
|
-
slopes: {
|
|
15257
|
-
rssMBMin: rssSlopeMBMin,
|
|
15258
|
-
heapMBMin: heapSlopeMBMin,
|
|
15259
|
-
externalMBMin: externalSlopeMBMin
|
|
15260
|
-
},
|
|
15261
|
-
totals: { wallSec, framesEmitted, fpsPerCamera, cpuAvgPct, droppedFrames },
|
|
15262
|
-
config: input
|
|
15263
|
-
};
|
|
14902
|
+
return out;
|
|
15264
14903
|
}
|
|
15265
14904
|
|
|
15266
14905
|
// src/replay/replay-decode.ts
|
|
@@ -15289,13 +14928,13 @@ var REPLAY_DECODE_MAX_CONCURRENT = 2;
|
|
|
15289
14928
|
var REPLAY_DECODE_TIMEOUT_MS = 15e3;
|
|
15290
14929
|
var KILL_GRACE_MS = 500;
|
|
15291
14930
|
var ReplayDecoder = class {
|
|
15292
|
-
|
|
14931
|
+
resolveFfmpegPath;
|
|
15293
14932
|
spawnFn;
|
|
15294
14933
|
maxConcurrent;
|
|
15295
14934
|
timeoutMs;
|
|
15296
14935
|
inFlight = 0;
|
|
15297
14936
|
constructor(deps) {
|
|
15298
|
-
this.
|
|
14937
|
+
this.resolveFfmpegPath = deps.resolveFfmpegPath;
|
|
15299
14938
|
this.spawnFn = deps.spawnFn ?? ((bin, args) => spawn(bin, [...args]));
|
|
15300
14939
|
this.maxConcurrent = deps.maxConcurrent ?? REPLAY_DECODE_MAX_CONCURRENT;
|
|
15301
14940
|
this.timeoutMs = deps.timeoutMs ?? REPLAY_DECODE_TIMEOUT_MS;
|
|
@@ -15312,7 +14951,7 @@ var ReplayDecoder = class {
|
|
|
15312
14951
|
* uploaded clip) are fetched through a capability call, not a local path.
|
|
15313
14952
|
* Resolves with the whole MJPEG stdout stream.
|
|
15314
14953
|
*/
|
|
15315
|
-
async decodeSequence(args, input) {
|
|
14954
|
+
async decodeSequence(args, input, opts = {}) {
|
|
15316
14955
|
if (this.inFlight >= this.maxConcurrent) {
|
|
15317
14956
|
throw new Error(
|
|
15318
14957
|
`replay decode busy: node already running ${this.inFlight}/${this.maxConcurrent} decodes`
|
|
@@ -15320,19 +14959,20 @@ var ReplayDecoder = class {
|
|
|
15320
14959
|
}
|
|
15321
14960
|
this.inFlight += 1;
|
|
15322
14961
|
try {
|
|
15323
|
-
|
|
14962
|
+
const ffmpegPath = await this.resolveFfmpegPath();
|
|
14963
|
+
return await this.runOnce(ffmpegPath, args, input, opts.timeoutMs ?? this.timeoutMs);
|
|
15324
14964
|
} finally {
|
|
15325
14965
|
this.inFlight -= 1;
|
|
15326
14966
|
}
|
|
15327
14967
|
}
|
|
15328
|
-
runOnce(args, input) {
|
|
15329
|
-
return new Promise((
|
|
14968
|
+
runOnce(ffmpegPath, args, input, timeoutMs) {
|
|
14969
|
+
return new Promise((resolve, reject) => {
|
|
15330
14970
|
const chunks = [];
|
|
15331
14971
|
const stderrTail = [];
|
|
15332
14972
|
let settled = false;
|
|
15333
14973
|
const child = (() => {
|
|
15334
14974
|
try {
|
|
15335
|
-
return this.spawnFn(
|
|
14975
|
+
return this.spawnFn(ffmpegPath, args);
|
|
15336
14976
|
} catch (err) {
|
|
15337
14977
|
reject(new Error(`replay decode: ffmpeg spawn failed: ${String(err)}`));
|
|
15338
14978
|
return null;
|
|
@@ -15363,7 +15003,7 @@ var ReplayDecoder = class {
|
|
|
15363
15003
|
const timer = setTimeout(() => {
|
|
15364
15004
|
settle(() => reject(new Error("replay decode: timed out")));
|
|
15365
15005
|
kill();
|
|
15366
|
-
},
|
|
15006
|
+
}, timeoutMs);
|
|
15367
15007
|
timer.unref?.();
|
|
15368
15008
|
child.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
15369
15009
|
child.stderr?.on("data", (data) => {
|
|
@@ -15379,7 +15019,7 @@ var ReplayDecoder = class {
|
|
|
15379
15019
|
child.on("exit", (code, signal) => {
|
|
15380
15020
|
const stdout = Buffer.concat(chunks);
|
|
15381
15021
|
if (code === 0) {
|
|
15382
|
-
settle(() =>
|
|
15022
|
+
settle(() => resolve(stdout));
|
|
15383
15023
|
return;
|
|
15384
15024
|
}
|
|
15385
15025
|
settle(
|
|
@@ -15401,6 +15041,7 @@ var ReplayDecoder = class {
|
|
|
15401
15041
|
|
|
15402
15042
|
// src/replay/replay-store.ts
|
|
15403
15043
|
var REPLAY_RUNS_COLLECTION = "benchmark:replay-runs";
|
|
15044
|
+
var REPLAY_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
15404
15045
|
var REPLAY_RUNS_COLUMNS = [
|
|
15405
15046
|
{ name: "id", type: "TEXT", primaryKey: true, notNull: true },
|
|
15406
15047
|
{ name: "deviceId", type: "INTEGER", notNull: true },
|
|
@@ -15415,7 +15056,14 @@ var REPLAY_RUNS_INDEXES = [
|
|
|
15415
15056
|
{ name: "idx_replay_runs_device_created", columns: ["deviceId", "createdAtMs"] },
|
|
15416
15057
|
{ name: "idx_replay_runs_created", columns: ["createdAtMs"] }
|
|
15417
15058
|
];
|
|
15418
|
-
var
|
|
15059
|
+
var StoredRowSchema = external_exports.object({
|
|
15060
|
+
deviceId: external_exports.number(),
|
|
15061
|
+
nodeId: external_exports.string(),
|
|
15062
|
+
createdAtMs: external_exports.number(),
|
|
15063
|
+
source: external_exports.object({ kind: external_exports.literal("motion-session") }).loose(),
|
|
15064
|
+
verdict: SessionReplayReportSchema
|
|
15065
|
+
});
|
|
15066
|
+
var ReplayStore = class _ReplayStore {
|
|
15419
15067
|
constructor(store) {
|
|
15420
15068
|
this.store = store;
|
|
15421
15069
|
}
|
|
@@ -15430,24 +15078,57 @@ var ReplayStore = class {
|
|
|
15430
15078
|
});
|
|
15431
15079
|
}
|
|
15432
15080
|
async save(run) {
|
|
15433
|
-
const {
|
|
15081
|
+
const { report } = run;
|
|
15434
15082
|
await this.store.insert.mutate({
|
|
15435
15083
|
collection: REPLAY_RUNS_COLLECTION,
|
|
15436
|
-
record: {
|
|
15437
|
-
|
|
15084
|
+
record: {
|
|
15085
|
+
id: run.id,
|
|
15086
|
+
data: {
|
|
15087
|
+
deviceId: run.deviceId,
|
|
15088
|
+
nodeId: run.nodeId,
|
|
15089
|
+
clipId: `session:${report.deviceId}:${report.motionEventId}`,
|
|
15090
|
+
createdAtMs: run.createdAtMs,
|
|
15091
|
+
source: { kind: "motion-session", motionEventId: report.motionEventId, ...report.window },
|
|
15092
|
+
plan: {
|
|
15093
|
+
pin: report.pin,
|
|
15094
|
+
rootStep: report.rootStep,
|
|
15095
|
+
cadence: report.cadence,
|
|
15096
|
+
frameSize: report.frameSize
|
|
15097
|
+
},
|
|
15098
|
+
verdict: report
|
|
15099
|
+
}
|
|
15100
|
+
}
|
|
15438
15101
|
});
|
|
15439
15102
|
}
|
|
15440
|
-
/**
|
|
15103
|
+
/** Session replays newest-first, optionally scoped to one camera. */
|
|
15441
15104
|
async list(query) {
|
|
15442
|
-
const
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15105
|
+
const rows = await this.store.query.query({
|
|
15106
|
+
collection: REPLAY_RUNS_COLLECTION,
|
|
15107
|
+
filter: {
|
|
15108
|
+
orderBy: { field: "createdAtMs", direction: "desc" },
|
|
15109
|
+
...query.deviceId !== void 0 ? { where: { deviceId: query.deviceId } } : {},
|
|
15110
|
+
...query.limit !== void 0 ? { limit: query.limit } : {}
|
|
15111
|
+
}
|
|
15112
|
+
});
|
|
15113
|
+
const runs = [];
|
|
15114
|
+
let skipped = 0;
|
|
15115
|
+
for (const row of rows) {
|
|
15116
|
+
const parsed = _ReplayStore.readRow(row.id, row.data);
|
|
15117
|
+
if (parsed === null) skipped += 1;
|
|
15118
|
+
else runs.push(parsed);
|
|
15119
|
+
}
|
|
15120
|
+
return { runs, skipped };
|
|
15121
|
+
}
|
|
15122
|
+
async get(id) {
|
|
15123
|
+
const rows = await this.store.query.query({
|
|
15124
|
+
collection: REPLAY_RUNS_COLLECTION,
|
|
15125
|
+
filter: { where: { id }, limit: 1 }
|
|
15126
|
+
});
|
|
15127
|
+
const row = rows[0];
|
|
15128
|
+
return row === void 0 ? null : _ReplayStore.readRow(row.id, row.data);
|
|
15447
15129
|
}
|
|
15448
15130
|
/** Delete every run created before `cutoffMs`. This store's OWN retention
|
|
15449
|
-
* clock — see the file header for why that matters.
|
|
15450
|
-
* a caller can log it, the way `OpsLogStore`-adjacent sweeps do. */
|
|
15131
|
+
* clock — see the file header for why that matters. */
|
|
15451
15132
|
async pruneBefore(cutoffMs) {
|
|
15452
15133
|
const { deleted } = await this.store.deleteWhere.mutate({
|
|
15453
15134
|
collection: REPLAY_RUNS_COLLECTION,
|
|
@@ -15455,449 +15136,845 @@ var ReplayStore = class {
|
|
|
15455
15136
|
});
|
|
15456
15137
|
return deleted;
|
|
15457
15138
|
}
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
function readPreflight(result) {
|
|
15462
|
-
if (!result.ok) {
|
|
15463
|
-
const detail = result.issues.map((i) => `${i.addonId}: ${i.detail}`).join("; ");
|
|
15464
|
-
return { kind: "refused", reason: "step-unrunnable", detail };
|
|
15465
|
-
}
|
|
15466
|
-
if (result.substitutions.length > 0) {
|
|
15467
|
-
const detail = result.substitutions.map((s) => `${s.addonId}: chose \`${s.chosen}\`, running \`${s.running}\` (${s.format})`).join("; ");
|
|
15468
|
-
return { kind: "refused", reason: "model-substituted", detail };
|
|
15469
|
-
}
|
|
15470
|
-
return { kind: "runnable", format: result.format };
|
|
15471
|
-
}
|
|
15472
|
-
var PACKAGE_DETECTION_STEP_ID = "package-detection";
|
|
15473
|
-
function applyPackageZoneOverlay(steps, overlay) {
|
|
15474
|
-
if (overlay.packageDetection === void 0) return steps;
|
|
15475
|
-
const existing = steps.find((s) => s.addonId === PACKAGE_DETECTION_STEP_ID);
|
|
15476
|
-
if (!overlay.packageDetection) {
|
|
15477
|
-
if (!existing) return steps;
|
|
15478
|
-
return steps.filter((s) => s.addonId !== PACKAGE_DETECTION_STEP_ID);
|
|
15479
|
-
}
|
|
15480
|
-
if (existing) {
|
|
15481
|
-
if (existing.enabled === true) return steps;
|
|
15482
|
-
return steps.map((s) => s.addonId === PACKAGE_DETECTION_STEP_ID ? { ...s, enabled: true } : s);
|
|
15483
|
-
}
|
|
15484
|
-
const node = {
|
|
15485
|
-
addonId: PACKAGE_DETECTION_STEP_ID,
|
|
15486
|
-
enabled: true,
|
|
15487
|
-
children: []
|
|
15488
|
-
};
|
|
15489
|
-
return [...steps, node];
|
|
15490
|
-
}
|
|
15491
|
-
function applySettingsAndModels(steps, overlay, ignored) {
|
|
15492
|
-
const settingsKeys = new Set(Object.keys(overlay.stepSettings ?? {}));
|
|
15493
|
-
const modelKeys = new Set(Object.keys(overlay.stepModels ?? {}));
|
|
15494
|
-
const presentIds = new Set(steps.map((s) => s.addonId));
|
|
15495
|
-
for (const id of settingsKeys) if (!presentIds.has(id)) ignored.add(id);
|
|
15496
|
-
for (const id of modelKeys) if (!presentIds.has(id)) ignored.add(id);
|
|
15497
|
-
return steps.map((s) => {
|
|
15498
|
-
const settingsPatch = overlay.stepSettings?.[s.addonId];
|
|
15499
|
-
const modelOverride = overlay.stepModels?.[s.addonId];
|
|
15500
|
-
if (settingsPatch === void 0 && modelOverride === void 0) return s;
|
|
15139
|
+
static readRow(id, data) {
|
|
15140
|
+
const parsed = StoredRowSchema.safeParse(data);
|
|
15141
|
+
if (!parsed.success) return null;
|
|
15501
15142
|
return {
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15143
|
+
id,
|
|
15144
|
+
deviceId: parsed.data.deviceId,
|
|
15145
|
+
nodeId: parsed.data.nodeId,
|
|
15146
|
+
createdAtMs: parsed.data.createdAtMs,
|
|
15147
|
+
report: parsed.data.verdict
|
|
15505
15148
|
};
|
|
15506
|
-
});
|
|
15507
|
-
}
|
|
15508
|
-
function buildReplayPlan(base, overlay) {
|
|
15509
|
-
const ignored = /* @__PURE__ */ new Set();
|
|
15510
|
-
const withPackageZone = applyPackageZoneOverlay(base.steps, overlay);
|
|
15511
|
-
const steps = applySettingsAndModels(withPackageZone, overlay, ignored);
|
|
15512
|
-
return { steps, ignoredOverlays: [...ignored] };
|
|
15513
|
-
}
|
|
15514
|
-
|
|
15515
|
-
// src/replay/replay-source.ts
|
|
15516
|
-
function planReplayFrames(source, fps, maxFrames) {
|
|
15517
|
-
const durationMs = source.kind === "recording" ? source.toMs - source.fromMs : source.durationMs;
|
|
15518
|
-
if (durationMs <= 0) return { kind: "refused", reason: "empty-window" };
|
|
15519
|
-
const stepMs = 1e3 / fps;
|
|
15520
|
-
const startAt = source.kind === "recording" ? source.fromMs : 0;
|
|
15521
|
-
const rawCount = Math.max(1, Math.floor(durationMs / stepMs));
|
|
15522
|
-
const truncated = rawCount > maxFrames;
|
|
15523
|
-
const count = Math.min(rawCount, maxFrames);
|
|
15524
|
-
const instants = [];
|
|
15525
|
-
for (let i = 0; i < count; i++) instants.push(startAt + Math.round(i * stepMs));
|
|
15526
|
-
return { kind: "planned", deviceId: source.deviceId, instants, truncated };
|
|
15527
|
-
}
|
|
15528
|
-
|
|
15529
|
-
// src/replay/mjpeg-split.ts
|
|
15530
|
-
var SOI = Buffer.from([255, 216]);
|
|
15531
|
-
var EOI = Buffer.from([255, 217]);
|
|
15532
|
-
function splitMjpegStream(bytes) {
|
|
15533
|
-
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
15534
|
-
const out = [];
|
|
15535
|
-
let pos = 0;
|
|
15536
|
-
for (; ; ) {
|
|
15537
|
-
const soi = buf.indexOf(SOI, pos);
|
|
15538
|
-
if (soi === -1) break;
|
|
15539
|
-
const eoi = buf.indexOf(EOI, soi + SOI.length);
|
|
15540
|
-
if (eoi === -1) break;
|
|
15541
|
-
out.push(buf.subarray(soi, eoi + EOI.length));
|
|
15542
|
-
pos = eoi + EOI.length;
|
|
15543
15149
|
}
|
|
15544
|
-
|
|
15545
|
-
}
|
|
15150
|
+
};
|
|
15546
15151
|
|
|
15547
|
-
// src/replay/
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
let framesFailed = 0;
|
|
15566
|
-
let stoppedOnBudget = false;
|
|
15567
|
-
for (let i = 0; i < frames.length; i++) {
|
|
15568
|
-
if (now() - startedAt >= budgetMs) {
|
|
15569
|
-
stoppedOnBudget = true;
|
|
15570
|
-
deps.logger.warn("replay: run stopped on budget \u2014 a partial census, reported as such", {
|
|
15571
|
-
tags,
|
|
15572
|
-
meta: {
|
|
15573
|
-
framesPlanned: frames.length,
|
|
15574
|
-
framesRan: succeeded.length,
|
|
15575
|
-
framesFailed,
|
|
15576
|
-
budgetMs
|
|
15577
|
-
}
|
|
15578
|
-
});
|
|
15579
|
-
break;
|
|
15152
|
+
// src/session-replay/recording-reader.ts
|
|
15153
|
+
async function readRecordingForInstants(deps, instants, stepMs) {
|
|
15154
|
+
const parts = [];
|
|
15155
|
+
let withoutFootage = 0;
|
|
15156
|
+
let truncated = false;
|
|
15157
|
+
let i = 0;
|
|
15158
|
+
while (i < instants.length) {
|
|
15159
|
+
const t = instants[i];
|
|
15160
|
+
if (t === void 0) break;
|
|
15161
|
+
const located = await deps.locateSegment(t);
|
|
15162
|
+
if (located.kind !== "segment") {
|
|
15163
|
+
const edge = located.nearestEdgeMs;
|
|
15164
|
+
let j2 = i + 1;
|
|
15165
|
+
if (edge === null) j2 = instants.length;
|
|
15166
|
+
else if (edge > t) while (j2 < instants.length && (instants[j2] ?? edge) < edge) j2++;
|
|
15167
|
+
withoutFootage += j2 - i;
|
|
15168
|
+
i = j2;
|
|
15169
|
+
continue;
|
|
15580
15170
|
}
|
|
15581
|
-
const
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
});
|
|
15171
|
+
const segEnd = located.startMs + located.durMs;
|
|
15172
|
+
let j = i;
|
|
15173
|
+
while (j < instants.length && (instants[j] ?? segEnd) < segEnd) j++;
|
|
15174
|
+
const covered = instants.slice(i, j);
|
|
15175
|
+
const lastCovered = covered[covered.length - 1] ?? t;
|
|
15176
|
+
const window = await deps.readWindowBytes({
|
|
15177
|
+
startMs: located.startMs,
|
|
15178
|
+
fromMs: t,
|
|
15179
|
+
toMs: Math.min(segEnd, lastCovered + Math.round(stepMs))
|
|
15180
|
+
});
|
|
15181
|
+
if (window.kind === "ok") {
|
|
15182
|
+
if (!window.reachesRequestedEnd) truncated = true;
|
|
15183
|
+
parts.push({ bytes: window.data, gopStartMs: window.gopStartMs, instants: covered });
|
|
15184
|
+
} else {
|
|
15185
|
+
withoutFootage += covered.length;
|
|
15597
15186
|
}
|
|
15598
|
-
|
|
15187
|
+
i = j === i ? i + 1 : j;
|
|
15188
|
+
}
|
|
15189
|
+
if (parts.length === 0) {
|
|
15190
|
+
return {
|
|
15191
|
+
kind: "refused",
|
|
15192
|
+
reason: "no-footage",
|
|
15193
|
+
detail: `no recording covers any of the ${instants.length} planned instants`
|
|
15194
|
+
};
|
|
15599
15195
|
}
|
|
15600
|
-
const tracks = succeeded.length > 0 ? (await deps.runTracking(succeeded)).tracks : [];
|
|
15601
|
-
const complete = !stoppedOnBudget && framesFailed === 0;
|
|
15602
|
-
deps.logger.info("replay: run finished", {
|
|
15603
|
-
tags,
|
|
15604
|
-
meta: {
|
|
15605
|
-
framesPlanned: frames.length,
|
|
15606
|
-
framesRan: succeeded.length,
|
|
15607
|
-
framesFailed,
|
|
15608
|
-
complete,
|
|
15609
|
-
tracks: tracks.length
|
|
15610
|
-
}
|
|
15611
|
-
});
|
|
15612
15196
|
return {
|
|
15613
|
-
kind:
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
tracks,
|
|
15618
|
-
discarded,
|
|
15619
|
-
counters: { framesPlanned: frames.length, framesRan: succeeded.length, framesFailed }
|
|
15197
|
+
kind: "ok",
|
|
15198
|
+
parts,
|
|
15199
|
+
instantsWithoutFootage: withoutFootage,
|
|
15200
|
+
truncatedBySafetyCap: truncated
|
|
15620
15201
|
};
|
|
15621
15202
|
}
|
|
15622
15203
|
|
|
15623
|
-
// src/replay/
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
if (
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15204
|
+
// src/session-replay/cadence.ts
|
|
15205
|
+
var DEFAULT_DETECTION_FPS = 10;
|
|
15206
|
+
var MIN_DETECTION_FPS = 1;
|
|
15207
|
+
var MAX_DETECTION_FPS = 30;
|
|
15208
|
+
var MIN_GAPS_TO_MEASURE = 3;
|
|
15209
|
+
function clampFps(fps) {
|
|
15210
|
+
return Math.min(MAX_DETECTION_FPS, Math.max(MIN_DETECTION_FPS, fps));
|
|
15211
|
+
}
|
|
15212
|
+
function resolveReplayCadence(requestedFps, positionTimesByTrack) {
|
|
15213
|
+
if (requestedFps !== void 0) return { fps: clampFps(requestedFps), source: "operator" };
|
|
15214
|
+
const gaps = [];
|
|
15215
|
+
for (const times of positionTimesByTrack) {
|
|
15216
|
+
for (let i = 1; i < times.length; i++) {
|
|
15217
|
+
const prev = times[i - 1];
|
|
15218
|
+
const cur = times[i];
|
|
15219
|
+
if (prev !== void 0 && cur !== void 0 && cur > prev) gaps.push(cur - prev);
|
|
15220
|
+
}
|
|
15221
|
+
}
|
|
15222
|
+
if (gaps.length < MIN_GAPS_TO_MEASURE) return { fps: DEFAULT_DETECTION_FPS, source: "default" };
|
|
15223
|
+
const sorted = gaps.toSorted((a, b) => a - b);
|
|
15224
|
+
const quartile = sorted[Math.floor((sorted.length - 1) / 4)] ?? 0;
|
|
15225
|
+
if (!(quartile > 0)) return { fps: DEFAULT_DETECTION_FPS, source: "default" };
|
|
15226
|
+
return { fps: clampFps(Math.round(1e3 / quartile)), source: "stored-positions" };
|
|
15227
|
+
}
|
|
15228
|
+
|
|
15229
|
+
// src/session-replay/compare.ts
|
|
15230
|
+
var MATCH_IOU_MIN = 0.5;
|
|
15231
|
+
var RELAXED_IOU_MIN = 0.3;
|
|
15232
|
+
var MAX_INTERPOLATION_GAP_MS = 5e3;
|
|
15233
|
+
function iou(a, b) {
|
|
15234
|
+
const x1 = Math.max(a.x, b.x);
|
|
15235
|
+
const y1 = Math.max(a.y, b.y);
|
|
15236
|
+
const x2 = Math.min(a.x + a.w, b.x + b.w);
|
|
15237
|
+
const y2 = Math.min(a.y + a.h, b.y + b.h);
|
|
15238
|
+
const inter = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
|
|
15239
|
+
if (inter === 0) return 0;
|
|
15240
|
+
const union2 = a.w * a.h + b.w * b.h - inter;
|
|
15241
|
+
return union2 > 0 ? inter / union2 : 0;
|
|
15242
|
+
}
|
|
15243
|
+
function lerp(a, b, f) {
|
|
15244
|
+
return a + (b - a) * f;
|
|
15245
|
+
}
|
|
15246
|
+
function entry(p, t, basis) {
|
|
15247
|
+
return { ...p, t, basis };
|
|
15248
|
+
}
|
|
15249
|
+
function trackAt(track, t, halfStepMs) {
|
|
15250
|
+
let prev;
|
|
15251
|
+
let next;
|
|
15252
|
+
let nearest;
|
|
15253
|
+
for (const p of track.positions) {
|
|
15254
|
+
if (p.t <= t) prev = p;
|
|
15255
|
+
if (p.t >= t && next === void 0) next = p;
|
|
15256
|
+
if (nearest === void 0 || Math.abs(p.t - t) < Math.abs(nearest.t - t)) nearest = p;
|
|
15647
15257
|
}
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
deps.logger.warn("replay: run refused \u2014 empty window", { tags });
|
|
15651
|
-
return { kind: "refused", reason: framePlan.reason };
|
|
15258
|
+
if (nearest !== void 0 && Math.abs(nearest.t - t) <= halfStepMs) {
|
|
15259
|
+
return entry(nearest, nearest.t, "exact");
|
|
15652
15260
|
}
|
|
15653
|
-
if (
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15261
|
+
if (t < track.firstSeen || t > track.lastSeen || nearest === void 0) return null;
|
|
15262
|
+
if (prev !== void 0 && next !== void 0 && next.t - prev.t <= MAX_INTERPOLATION_GAP_MS) {
|
|
15263
|
+
const f = next.t === prev.t ? 0 : (t - prev.t) / (next.t - prev.t);
|
|
15264
|
+
return {
|
|
15265
|
+
trackId: track.trackId,
|
|
15266
|
+
className: track.className,
|
|
15267
|
+
t,
|
|
15268
|
+
box: {
|
|
15269
|
+
x: lerp(prev.box.x, next.box.x, f),
|
|
15270
|
+
y: lerp(prev.box.y, next.box.y, f),
|
|
15271
|
+
w: lerp(prev.box.w, next.box.w, f),
|
|
15272
|
+
h: lerp(prev.box.h, next.box.h, f)
|
|
15273
|
+
},
|
|
15274
|
+
score: prev.score !== null && next.score !== null ? lerp(prev.score, next.score, f) : null,
|
|
15275
|
+
basis: "interpolated"
|
|
15276
|
+
};
|
|
15658
15277
|
}
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15278
|
+
return entry(nearest, t, "uncertain");
|
|
15279
|
+
}
|
|
15280
|
+
function referenceAt(tracks, t, halfStepMs) {
|
|
15281
|
+
const entries = [];
|
|
15282
|
+
let live = false;
|
|
15283
|
+
for (const track of tracks) {
|
|
15284
|
+
if (t >= track.firstSeen && t <= track.lastSeen) live = true;
|
|
15285
|
+
const e = trackAt(track, t, halfStepMs);
|
|
15286
|
+
if (e !== null) {
|
|
15287
|
+
entries.push(e);
|
|
15288
|
+
live = true;
|
|
15289
|
+
}
|
|
15290
|
+
}
|
|
15291
|
+
return { live, entries };
|
|
15292
|
+
}
|
|
15293
|
+
function sameClass(a, b) {
|
|
15294
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
15295
|
+
}
|
|
15296
|
+
function barFor(basis, iouMin) {
|
|
15297
|
+
if (basis === "exact") return iouMin;
|
|
15298
|
+
if (basis === "interpolated") return Math.min(iouMin, RELAXED_IOU_MIN);
|
|
15299
|
+
return 0;
|
|
15300
|
+
}
|
|
15301
|
+
function pairUp(replay, stored, accept, bar) {
|
|
15302
|
+
const candidates = [];
|
|
15303
|
+
replay.forEach((r, ri) => {
|
|
15304
|
+
stored.forEach((s, si) => {
|
|
15305
|
+
if (!accept(r, s)) return;
|
|
15306
|
+
const v = iou(r.box, s.box);
|
|
15307
|
+
if (v >= bar(s)) candidates.push({ r: ri, s: si, iou: v });
|
|
15664
15308
|
});
|
|
15665
|
-
return { kind: "refused", reason: sourceFetch.reason };
|
|
15666
|
-
}
|
|
15667
|
-
const sourceBytes = sourceFetch.bytes;
|
|
15668
|
-
const first = framePlan.instants[0] ?? 0;
|
|
15669
|
-
const last = framePlan.instants[framePlan.instants.length - 1] ?? first;
|
|
15670
|
-
const startSec = (first - sourceFetch.startMs) / 1e3;
|
|
15671
|
-
const durationSec = Math.max(1 / input.fps, (last - first) / 1e3 + 1 / input.fps);
|
|
15672
|
-
const args = buildReplaySequenceArgs({
|
|
15673
|
-
startSec,
|
|
15674
|
-
durationSec,
|
|
15675
|
-
fps: input.fps,
|
|
15676
|
-
maxWidth: input.maxWidth
|
|
15677
15309
|
});
|
|
15678
|
-
const
|
|
15679
|
-
const
|
|
15680
|
-
const
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15310
|
+
const ranked = candidates.toSorted((a, b) => b.iou - a.iou);
|
|
15311
|
+
const usedR = /* @__PURE__ */ new Set();
|
|
15312
|
+
const usedS = /* @__PURE__ */ new Set();
|
|
15313
|
+
const pairs = [];
|
|
15314
|
+
for (const c of ranked) {
|
|
15315
|
+
if (usedR.has(c.r) || usedS.has(c.s)) continue;
|
|
15316
|
+
const r = replay[c.r];
|
|
15317
|
+
const s = stored[c.s];
|
|
15318
|
+
if (r === void 0 || s === void 0) continue;
|
|
15319
|
+
usedR.add(c.r);
|
|
15320
|
+
usedS.add(c.s);
|
|
15321
|
+
pairs.push({
|
|
15322
|
+
replay: r,
|
|
15323
|
+
stored: s,
|
|
15324
|
+
iou: c.iou,
|
|
15325
|
+
scoreDelta: s.score === null ? null : r.score - s.score
|
|
15691
15326
|
});
|
|
15692
15327
|
}
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
runTracking: async (tracked) => deps.api.pipelineAnalytics.runReplayFrameProcessor.mutate({
|
|
15712
|
-
deviceId: input.deviceId,
|
|
15713
|
-
source: "pipeline",
|
|
15714
|
-
frames: [...tracked]
|
|
15715
|
-
}),
|
|
15716
|
-
logger: deps.logger
|
|
15717
|
-
},
|
|
15718
|
-
frames
|
|
15328
|
+
return {
|
|
15329
|
+
pairs,
|
|
15330
|
+
restReplay: replay.filter((_r, i) => !usedR.has(i)),
|
|
15331
|
+
restStored: stored.filter((_s, i) => !usedS.has(i))
|
|
15332
|
+
};
|
|
15333
|
+
}
|
|
15334
|
+
function compareFrame(t, replay, reference, iouMin = MATCH_IOU_MIN) {
|
|
15335
|
+
const same = pairUp(
|
|
15336
|
+
replay,
|
|
15337
|
+
reference.entries,
|
|
15338
|
+
(r, s) => sameClass(r.className, s.className),
|
|
15339
|
+
(s) => barFor(s.basis, iouMin)
|
|
15340
|
+
);
|
|
15341
|
+
const changed = pairUp(
|
|
15342
|
+
same.restReplay,
|
|
15343
|
+
same.restStored,
|
|
15344
|
+
(_r, s) => s.basis !== "uncertain",
|
|
15345
|
+
(s) => barFor(s.basis, iouMin)
|
|
15719
15346
|
);
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
15347
|
+
return {
|
|
15348
|
+
t,
|
|
15349
|
+
referenced: reference.live,
|
|
15350
|
+
matched: same.pairs,
|
|
15351
|
+
classChanged: changed.pairs,
|
|
15352
|
+
added: reference.live ? changed.restReplay : [],
|
|
15353
|
+
unreferenced: reference.live ? [] : changed.restReplay,
|
|
15354
|
+
missed: changed.restStored
|
|
15355
|
+
};
|
|
15356
|
+
}
|
|
15357
|
+
function percentile(sorted, q) {
|
|
15358
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(q * sorted.length) - 1));
|
|
15359
|
+
return sorted[idx] ?? 0;
|
|
15360
|
+
}
|
|
15361
|
+
function summarizeReplay(frames, inferenceMs) {
|
|
15362
|
+
const perClass = /* @__PURE__ */ new Map();
|
|
15363
|
+
const bump = (cls, field) => {
|
|
15364
|
+
const key = cls.trim().toLowerCase();
|
|
15365
|
+
const e = perClass.get(key) ?? {
|
|
15366
|
+
replay: 0,
|
|
15367
|
+
stored: 0,
|
|
15368
|
+
matched: 0,
|
|
15369
|
+
added: 0,
|
|
15370
|
+
missed: 0,
|
|
15371
|
+
unreferenced: 0,
|
|
15372
|
+
interpolated: 0
|
|
15373
|
+
};
|
|
15374
|
+
e[field] += 1;
|
|
15375
|
+
perClass.set(key, e);
|
|
15376
|
+
};
|
|
15377
|
+
const deltas = [];
|
|
15378
|
+
let matched = 0;
|
|
15379
|
+
let classChanged = 0;
|
|
15380
|
+
let added = 0;
|
|
15381
|
+
let missed = 0;
|
|
15382
|
+
let unreferenced = 0;
|
|
15383
|
+
let interpolated = 0;
|
|
15384
|
+
const noteBasis = (s) => {
|
|
15385
|
+
if (s.basis === "exact") return;
|
|
15386
|
+
interpolated += 1;
|
|
15387
|
+
bump(s.className, "interpolated");
|
|
15388
|
+
};
|
|
15389
|
+
for (const f of frames) {
|
|
15390
|
+
for (const p of f.matched) {
|
|
15391
|
+
matched += 1;
|
|
15392
|
+
bump(p.replay.className, "replay");
|
|
15393
|
+
bump(p.stored.className, "stored");
|
|
15394
|
+
bump(p.replay.className, "matched");
|
|
15395
|
+
noteBasis(p.stored);
|
|
15396
|
+
if (p.scoreDelta !== null) deltas.push(p.scoreDelta);
|
|
15397
|
+
}
|
|
15398
|
+
for (const p of f.classChanged) {
|
|
15399
|
+
classChanged += 1;
|
|
15400
|
+
bump(p.replay.className, "replay");
|
|
15401
|
+
bump(p.stored.className, "stored");
|
|
15402
|
+
noteBasis(p.stored);
|
|
15403
|
+
}
|
|
15404
|
+
for (const r of f.added) {
|
|
15405
|
+
added += 1;
|
|
15406
|
+
bump(r.className, "replay");
|
|
15407
|
+
bump(r.className, "added");
|
|
15408
|
+
}
|
|
15409
|
+
for (const r of f.unreferenced) {
|
|
15410
|
+
unreferenced += 1;
|
|
15411
|
+
bump(r.className, "replay");
|
|
15412
|
+
bump(r.className, "unreferenced");
|
|
15413
|
+
}
|
|
15414
|
+
for (const s of f.missed) {
|
|
15415
|
+
missed += 1;
|
|
15416
|
+
bump(s.className, "stored");
|
|
15417
|
+
bump(s.className, "missed");
|
|
15418
|
+
noteBasis(s);
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
const sortedDeltas = deltas.toSorted((a, b) => a - b);
|
|
15422
|
+
const sortedInfer = [...inferenceMs].toSorted((a, b) => a - b);
|
|
15423
|
+
return {
|
|
15424
|
+
totals: { matched, classChanged, added, missed, unreferenced, interpolated },
|
|
15425
|
+
perClass: Object.fromEntries(
|
|
15426
|
+
[...perClass.entries()].toSorted(([a], [b]) => a.localeCompare(b))
|
|
15427
|
+
),
|
|
15428
|
+
scoreDelta: sortedDeltas.length === 0 ? null : {
|
|
15429
|
+
mean: sortedDeltas.reduce((s, v) => s + v, 0) / sortedDeltas.length,
|
|
15430
|
+
p10: percentile(sortedDeltas, 0.1),
|
|
15431
|
+
p50: percentile(sortedDeltas, 0.5),
|
|
15432
|
+
p90: percentile(sortedDeltas, 0.9)
|
|
15433
|
+
},
|
|
15434
|
+
inferenceMs: sortedInfer.length === 0 ? null : {
|
|
15435
|
+
p50: percentile(sortedInfer, 0.5),
|
|
15436
|
+
p95: percentile(sortedInfer, 0.95),
|
|
15437
|
+
count: sortedInfer.length
|
|
15438
|
+
}
|
|
15439
|
+
};
|
|
15735
15440
|
}
|
|
15736
15441
|
|
|
15737
|
-
// src/replay/
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
const
|
|
15741
|
-
const
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
}
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
const
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
let bestIdx = -1;
|
|
15759
|
-
let bestIou = 0;
|
|
15760
|
-
for (let i = 0; i < b.length; i++) {
|
|
15761
|
-
if (bMatched.has(i)) continue;
|
|
15762
|
-
const tb = b[i];
|
|
15763
|
-
if (tb === void 0 || tb.className !== ta.className) continue;
|
|
15764
|
-
if (!windowsOverlap(ta, tb)) continue;
|
|
15765
|
-
const score = iou(ta.bbox, tb.bbox);
|
|
15766
|
-
if (score >= TRACK_PAIR_MIN_IOU && score > bestIou) {
|
|
15767
|
-
bestIou = score;
|
|
15768
|
-
bestIdx = i;
|
|
15769
|
-
}
|
|
15770
|
-
}
|
|
15771
|
-
if (bestIdx === -1) lost.push(ta);
|
|
15772
|
-
else bMatched.add(bestIdx);
|
|
15773
|
-
}
|
|
15774
|
-
const gained = b.filter((_, i) => !bMatched.has(i));
|
|
15775
|
-
return { gained, lost };
|
|
15776
|
-
}
|
|
15777
|
-
function diffKnobs(a, b) {
|
|
15778
|
-
const stepIds = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
15779
|
-
const changed = [];
|
|
15780
|
-
for (const stepId of stepIds) {
|
|
15781
|
-
const aStep = a[stepId] ?? {};
|
|
15782
|
-
const bStep = b[stepId] ?? {};
|
|
15783
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(aStep), ...Object.keys(bStep)]);
|
|
15784
|
-
for (const key of keys) {
|
|
15785
|
-
const from = aStep[key];
|
|
15786
|
-
const to = bStep[key];
|
|
15787
|
-
if (from !== to) changed.push({ stepId, key, from, to });
|
|
15788
|
-
}
|
|
15789
|
-
}
|
|
15790
|
-
return changed;
|
|
15791
|
-
}
|
|
15792
|
-
function diffPromoted(discardedA, tracksB) {
|
|
15793
|
-
const promoted = [];
|
|
15794
|
-
for (const d of discardedA) {
|
|
15795
|
-
const nowTracked = tracksB.some((t) => t.className === d.macroClass);
|
|
15796
|
-
if (nowTracked)
|
|
15797
|
-
promoted.push({ macroClass: d.macroClass, score: d.score, wasDiscardedBy: d.reason });
|
|
15798
|
-
}
|
|
15799
|
-
return promoted;
|
|
15800
|
-
}
|
|
15801
|
-
function diffReplayRuns(a, b) {
|
|
15802
|
-
if (a.clipId !== b.clipId) return { kind: "incomparable", reason: "different-input" };
|
|
15803
|
-
if (a.nodeId !== b.nodeId) return { kind: "incomparable", reason: "different-node" };
|
|
15804
|
-
return {
|
|
15805
|
-
kind: "compared",
|
|
15806
|
-
changedKnobs: diffKnobs(a.settings, b.settings),
|
|
15807
|
-
tracks: diffTracks(a.tracks, b.tracks),
|
|
15808
|
-
promoted: diffPromoted(a.discarded, b.tracks)
|
|
15442
|
+
// src/session-replay/frame-plan.ts
|
|
15443
|
+
var REPLAY_CHUNK_MS = 1e4;
|
|
15444
|
+
function planReplayFrames(window, fps, maxFrames, chunkMs = REPLAY_CHUNK_MS) {
|
|
15445
|
+
const stepMs = 1e3 / fps;
|
|
15446
|
+
const instants = [];
|
|
15447
|
+
let truncated = false;
|
|
15448
|
+
for (let k = 0; ; k++) {
|
|
15449
|
+
const t = window.startMs + Math.round(k * stepMs);
|
|
15450
|
+
if (t >= window.endMs) break;
|
|
15451
|
+
if (instants.length >= maxFrames) {
|
|
15452
|
+
truncated = true;
|
|
15453
|
+
break;
|
|
15454
|
+
}
|
|
15455
|
+
instants.push(t);
|
|
15456
|
+
}
|
|
15457
|
+
const chunks = [];
|
|
15458
|
+
let current = [];
|
|
15459
|
+
let chunkStart = window.startMs;
|
|
15460
|
+
const flush = (toMs) => {
|
|
15461
|
+
if (current.length > 0) chunks.push({ fromMs: chunkStart, toMs, instants: current });
|
|
15462
|
+
current = [];
|
|
15809
15463
|
};
|
|
15464
|
+
for (const t of instants) {
|
|
15465
|
+
while (t >= chunkStart + chunkMs) {
|
|
15466
|
+
flush(chunkStart + chunkMs);
|
|
15467
|
+
chunkStart += chunkMs;
|
|
15468
|
+
}
|
|
15469
|
+
current.push(t);
|
|
15470
|
+
}
|
|
15471
|
+
const last = instants[instants.length - 1];
|
|
15472
|
+
flush(
|
|
15473
|
+
truncated && last !== void 0 ? last + Math.round(stepMs) : Math.min(window.endMs, chunkStart + chunkMs)
|
|
15474
|
+
);
|
|
15475
|
+
return { chunks, planned: instants.length, truncated };
|
|
15810
15476
|
}
|
|
15811
15477
|
|
|
15812
|
-
// src/
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
const
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
const
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15478
|
+
// src/session-replay/replay-steps.ts
|
|
15479
|
+
var REPLAY_ROOT_STEP = "object-detection";
|
|
15480
|
+
function selectRootReplayStep(tree, requestedModelId) {
|
|
15481
|
+
const root = tree.steps.find((s) => s.addonId === REPLAY_ROOT_STEP);
|
|
15482
|
+
if (root === void 0 || !root.enabled) {
|
|
15483
|
+
return {
|
|
15484
|
+
kind: "refused",
|
|
15485
|
+
reason: "no-root-step",
|
|
15486
|
+
detail: `the camera does not run ${REPLAY_ROOT_STEP} as an enabled root step`
|
|
15487
|
+
};
|
|
15488
|
+
}
|
|
15489
|
+
const cameraModelId = root.modelId ?? null;
|
|
15490
|
+
const modelId = requestedModelId ?? cameraModelId;
|
|
15491
|
+
if (modelId === null || modelId.length === 0) {
|
|
15492
|
+
return {
|
|
15493
|
+
kind: "refused",
|
|
15494
|
+
reason: "model-unpinned",
|
|
15495
|
+
detail: `the camera's ${REPLAY_ROOT_STEP} names no model (it runs the node's format default); pick the model to replay explicitly`
|
|
15496
|
+
};
|
|
15497
|
+
}
|
|
15498
|
+
return {
|
|
15499
|
+
kind: "ok",
|
|
15500
|
+
cameraModelId,
|
|
15501
|
+
step: {
|
|
15502
|
+
addonId: root.addonId,
|
|
15503
|
+
modelId,
|
|
15504
|
+
enabled: true,
|
|
15505
|
+
...root.settings !== void 0 ? { settings: root.settings } : {},
|
|
15506
|
+
children: []
|
|
15839
15507
|
}
|
|
15840
15508
|
};
|
|
15841
|
-
|
|
15842
|
-
|
|
15509
|
+
}
|
|
15510
|
+
|
|
15511
|
+
// src/session-replay/session-window.ts
|
|
15512
|
+
var SESSION_PREROLL_MS = 5e3;
|
|
15513
|
+
var SESSION_TAIL_MS = 3e4;
|
|
15514
|
+
var SESSION_MAX_WINDOW_MS = 10 * 6e4;
|
|
15515
|
+
function motionSessionWindow(session, camera = {}, opts = {}) {
|
|
15516
|
+
if (session.durationMs === null) {
|
|
15517
|
+
return {
|
|
15518
|
+
kind: "refused",
|
|
15519
|
+
reason: "session-open",
|
|
15520
|
+
detail: `motion session ${session.id} is still open \u2014 its end is not known yet`
|
|
15521
|
+
};
|
|
15522
|
+
}
|
|
15523
|
+
if (session.durationMs === void 0) {
|
|
15524
|
+
return {
|
|
15525
|
+
kind: "refused",
|
|
15526
|
+
reason: "session-length-unknown",
|
|
15527
|
+
detail: `motion session ${session.id} predates the episode duration column \u2014 its length is unknown`
|
|
15528
|
+
};
|
|
15529
|
+
}
|
|
15530
|
+
const motionEndMs = session.timestamp + session.durationMs;
|
|
15531
|
+
const prerollSource = camera.preRollInferenceEnabled === false ? "camera" : "default";
|
|
15532
|
+
const prerollMs = camera.preRollInferenceEnabled === false ? 0 : SESSION_PREROLL_MS;
|
|
15533
|
+
const tailSource = camera.motionCooldownMs !== void 0 ? "camera" : "default";
|
|
15534
|
+
const tailMs = camera.motionCooldownMs ?? SESSION_TAIL_MS;
|
|
15535
|
+
const startMs = session.timestamp - prerollMs;
|
|
15536
|
+
const endMs = motionEndMs + tailMs;
|
|
15537
|
+
const maxWindowMs = opts.maxWindowMs ?? SESSION_MAX_WINDOW_MS;
|
|
15538
|
+
if (endMs - startMs > maxWindowMs) {
|
|
15539
|
+
return {
|
|
15540
|
+
kind: "refused",
|
|
15541
|
+
reason: "window-too-long",
|
|
15542
|
+
detail: `motion session ${session.id} spans ${Math.round((endMs - startMs) / 1e3)} s \u2014 longer than the ${Math.round(maxWindowMs / 1e3)} s a replay reads`
|
|
15543
|
+
};
|
|
15544
|
+
}
|
|
15843
15545
|
return {
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15546
|
+
kind: "window",
|
|
15547
|
+
motionEventId: session.id,
|
|
15548
|
+
motionStartMs: session.timestamp,
|
|
15549
|
+
motionEndMs,
|
|
15550
|
+
startMs,
|
|
15551
|
+
endMs,
|
|
15552
|
+
prerollSource,
|
|
15553
|
+
tailSource
|
|
15852
15554
|
};
|
|
15853
15555
|
}
|
|
15854
15556
|
|
|
15855
|
-
// src/
|
|
15856
|
-
var
|
|
15857
|
-
function
|
|
15858
|
-
const
|
|
15859
|
-
|
|
15860
|
-
const
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15557
|
+
// src/session-replay/stored-reference.ts
|
|
15558
|
+
var UNKNOWN_MODEL = "unknown";
|
|
15559
|
+
function buildStoredReference(tracks, window) {
|
|
15560
|
+
const refs = [];
|
|
15561
|
+
const models = /* @__PURE__ */ new Map();
|
|
15562
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
15563
|
+
const timesByTrack = [];
|
|
15564
|
+
let recoveryExcluded = 0;
|
|
15565
|
+
let withoutGeometry = 0;
|
|
15566
|
+
let trackCount = 0;
|
|
15567
|
+
for (const track of tracks) {
|
|
15568
|
+
const inWindow = track.positions.filter(
|
|
15569
|
+
(p) => p.timestamp >= window.startMs && p.timestamp < window.endMs
|
|
15570
|
+
);
|
|
15571
|
+
const aliveInWindow = track.firstSeen < window.endMs && track.lastSeen >= window.startMs;
|
|
15572
|
+
if (inWindow.length === 0 && !aliveInWindow) continue;
|
|
15573
|
+
trackCount += 1;
|
|
15574
|
+
const model = track.modelId ?? UNKNOWN_MODEL;
|
|
15575
|
+
models.set(model, (models.get(model) ?? 0) + 1);
|
|
15576
|
+
const fw = track.frameWidth;
|
|
15577
|
+
const fh = track.frameHeight;
|
|
15578
|
+
const hasGeometry = fw !== void 0 && fh !== void 0 && fw > 0 && fh > 0;
|
|
15579
|
+
if (hasGeometry) {
|
|
15580
|
+
const key = `${fw}x${fh}`;
|
|
15581
|
+
const entry2 = sizes.get(key) ?? { width: fw, height: fh, count: 0 };
|
|
15582
|
+
entry2.count += 1;
|
|
15583
|
+
sizes.set(key, entry2);
|
|
15584
|
+
}
|
|
15585
|
+
const times = [];
|
|
15586
|
+
const positions = [];
|
|
15587
|
+
for (const p of inWindow) {
|
|
15588
|
+
if (p.source === "recovery") {
|
|
15589
|
+
recoveryExcluded += 1;
|
|
15590
|
+
continue;
|
|
15591
|
+
}
|
|
15592
|
+
times.push(p.timestamp);
|
|
15593
|
+
if (!hasGeometry) {
|
|
15594
|
+
withoutGeometry += 1;
|
|
15595
|
+
continue;
|
|
15596
|
+
}
|
|
15597
|
+
positions.push({
|
|
15598
|
+
trackId: track.trackId,
|
|
15599
|
+
className: track.className,
|
|
15600
|
+
t: p.timestamp,
|
|
15601
|
+
box: { x: p.bbox.x / fw, y: p.bbox.y / fh, w: p.bbox.w / fw, h: p.bbox.h / fh },
|
|
15602
|
+
score: p.score ?? null
|
|
15603
|
+
});
|
|
15604
|
+
}
|
|
15605
|
+
timesByTrack.push(times.toSorted((a, b) => a - b));
|
|
15606
|
+
if (hasGeometry) {
|
|
15607
|
+
const sorted = positions.toSorted((a, b) => a.t - b.t);
|
|
15608
|
+
refs.push({
|
|
15609
|
+
trackId: track.trackId,
|
|
15610
|
+
className: track.className,
|
|
15611
|
+
firstSeen: Math.min(track.firstSeen, sorted[0]?.t ?? track.firstSeen),
|
|
15612
|
+
lastSeen: Math.max(track.lastSeen, sorted[sorted.length - 1]?.t ?? track.lastSeen),
|
|
15613
|
+
positions: sorted
|
|
15614
|
+
});
|
|
15615
|
+
}
|
|
15616
|
+
}
|
|
15617
|
+
const commonest = [...sizes.values()].toSorted((a, b) => b.count - a.count)[0];
|
|
15871
15618
|
return {
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15619
|
+
tracks: refs,
|
|
15620
|
+
trackCount,
|
|
15621
|
+
recoveryExcluded,
|
|
15622
|
+
withoutGeometry,
|
|
15623
|
+
models: Object.fromEntries(models),
|
|
15624
|
+
frameSize: commonest ? { width: commonest.width, height: commonest.height } : null,
|
|
15625
|
+
positionTimesByTrack: timesByTrack
|
|
15878
15626
|
};
|
|
15879
15627
|
}
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15628
|
+
|
|
15629
|
+
// src/session-replay/run-session-replay.ts
|
|
15630
|
+
var TRACK_LOOKBACK_MS = 12e4;
|
|
15631
|
+
var DEFAULT_REPLAY_WIDTH = 1280;
|
|
15632
|
+
var SUPPRESSED_BIRTH_PROXIMITY_MS = 2e3;
|
|
15633
|
+
var MAX_REPORTED_FAILURES = 20;
|
|
15634
|
+
var MAX_CONSECUTIVE_FAILURES = 10;
|
|
15635
|
+
var STORED_TRACKS_READ_LIMIT = 1e3;
|
|
15636
|
+
var REAL_PACE = {
|
|
15637
|
+
now: () => Date.now(),
|
|
15638
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
15639
|
+
};
|
|
15640
|
+
var PIN_REFUSALS = ["model-not-servable", "device-not-servable"];
|
|
15641
|
+
function pinRefusalOf(err) {
|
|
15642
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15643
|
+
for (const reason of PIN_REFUSALS) {
|
|
15644
|
+
if (message.includes(`${reason}:`)) return { reason, detail: message };
|
|
15645
|
+
}
|
|
15646
|
+
return null;
|
|
15647
|
+
}
|
|
15648
|
+
function toReplayBoxes(frame) {
|
|
15649
|
+
const w = frame.width;
|
|
15650
|
+
const h = frame.height;
|
|
15651
|
+
if (!(w > 0 && h > 0)) return [];
|
|
15652
|
+
return frame.detections.filter((d) => d.kind === "first-level").map((d) => ({
|
|
15653
|
+
className: d.macroClass,
|
|
15654
|
+
score: d.score,
|
|
15655
|
+
box: { x: d.bbox.x / w, y: d.bbox.y / h, w: d.bbox.width / w, h: d.bbox.height / h }
|
|
15656
|
+
}));
|
|
15883
15657
|
}
|
|
15884
|
-
function
|
|
15885
|
-
|
|
15886
|
-
return totalDetections * 1e3 / wallMs;
|
|
15658
|
+
function sameClass2(a, b) {
|
|
15659
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
15887
15660
|
}
|
|
15888
|
-
function
|
|
15889
|
-
|
|
15890
|
-
|
|
15661
|
+
async function runSessionReplay(deps, request, run) {
|
|
15662
|
+
const tags = { deviceId: request.deviceId };
|
|
15663
|
+
const refuse = (reason, detail) => {
|
|
15664
|
+
deps.logger.warn("session replay refused", {
|
|
15665
|
+
tags,
|
|
15666
|
+
meta: { reason, detail, motionEventId: request.motionEventId, nodeId: request.nodeId }
|
|
15667
|
+
});
|
|
15668
|
+
return { kind: "refused", reason, detail };
|
|
15669
|
+
};
|
|
15670
|
+
run.report({ phase: "preparing", framesPlanned: 0, framesDone: 0 });
|
|
15671
|
+
const session = await deps.findMotionSession(
|
|
15672
|
+
request.deviceId,
|
|
15673
|
+
request.motionEventId,
|
|
15674
|
+
request.motionStartMs
|
|
15675
|
+
);
|
|
15676
|
+
if (session === null) {
|
|
15677
|
+
return refuse(
|
|
15678
|
+
"session-not-found",
|
|
15679
|
+
`no stored motion session ${request.motionEventId} for camera ${request.deviceId}`
|
|
15680
|
+
);
|
|
15681
|
+
}
|
|
15682
|
+
const window = motionSessionWindow(session, await deps.cameraSessionSettings(request.deviceId));
|
|
15683
|
+
if (window.kind === "refused") return refuse(window.reason, window.detail);
|
|
15684
|
+
const tree = await deps.resolveCameraTree(request.deviceId, request.nodeId);
|
|
15685
|
+
const selection = selectRootReplayStep(tree, request.modelId);
|
|
15686
|
+
if (selection.kind === "refused") return refuse(selection.reason, selection.detail);
|
|
15687
|
+
const step = selection.step;
|
|
15688
|
+
const modelId = step.modelId ?? "";
|
|
15689
|
+
const validation = await deps.validate([step], request.nodeId, request.deviceKey);
|
|
15690
|
+
const pool = `${request.nodeId}/${request.deviceKey ?? "node default"}`;
|
|
15691
|
+
if (!validation.ok) {
|
|
15692
|
+
return refuse(
|
|
15693
|
+
"device-not-servable",
|
|
15694
|
+
`${pool} cannot run ${step.addonId}: ${validation.issues.map((i) => i.detail).join("; ")}`
|
|
15695
|
+
);
|
|
15696
|
+
}
|
|
15697
|
+
const substitution = validation.substitutions[0];
|
|
15698
|
+
if (substitution !== void 0) {
|
|
15699
|
+
return refuse(
|
|
15700
|
+
"model-not-servable",
|
|
15701
|
+
`${pool} would run ${substitution.running} instead of ${substitution.chosen} (${substitution.format})`
|
|
15702
|
+
);
|
|
15703
|
+
}
|
|
15704
|
+
if (!await deps.isRecording(request.deviceId)) {
|
|
15705
|
+
return refuse("no-recording", `camera ${request.deviceId} does not have recording enabled`);
|
|
15706
|
+
}
|
|
15707
|
+
const [tracks, births] = await Promise.all([
|
|
15708
|
+
deps.listStoredTracks(request.deviceId, window.startMs - TRACK_LOOKBACK_MS, window.endMs),
|
|
15709
|
+
deps.listBirthDecisions(request.deviceId, window.startMs)
|
|
15710
|
+
]);
|
|
15711
|
+
const reference = buildStoredReference(tracks, window);
|
|
15712
|
+
const readTruncated = tracks.length >= STORED_TRACKS_READ_LIMIT;
|
|
15713
|
+
if (readTruncated) {
|
|
15714
|
+
deps.logger.warn(
|
|
15715
|
+
"session replay: stored-track read hit its limit \u2014 the reference may be incomplete",
|
|
15716
|
+
{
|
|
15717
|
+
tags,
|
|
15718
|
+
meta: { limit: STORED_TRACKS_READ_LIMIT, motionEventId: request.motionEventId }
|
|
15719
|
+
}
|
|
15720
|
+
);
|
|
15721
|
+
}
|
|
15722
|
+
const cadence = resolveReplayCadence(request.fps, reference.positionTimesByTrack);
|
|
15723
|
+
const frameSize = reference.frameSize !== null ? { width: reference.frameSize.width, source: "stored-tracks" } : { width: DEFAULT_REPLAY_WIDTH, source: "default" };
|
|
15724
|
+
const windowBirths = births.filter((b) => b.at >= window.startMs && b.at < window.endMs);
|
|
15725
|
+
const suppressedBirths = windowBirths.filter((b) => b.verdict === "suppressed");
|
|
15726
|
+
const liveVerdicts = {};
|
|
15727
|
+
for (const b of windowBirths) liveVerdicts[b.verdict] = (liveVerdicts[b.verdict] ?? 0) + 1;
|
|
15728
|
+
const plan = planReplayFrames(window, cadence.fps, request.maxFrames);
|
|
15729
|
+
if (plan.truncated) {
|
|
15730
|
+
deps.logger.warn("session replay: frame plan truncated by maxFrames", {
|
|
15731
|
+
tags,
|
|
15732
|
+
meta: { maxFrames: request.maxFrames, motionEventId: request.motionEventId }
|
|
15733
|
+
});
|
|
15734
|
+
}
|
|
15735
|
+
const stepMs = 1e3 / cadence.fps;
|
|
15736
|
+
const halfStepMs = stepMs / 2;
|
|
15737
|
+
const timeline = [];
|
|
15738
|
+
const comparisons = [];
|
|
15739
|
+
const inferenceMs = [];
|
|
15740
|
+
const failures = [];
|
|
15741
|
+
let decoded = 0;
|
|
15742
|
+
let ran = 0;
|
|
15743
|
+
let failed = 0;
|
|
15744
|
+
let consecutiveFailures = 0;
|
|
15745
|
+
let lastCallAt = null;
|
|
15746
|
+
const pace = deps.pace ?? REAL_PACE;
|
|
15747
|
+
let withoutFootage = 0;
|
|
15748
|
+
let notDecoded = 0;
|
|
15749
|
+
let truncatedBySafetyCap = false;
|
|
15750
|
+
let nearSuppressedBirth = 0;
|
|
15751
|
+
let positionsCompared = 0;
|
|
15752
|
+
let partsRead = 0;
|
|
15753
|
+
const progress = () => run.report({ phase: "replaying", framesPlanned: plan.planned, framesDone: ran + failed });
|
|
15754
|
+
progress();
|
|
15755
|
+
for (const chunk of plan.chunks) {
|
|
15756
|
+
if (run.signal.aborted) break;
|
|
15757
|
+
const recording = await deps.readRecording(request.deviceId, chunk.instants, stepMs);
|
|
15758
|
+
if (recording.kind === "refused") {
|
|
15759
|
+
withoutFootage += chunk.instants.length;
|
|
15760
|
+
deps.logger.warn("session replay: no footage for a stretch of the session", {
|
|
15761
|
+
tags,
|
|
15762
|
+
meta: { fromMs: chunk.fromMs, toMs: chunk.toMs, detail: recording.detail }
|
|
15763
|
+
});
|
|
15764
|
+
continue;
|
|
15765
|
+
}
|
|
15766
|
+
withoutFootage += recording.instantsWithoutFootage;
|
|
15767
|
+
partsRead += recording.parts.length;
|
|
15768
|
+
if (recording.truncatedBySafetyCap) truncatedBySafetyCap = true;
|
|
15769
|
+
for (const part of recording.parts) {
|
|
15770
|
+
if (run.signal.aborted) break;
|
|
15771
|
+
const first = part.instants[0];
|
|
15772
|
+
if (first === void 0) continue;
|
|
15773
|
+
const frames = await deps.decode(part.bytes, {
|
|
15774
|
+
startSec: (first - part.gopStartMs) / 1e3,
|
|
15775
|
+
durationSec: part.instants.length / cadence.fps,
|
|
15776
|
+
fps: cadence.fps,
|
|
15777
|
+
maxWidth: frameSize.width
|
|
15778
|
+
});
|
|
15779
|
+
decoded += Math.min(frames.length, part.instants.length);
|
|
15780
|
+
if (frames.length < part.instants.length) {
|
|
15781
|
+
notDecoded += part.instants.length - frames.length;
|
|
15782
|
+
deps.logger.warn("session replay: decode produced fewer frames than planned", {
|
|
15783
|
+
tags,
|
|
15784
|
+
meta: { planned: part.instants.length, decoded: frames.length, fromMs: first }
|
|
15785
|
+
});
|
|
15786
|
+
}
|
|
15787
|
+
for (let k = 0; k < part.instants.length; k++) {
|
|
15788
|
+
if (run.signal.aborted) break;
|
|
15789
|
+
const t = part.instants[k];
|
|
15790
|
+
const image = frames[k];
|
|
15791
|
+
if (t === void 0 || image === void 0) break;
|
|
15792
|
+
if (lastCallAt !== null) {
|
|
15793
|
+
const wait = lastCallAt + stepMs - pace.now();
|
|
15794
|
+
if (wait > 0) await pace.sleep(wait);
|
|
15795
|
+
}
|
|
15796
|
+
lastCallAt = pace.now();
|
|
15797
|
+
let result;
|
|
15798
|
+
try {
|
|
15799
|
+
result = await deps.runRootDetector({
|
|
15800
|
+
nodeId: request.nodeId,
|
|
15801
|
+
...request.deviceKey !== void 0 ? { deviceKey: request.deviceKey } : {},
|
|
15802
|
+
steps: [step],
|
|
15803
|
+
image,
|
|
15804
|
+
deviceId: request.deviceId,
|
|
15805
|
+
t
|
|
15806
|
+
});
|
|
15807
|
+
} catch (err) {
|
|
15808
|
+
const pinRefusal = pinRefusalOf(err);
|
|
15809
|
+
if (pinRefusal !== null) return refuse(pinRefusal.reason, pinRefusal.detail);
|
|
15810
|
+
failed += 1;
|
|
15811
|
+
consecutiveFailures += 1;
|
|
15812
|
+
const error51 = err instanceof Error ? err.message : String(err);
|
|
15813
|
+
if (failures.length < MAX_REPORTED_FAILURES) failures.push({ t, error: error51 });
|
|
15814
|
+
deps.logger.warn("session replay: frame failed \u2014 counted, the run continues", {
|
|
15815
|
+
tags,
|
|
15816
|
+
meta: { t, error: error51, consecutiveFailures }
|
|
15817
|
+
});
|
|
15818
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
15819
|
+
return refuse(
|
|
15820
|
+
"pool-failing",
|
|
15821
|
+
`${pool} failed ${consecutiveFailures} frames in a row (last: ${error51}) \u2014 stopped rather than keep loading a live pool`
|
|
15822
|
+
);
|
|
15823
|
+
}
|
|
15824
|
+
progress();
|
|
15825
|
+
continue;
|
|
15826
|
+
}
|
|
15827
|
+
consecutiveFailures = 0;
|
|
15828
|
+
if (result.rootModelId !== modelId) {
|
|
15829
|
+
return refuse(
|
|
15830
|
+
"model-not-servable",
|
|
15831
|
+
result.rootModelId === void 0 ? `${pool} does not report the model it ran (an executor older than D646) \u2014 the pin cannot be verified` : `${pool} reported ${result.rootModelId} for a frame pinned to ${modelId}`
|
|
15832
|
+
);
|
|
15833
|
+
}
|
|
15834
|
+
ran += 1;
|
|
15835
|
+
const frameInferMs = result.debug?.totalInferenceMs;
|
|
15836
|
+
if (frameInferMs !== void 0) inferenceMs.push(frameInferMs);
|
|
15837
|
+
const replayBoxes = toReplayBoxes(result);
|
|
15838
|
+
const instantRef = referenceAt(reference.tracks, t, halfStepMs);
|
|
15839
|
+
positionsCompared += instantRef.entries.length;
|
|
15840
|
+
const cmp = compareFrame(t, replayBoxes, instantRef);
|
|
15841
|
+
comparisons.push(cmp);
|
|
15842
|
+
for (const unmatched of [...cmp.added, ...cmp.unreferenced]) {
|
|
15843
|
+
if (suppressedBirths.some(
|
|
15844
|
+
(b) => sameClass2(b.className, unmatched.className) && Math.abs(b.at - t) <= SUPPRESSED_BIRTH_PROXIMITY_MS
|
|
15845
|
+
)) {
|
|
15846
|
+
nearSuppressedBirth += 1;
|
|
15847
|
+
}
|
|
15848
|
+
}
|
|
15849
|
+
timeline.push({
|
|
15850
|
+
t,
|
|
15851
|
+
referenced: cmp.referenced,
|
|
15852
|
+
inferenceMs: frameInferMs ?? null,
|
|
15853
|
+
replay: [
|
|
15854
|
+
...cmp.matched.map((p) => ({ ...p.replay, outcome: "matched" })),
|
|
15855
|
+
...cmp.classChanged.map((p) => ({ ...p.replay, outcome: "class-changed" })),
|
|
15856
|
+
...cmp.added.map((r) => ({ ...r, outcome: "added" })),
|
|
15857
|
+
...cmp.unreferenced.map((r) => ({ ...r, outcome: "unreferenced" }))
|
|
15858
|
+
],
|
|
15859
|
+
stored: [
|
|
15860
|
+
...cmp.matched.map((p) => ({ ...p.stored, outcome: "matched" })),
|
|
15861
|
+
...cmp.classChanged.map((p) => ({ ...p.stored, outcome: "class-changed" })),
|
|
15862
|
+
...cmp.missed.map((s) => ({ ...s, outcome: "missed" }))
|
|
15863
|
+
]
|
|
15864
|
+
});
|
|
15865
|
+
progress();
|
|
15866
|
+
}
|
|
15867
|
+
}
|
|
15868
|
+
}
|
|
15869
|
+
const aborted2 = run.signal.aborted;
|
|
15870
|
+
if (partsRead === 0 && !aborted2) {
|
|
15871
|
+
return refuse(
|
|
15872
|
+
"no-footage",
|
|
15873
|
+
`no recording covers any of the ${plan.planned} planned instants of motion session ${request.motionEventId}`
|
|
15874
|
+
);
|
|
15875
|
+
}
|
|
15876
|
+
if (aborted2) {
|
|
15877
|
+
deps.logger.info("session replay aborted \u2014 the report covers the frames that ran", {
|
|
15878
|
+
tags,
|
|
15879
|
+
meta: { ran, planned: plan.planned }
|
|
15880
|
+
});
|
|
15881
|
+
}
|
|
15882
|
+
run.report({ phase: "done", framesPlanned: plan.planned, framesDone: ran + failed });
|
|
15883
|
+
return {
|
|
15884
|
+
kind: "ran",
|
|
15885
|
+
report: {
|
|
15886
|
+
deviceId: request.deviceId,
|
|
15887
|
+
motionEventId: request.motionEventId,
|
|
15888
|
+
window: {
|
|
15889
|
+
startMs: window.startMs,
|
|
15890
|
+
endMs: window.endMs,
|
|
15891
|
+
motionStartMs: window.motionStartMs,
|
|
15892
|
+
motionEndMs: window.motionEndMs,
|
|
15893
|
+
prerollSource: window.prerollSource,
|
|
15894
|
+
tailSource: window.tailSource
|
|
15895
|
+
},
|
|
15896
|
+
pin: {
|
|
15897
|
+
nodeId: request.nodeId,
|
|
15898
|
+
deviceKey: request.deviceKey ?? null,
|
|
15899
|
+
modelId,
|
|
15900
|
+
cameraModelId: selection.cameraModelId
|
|
15901
|
+
},
|
|
15902
|
+
rootStep: step,
|
|
15903
|
+
cadence,
|
|
15904
|
+
frameSize,
|
|
15905
|
+
frames: {
|
|
15906
|
+
planned: plan.planned,
|
|
15907
|
+
decoded,
|
|
15908
|
+
ran,
|
|
15909
|
+
failed,
|
|
15910
|
+
withoutFootage,
|
|
15911
|
+
notDecoded,
|
|
15912
|
+
truncated: plan.truncated,
|
|
15913
|
+
truncatedBySafetyCap,
|
|
15914
|
+
aborted: aborted2
|
|
15915
|
+
},
|
|
15916
|
+
reference: {
|
|
15917
|
+
tracks: reference.trackCount,
|
|
15918
|
+
positionsCompared,
|
|
15919
|
+
recoveryExcluded: reference.recoveryExcluded,
|
|
15920
|
+
withoutGeometry: reference.withoutGeometry,
|
|
15921
|
+
models: reference.models,
|
|
15922
|
+
read: { tracks: tracks.length, truncated: readTruncated }
|
|
15923
|
+
},
|
|
15924
|
+
confirmation: { liveVerdicts, nearSuppressedBirth },
|
|
15925
|
+
summary: summarizeReplay(comparisons, inferenceMs),
|
|
15926
|
+
timeline,
|
|
15927
|
+
failures
|
|
15928
|
+
}
|
|
15929
|
+
};
|
|
15930
|
+
}
|
|
15931
|
+
|
|
15932
|
+
// src/session-replay/camera-settings.ts
|
|
15933
|
+
var FieldSchema = external_exports.object({
|
|
15934
|
+
key: external_exports.string().optional(),
|
|
15935
|
+
value: external_exports.unknown().optional(),
|
|
15936
|
+
fields: external_exports.array(external_exports.unknown()).optional(),
|
|
15937
|
+
tabs: external_exports.array(external_exports.object({ fields: external_exports.array(external_exports.unknown()).optional() }).loose()).optional()
|
|
15938
|
+
}).loose();
|
|
15939
|
+
var FormSchema = external_exports.object({
|
|
15940
|
+
sections: external_exports.array(external_exports.object({ fields: external_exports.array(external_exports.unknown()) }).loose())
|
|
15941
|
+
});
|
|
15942
|
+
function collect(fields, out) {
|
|
15943
|
+
for (const raw of fields) {
|
|
15944
|
+
const field = FieldSchema.safeParse(raw);
|
|
15945
|
+
if (!field.success) continue;
|
|
15946
|
+
if (field.data.key !== void 0 && "value" in field.data)
|
|
15947
|
+
out.set(field.data.key, field.data.value);
|
|
15948
|
+
if (field.data.fields !== void 0) collect(field.data.fields, out);
|
|
15949
|
+
for (const tab of field.data.tabs ?? []) collect(tab.fields ?? [], out);
|
|
15950
|
+
}
|
|
15951
|
+
}
|
|
15952
|
+
function readCameraSessionSettings(raw) {
|
|
15953
|
+
const form = FormSchema.safeParse(raw);
|
|
15954
|
+
if (!form.success) return {};
|
|
15955
|
+
const values = /* @__PURE__ */ new Map();
|
|
15956
|
+
for (const section of form.data.sections) collect(section.fields, values);
|
|
15957
|
+
const cooldown = values.get("motionCooldownMs");
|
|
15958
|
+
const preRoll = values.get("preRollInferenceEnabled");
|
|
15959
|
+
return {
|
|
15960
|
+
...typeof cooldown === "number" && Number.isFinite(cooldown) && cooldown >= 0 ? { motionCooldownMs: cooldown } : {},
|
|
15961
|
+
...typeof preRoll === "boolean" ? { preRollInferenceEnabled: preRoll } : {}
|
|
15962
|
+
};
|
|
15891
15963
|
}
|
|
15892
15964
|
|
|
15893
15965
|
// src/benchmark-addon.ts
|
|
15894
15966
|
var KEY_BENCHMARK_HISTORY = "benchmarkHistory";
|
|
15895
15967
|
var MAX_HISTORY_ENTRIES = 100;
|
|
15896
15968
|
var EMPTY_HISTORY_JSON = "[]";
|
|
15897
|
-
var BENCH_RUN_TIMEOUT_MS = Math.
|
|
15898
|
-
|
|
15899
|
-
Number(process.env["CAMSTACK_BENCH_RUN_TIMEOUT_MS"]) ||
|
|
15969
|
+
var BENCH_RUN_TIMEOUT_MS = Math.min(
|
|
15970
|
+
55e3,
|
|
15971
|
+
Math.max(1e4, Number(process.env["CAMSTACK_BENCH_RUN_TIMEOUT_MS"]) || 55e3)
|
|
15900
15972
|
);
|
|
15973
|
+
var REPLAY_RECORDING_PROFILE = "high";
|
|
15974
|
+
var REPLAY_BIRTHS_LIMIT = 1e3;
|
|
15975
|
+
var REPLAY_DECODE_TIMEOUT_FLOOR_MS = 15e3;
|
|
15976
|
+
var REPLAY_DECODE_TIMEOUT_PER_SEC_MS = 3e3;
|
|
15977
|
+
var MOTION_SESSIONS_DEFAULT_SPAN_MS = 24 * 60 * 60 * 1e3;
|
|
15901
15978
|
async function withHardTimeout(promise2, ms, label) {
|
|
15902
15979
|
let timer;
|
|
15903
15980
|
const timeout = new Promise((_resolve, reject) => {
|
|
@@ -15914,60 +15991,35 @@ async function withHardTimeout(promise2, ms, label) {
|
|
|
15914
15991
|
}
|
|
15915
15992
|
}
|
|
15916
15993
|
var HistoryArraySchema = external_exports.array(HistoryEntrySchema);
|
|
15917
|
-
var
|
|
15918
|
-
"camera-sim-360p.h264": {
|
|
15919
|
-
codec: "h264",
|
|
15920
|
-
resolutionLabel: "360p",
|
|
15921
|
-
description: "Synthetic 360p H.264 \u2014 light workload"
|
|
15922
|
-
},
|
|
15923
|
-
"camera-sim-1080p.h264": {
|
|
15924
|
-
codec: "h264",
|
|
15925
|
-
resolutionLabel: "1080p",
|
|
15926
|
-
description: "Synthetic 1080p H.264 \u2014 production-like"
|
|
15927
|
-
},
|
|
15928
|
-
"camera-sim-1440p-h265.hevc": {
|
|
15929
|
-
codec: "h265",
|
|
15930
|
-
resolutionLabel: "1440p",
|
|
15931
|
-
description: "Synthetic 1440p H.265 \u2014 hardware decode stress"
|
|
15932
|
-
}
|
|
15933
|
-
};
|
|
15934
|
-
function resolveFixturesDir() {
|
|
15935
|
-
const here = fileURLToPath(import.meta.url);
|
|
15936
|
-
const pkgRoot = path2.resolve(path2.dirname(here), "..");
|
|
15937
|
-
return path2.join(pkgRoot, "assets", "fixtures");
|
|
15938
|
-
}
|
|
15939
|
-
var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
15994
|
+
var BenchmarkAddon = class extends BaseAddon {
|
|
15940
15995
|
id = "benchmark";
|
|
15941
|
-
/** Per-sessionId abort flags for in-flight decoder perf runs. */
|
|
15942
|
-
decoderPerfAbortFlags = /* @__PURE__ */ new Map();
|
|
15943
|
-
/** Per-sessionId abort flags for in-flight sustained-throughput runs. */
|
|
15944
|
-
sustainedAbortFlags = /* @__PURE__ */ new Map();
|
|
15945
|
-
/**
|
|
15946
|
-
* In-memory cache of original JPEG bytes keyed by `frameId`. Populated by
|
|
15947
|
-
* `cacheBenchFrame` (UI uploads JPEG once at bench start). Subsequent
|
|
15948
|
-
* `runPipelineTest` calls ship only `frameId` — bench addon forwards the
|
|
15949
|
-
* cached JPEG buffer as `image: Uint8Array` to pipelineExecutor.runPipeline
|
|
15950
|
-
* (Moleculer MsgPack ships binary efficiently). Why JPEG and not raw RGB:
|
|
15951
|
-
* cross-process MsgPack of a 2.7MB raw RGB frame (1280×720×3) is 13×
|
|
15952
|
-
* heavier than the 200KB JPEG, and the pipeline addon's per-call sharp
|
|
15953
|
-
* decode (~5ms) is cheaper than serialising the raw buffer twice.
|
|
15954
|
-
* Production's broker→detection path is in-process (same group) so neither
|
|
15955
|
-
* cost matters there.
|
|
15956
|
-
*/
|
|
15957
|
-
frameCache = /* @__PURE__ */ new Map();
|
|
15958
|
-
/** Periodic GC handle — clears expired frames every 60s. */
|
|
15959
|
-
frameCacheSweeper = null;
|
|
15960
|
-
/** The replay run collection — declared in `onInitialize`, before any
|
|
15961
|
-
* `runReplayClip` call can save into it. See `replay/replay-store.ts`. */
|
|
15962
15996
|
replayStore = null;
|
|
15963
|
-
/** The replay's
|
|
15997
|
+
/** The replay's ffmpeg spawns — the PINNED binary (`ctx.deps.ensureFfmpeg`, D540). */
|
|
15964
15998
|
replayDecoder = new ReplayDecoder({
|
|
15965
|
-
|
|
15999
|
+
resolveFfmpegPath: () => this.ctx.deps.ensureFfmpeg()
|
|
15966
16000
|
});
|
|
15967
|
-
/**
|
|
15968
|
-
*
|
|
15969
|
-
*
|
|
15970
|
-
|
|
16001
|
+
/**
|
|
16002
|
+
* Session replays, start-then-poll (D646). One at a time — a replay drives
|
|
16003
|
+
* one inference call after another on a node that also serves live
|
|
16004
|
+
* cameras — and the last few finished runs kept for a late poll.
|
|
16005
|
+
*/
|
|
16006
|
+
replays = new BackgroundRunRegistry(
|
|
16007
|
+
{
|
|
16008
|
+
maxConcurrent: 1,
|
|
16009
|
+
keepFinished: 10,
|
|
16010
|
+
onSettled: (status) => {
|
|
16011
|
+
if (status.state === "failed") {
|
|
16012
|
+
this.ctx.logger.warn("session replay failed", {
|
|
16013
|
+
meta: { runId: status.runId, error: status.error }
|
|
16014
|
+
});
|
|
16015
|
+
return;
|
|
16016
|
+
}
|
|
16017
|
+
const outcome = status.result;
|
|
16018
|
+
if (outcome === null || outcome.kind !== "ran") return;
|
|
16019
|
+
void this.persistReplay(status.runId, outcome.report.pin.nodeId, outcome);
|
|
16020
|
+
}
|
|
16021
|
+
}
|
|
16022
|
+
);
|
|
15971
16023
|
pages = [
|
|
15972
16024
|
{
|
|
15973
16025
|
id: "benchmark",
|
|
@@ -15990,99 +16042,50 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
15990
16042
|
};
|
|
15991
16043
|
await ReplayStore.declare(this.api.settingsStore);
|
|
15992
16044
|
this.replayStore = new ReplayStore(this.api.settingsStore);
|
|
16045
|
+
await this.pruneReplays();
|
|
15993
16046
|
return {
|
|
15994
16047
|
providers: [{ capability: addonPagesSourceCapability, provider }],
|
|
15995
16048
|
customActions: benchmarkActions,
|
|
15996
16049
|
actionHandlers: {
|
|
15997
16050
|
runPipelineTest: async (input) => this.runPipelineTest(input),
|
|
15998
|
-
|
|
15999
|
-
runMultiCameraStressTest: async (input) => this.runMultiCameraStressTest(input),
|
|
16000
|
-
runSustainedThroughput: async (input) => this.runSustainedThroughput(input),
|
|
16001
|
-
abortSustainedThroughput: async (input) => this.abortSustainedThroughput(input),
|
|
16002
|
-
cacheBenchFrame: async (input) => this.cacheBenchFrame(input),
|
|
16003
|
-
releaseBenchFrame: async (input) => this.releaseBenchFrame(input),
|
|
16004
|
-
listBenchmarkHistory: async () => this.listBenchmarkHistory(),
|
|
16051
|
+
listBenchmarkHistory: async () => this.loadHistory(),
|
|
16005
16052
|
saveBenchmarkResult: async (input) => this.saveBenchmarkResult(input),
|
|
16006
|
-
saveDecoderPerformanceResult: async (input) => this.saveDecoderPerformanceResult(input),
|
|
16007
16053
|
deleteBenchmarkResult: async (input) => this.deleteBenchmarkResult(input),
|
|
16008
16054
|
clearBenchmarkHistory: async () => this.clearBenchmarkHistory(),
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
diffReplayRuns: async (input) => this.diffReplayRunsAction(input)
|
|
16055
|
+
listMotionSessions: async (input) => this.listMotionSessions(input),
|
|
16056
|
+
startSessionReplay: async (input) => this.startSessionReplay(input),
|
|
16057
|
+
getSessionReplayStatus: async (input) => this.getSessionReplayStatus(input),
|
|
16058
|
+
abortSessionReplay: async (input) => ({ aborted: this.replays.abort(input.runId) }),
|
|
16059
|
+
listSessionReplays: async (input) => this.listSessionReplays(input),
|
|
16060
|
+
getSessionReplay: async (input) => this.replayStore === null ? null : this.replayStore.get(input.id)
|
|
16016
16061
|
}
|
|
16017
16062
|
};
|
|
16018
16063
|
}
|
|
16019
16064
|
async onShutdown() {
|
|
16020
|
-
|
|
16021
|
-
clearInterval(this.frameCacheSweeper);
|
|
16022
|
-
this.frameCacheSweeper = null;
|
|
16023
|
-
}
|
|
16024
|
-
this.frameCache.clear();
|
|
16025
|
-
for (const flag of this.sustainedAbortFlags.values()) flag.aborted = true;
|
|
16026
|
-
this.sustainedAbortFlags.clear();
|
|
16027
|
-
}
|
|
16028
|
-
// ── Frame cache (raw RGB) ────────────────────────────────────────────────
|
|
16029
|
-
async cacheBenchFrame(input) {
|
|
16030
|
-
const jpeg = Buffer.from(input.imageBase64, "base64");
|
|
16031
|
-
const sharp = (await import("sharp")).default;
|
|
16032
|
-
const meta3 = await sharp(jpeg).metadata();
|
|
16033
|
-
const width = meta3.width ?? 0;
|
|
16034
|
-
const height = meta3.height ?? 0;
|
|
16035
|
-
if (width <= 0 || height <= 0) {
|
|
16036
|
-
throw new Error("cacheBenchFrame: failed to probe JPEG dimensions");
|
|
16037
|
-
}
|
|
16038
|
-
const ttlMs = Math.max(6e4, (input.ttlSeconds ?? 600) * 1e3);
|
|
16039
|
-
const frameId = `bench-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
16040
|
-
const expiresAt = Date.now() + ttlMs;
|
|
16041
|
-
this.frameCache.set(frameId, { jpeg, width, height, expiresAt });
|
|
16042
|
-
if (!this.frameCacheSweeper) {
|
|
16043
|
-
this.frameCacheSweeper = setInterval(() => this.sweepFrameCache(), 6e4);
|
|
16044
|
-
this.frameCacheSweeper.unref?.();
|
|
16045
|
-
}
|
|
16046
|
-
this.ctx.logger.info("cached bench frame", {
|
|
16047
|
-
meta: { frameId, width, height, jpegBytes: jpeg.length, ttlMs }
|
|
16048
|
-
});
|
|
16049
|
-
return { frameId, width, height, expiresAt };
|
|
16065
|
+
this.replays.abortAll();
|
|
16050
16066
|
}
|
|
16051
|
-
async releaseBenchFrame(input) {
|
|
16052
|
-
return { released: this.frameCache.delete(input.frameId) };
|
|
16053
|
-
}
|
|
16054
|
-
sweepFrameCache() {
|
|
16055
|
-
const now = Date.now();
|
|
16056
|
-
for (const [id, entry] of this.frameCache) {
|
|
16057
|
-
if (entry.expiresAt < now) this.frameCache.delete(id);
|
|
16058
|
-
}
|
|
16059
|
-
}
|
|
16060
|
-
// ── Handler implementations ──────────────────────────────────────────────
|
|
16061
16067
|
get api() {
|
|
16062
16068
|
const api = this.ctx.api;
|
|
16063
16069
|
if (!api) throw new Error("BenchmarkAddon: ctx.api not available");
|
|
16064
16070
|
return api;
|
|
16065
16071
|
}
|
|
16072
|
+
// ── Detection Pipeline — one run ─────────────────────────────────────────
|
|
16066
16073
|
async runPipelineTest(input) {
|
|
16067
|
-
const { nodeId, steps, imageBase64, referenceImage,
|
|
16068
|
-
const { deviceKey, plane } = input;
|
|
16074
|
+
const { nodeId, steps, imageBase64, referenceImage, sessionId, deviceKey, plane } = input;
|
|
16069
16075
|
this.ctx.logger.debug("runPipelineTest payload", {
|
|
16070
16076
|
meta: {
|
|
16071
16077
|
nodeId: nodeId ?? null,
|
|
16072
16078
|
deviceKey: deviceKey ?? null,
|
|
16073
16079
|
plane: plane ?? null,
|
|
16074
16080
|
sessionId: sessionId ?? null,
|
|
16075
|
-
engine: engine ?? null,
|
|
16076
16081
|
stepsCount: steps.length,
|
|
16077
|
-
steps: steps.map(
|
|
16078
|
-
|
|
16082
|
+
steps: steps.map(
|
|
16083
|
+
(s) => `${s.addonId}/${s.modelId ?? "(format default)"}${s.enabled ? "" : "[off]"}`
|
|
16084
|
+
),
|
|
16085
|
+
imageSource: referenceImage ? `ref:${referenceImage}` : imageBase64 ? `base64:${Math.round(imageBase64.length / 1024)}KB` : "none"
|
|
16079
16086
|
}
|
|
16080
16087
|
});
|
|
16081
|
-
const
|
|
16082
|
-
if (frameId && !cached2)
|
|
16083
|
-
throw new Error(`runPipelineTest: frameId ${frameId} not in cache (expired or never cached)`);
|
|
16084
|
-
const cachedImage = cached2 ? new Uint8Array(cached2.jpeg) : void 0;
|
|
16085
|
-
const image = cachedImage ?? (imageBase64 ? new Uint8Array(Buffer.from(imageBase64, "base64")) : void 0);
|
|
16088
|
+
const image = imageBase64 ? new Uint8Array(Buffer.from(imageBase64, "base64")) : void 0;
|
|
16086
16089
|
const stepsToRun = steps.length > 0 ? [...steps] : await (async () => {
|
|
16087
16090
|
const globalSteps = await this.api.pipelineExecutor.getGlobalSteps.query({ nodeId });
|
|
16088
16091
|
if (!globalSteps || globalSteps.length === 0) {
|
|
@@ -16101,10 +16104,8 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16101
16104
|
nodeId,
|
|
16102
16105
|
steps: stepsToRun,
|
|
16103
16106
|
...image ? { image } : {},
|
|
16104
|
-
...!image && imageBase64 ? { imageBase64 } : {},
|
|
16105
16107
|
referenceImage,
|
|
16106
16108
|
sessionId,
|
|
16107
|
-
...engine ? { engine } : {},
|
|
16108
16109
|
// Absent, never present-and-undefined: the generated router takes
|
|
16109
16110
|
// `.loose()` input and an explicit `undefined` is not the same thing
|
|
16110
16111
|
// as "the node's default pool" / "the cap's default plane".
|
|
@@ -16115,579 +16116,235 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16115
16116
|
"runPipelineTest"
|
|
16116
16117
|
);
|
|
16117
16118
|
}
|
|
16118
|
-
// ──
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
static REPLAY_CLIPS_LOCATION = "replayClips";
|
|
16129
|
-
async uploadReplayClip(input) {
|
|
16130
|
-
const bytes = Buffer.from(input.dataBase64, "base64");
|
|
16131
|
-
const clipId = `clip:${createHash("sha256").update(bytes).digest("hex").slice(0, 32)}`;
|
|
16132
|
-
await this.api.storage.write.mutate({
|
|
16133
|
-
location: _BenchmarkAddon.REPLAY_CLIPS_LOCATION,
|
|
16134
|
-
relativePath: `${clipId}.bin`,
|
|
16135
|
-
data: new Uint8Array(bytes)
|
|
16119
|
+
// ── Session replay (D646) ─────────────────────────────────────────────────
|
|
16120
|
+
async listMotionSessions(input) {
|
|
16121
|
+
const untilMs = input.untilMs ?? Date.now();
|
|
16122
|
+
const sinceMs = input.sinceMs ?? untilMs - MOTION_SESSIONS_DEFAULT_SPAN_MS;
|
|
16123
|
+
const events = await this.api.pipelineAnalytics.getMotionEvents.query({
|
|
16124
|
+
deviceId: input.deviceId,
|
|
16125
|
+
since: sinceMs,
|
|
16126
|
+
until: untilMs,
|
|
16127
|
+
limit: input.limit,
|
|
16128
|
+
projection: "full"
|
|
16136
16129
|
});
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
if (this.replayStore === null) throw new Error("replay: store not initialized");
|
|
16142
|
-
const store = this.replayStore;
|
|
16143
|
-
const source = input.source;
|
|
16144
|
-
const result = await orchestrateReplayRun(
|
|
16145
|
-
{
|
|
16146
|
-
api: this.api,
|
|
16147
|
-
logger: this.ctx.logger,
|
|
16148
|
-
decoder: this.replayDecoder,
|
|
16149
|
-
store,
|
|
16150
|
-
ffmpegPath: process.env["FFMPEG_PATH"] ?? "ffmpeg",
|
|
16151
|
-
resolveBaseTree: (deviceId) => this.api.pipelineOrchestrator.resolvePipeline.query({ deviceId }),
|
|
16152
|
-
fetchSourceBytes: (src) => this.fetchReplaySourceBytes(src)
|
|
16153
|
-
},
|
|
16154
|
-
{
|
|
16155
|
-
deviceId: input.deviceId,
|
|
16156
|
-
nodeId: input.nodeId,
|
|
16157
|
-
source,
|
|
16158
|
-
overlay: input.overlay,
|
|
16159
|
-
fps: input.fps,
|
|
16160
|
-
maxFrames: input.maxFrames,
|
|
16161
|
-
maxWidth: input.maxWidth,
|
|
16162
|
-
...input.deviceKey !== void 0 ? { deviceKey: input.deviceKey } : {}
|
|
16163
|
-
}
|
|
16130
|
+
const camera = await this.cameraSessionSettings(input.deviceId);
|
|
16131
|
+
const windows = events.map((ev) => ({ ev, window: motionSessionWindow(ev, camera) }));
|
|
16132
|
+
const starts = windows.map(
|
|
16133
|
+
({ ev, window }) => window.kind === "window" ? window.startMs : ev.timestamp
|
|
16164
16134
|
);
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
id: result.replayRunId,
|
|
16168
|
-
deviceId: input.deviceId,
|
|
16169
|
-
nodeId: input.nodeId,
|
|
16170
|
-
clipId: source.kind === "upload" ? source.clipId : `recording:${source.deviceId}:${source.fromMs}`,
|
|
16171
|
-
createdAtMs: Date.now(),
|
|
16172
|
-
source,
|
|
16173
|
-
plan: null,
|
|
16174
|
-
verdict: result.verdict
|
|
16175
|
-
});
|
|
16176
|
-
return result;
|
|
16177
|
-
}
|
|
16178
|
-
/**
|
|
16179
|
-
* The profile a replay window is read from. `high` — same choice
|
|
16180
|
-
* `RECORDED_STILL_PROFILE` makes in `addon-post-analysis` — because the
|
|
16181
|
-
* whole point of `recording` over `upload` is NATIVE pixels (`-c:v copy`
|
|
16182
|
-
* means the recorded frame IS the live frame); a window cut from a
|
|
16183
|
-
* lower-rate rendition would be a worse version of the thing this source
|
|
16184
|
-
* exists to avoid.
|
|
16185
|
-
*/
|
|
16186
|
-
static REPLAY_RECORDING_PROFILE = "high";
|
|
16187
|
-
/**
|
|
16188
|
-
* Byte source for the replay decode step.
|
|
16189
|
-
*
|
|
16190
|
-
* `upload` is `storage.read` against the `replayClips` location. `recording`
|
|
16191
|
-
* is `recording.getDeviceConfig` → `recording.locateSegment` →
|
|
16192
|
-
* `recording.readWindowBytes` — the same "cheap question first" ordering
|
|
16193
|
-
* `RecordedStillService` uses (a camera that is not recording costs a
|
|
16194
|
-
* settings read, never a disk read), through `ctx.api` because addons never
|
|
16195
|
-
* import each other.
|
|
16196
|
-
*
|
|
16197
|
-
* Every expected failure — not recording, no footage covering `fromMs`, a
|
|
16198
|
-
* window that runs past its covering segment — returns a NAMED `refused`,
|
|
16199
|
-
* never a thrown error: "the window has no segments" is a RESULT this
|
|
16200
|
-
* whole feature exists to make legible (see the plan's stated unknown about
|
|
16201
|
-
* whether device 615's footage covers 2026-08-26 18:12), not a failure to
|
|
16202
|
-
* hide behind a generic exception.
|
|
16203
|
-
*/
|
|
16204
|
-
async fetchReplaySourceBytes(source) {
|
|
16205
|
-
if (source.kind === "upload") {
|
|
16206
|
-
try {
|
|
16207
|
-
const bytes = await this.api.storage.read.query({
|
|
16208
|
-
location: _BenchmarkAddon.REPLAY_CLIPS_LOCATION,
|
|
16209
|
-
relativePath: `${source.clipId}.bin`
|
|
16210
|
-
});
|
|
16211
|
-
return { kind: "ok", bytes, startMs: 0 };
|
|
16212
|
-
} catch (err) {
|
|
16213
|
-
return { kind: "refused", reason: `clip-not-found: ${errMsg(err)}` };
|
|
16214
|
-
}
|
|
16215
|
-
}
|
|
16216
|
-
const profile = _BenchmarkAddon.REPLAY_RECORDING_PROFILE;
|
|
16217
|
-
const tags = { deviceId: source.deviceId };
|
|
16218
|
-
const config2 = await this.api.recordingArchive.getDeviceConfig.query({
|
|
16219
|
-
deviceId: source.deviceId
|
|
16220
|
-
});
|
|
16221
|
-
if (config2.enabled === false) {
|
|
16222
|
-
this.ctx.logger.warn("replay: recording source refused \u2014 camera is not recording", {
|
|
16223
|
-
tags
|
|
16224
|
-
});
|
|
16225
|
-
return {
|
|
16226
|
-
kind: "refused",
|
|
16227
|
-
reason: `no-recording: device ${source.deviceId} does not have recording enabled`
|
|
16228
|
-
};
|
|
16229
|
-
}
|
|
16230
|
-
const located = await this.api.recordingArchive.locateSegment.query({
|
|
16231
|
-
deviceId: source.deviceId,
|
|
16232
|
-
profile,
|
|
16233
|
-
epochMs: source.fromMs
|
|
16234
|
-
});
|
|
16235
|
-
if (located.kind !== "segment") {
|
|
16236
|
-
this.ctx.logger.warn(
|
|
16237
|
-
"replay: recording source refused \u2014 no footage covers the window start",
|
|
16238
|
-
{ tags, meta: { fromMs: source.fromMs, toMs: source.toMs, profile } }
|
|
16239
|
-
);
|
|
16240
|
-
return {
|
|
16241
|
-
kind: "refused",
|
|
16242
|
-
reason: `no-footage: device ${source.deviceId} has no ${profile} recording covering ${new Date(source.fromMs).toISOString()}`
|
|
16243
|
-
};
|
|
16244
|
-
}
|
|
16245
|
-
const window = await this.api.recordingArchive.readWindowBytes.query({
|
|
16246
|
-
deviceId: source.deviceId,
|
|
16247
|
-
profile,
|
|
16248
|
-
startMs: located.startMs,
|
|
16249
|
-
fromMs: source.fromMs,
|
|
16250
|
-
toMs: source.toMs
|
|
16251
|
-
});
|
|
16252
|
-
if (window.kind !== "ok") {
|
|
16253
|
-
this.ctx.logger.warn(
|
|
16254
|
-
"replay: recording source refused \u2014 window runs past the covering segment",
|
|
16255
|
-
{
|
|
16256
|
-
tags,
|
|
16257
|
-
meta: { fromMs: source.fromMs, toMs: source.toMs, segmentEndMs: window.segmentEndMs }
|
|
16258
|
-
}
|
|
16259
|
-
);
|
|
16260
|
-
return {
|
|
16261
|
-
kind: "refused",
|
|
16262
|
-
reason: `window-spans-multiple-segments: footage for device ${source.deviceId} changes segment at ${window.segmentEndMs}, before the requested end ${source.toMs} \u2014 shorten the window or align it to one segment`
|
|
16263
|
-
};
|
|
16264
|
-
}
|
|
16265
|
-
if (!window.reachesRequestedEnd) {
|
|
16266
|
-
this.ctx.logger.warn(
|
|
16267
|
-
"replay: recording window bytes truncated by the recorder read safety cap",
|
|
16268
|
-
{ tags, meta: { fromMs: source.fromMs, toMs: source.toMs, gopDurMs: window.gopDurMs } }
|
|
16269
|
-
);
|
|
16270
|
-
}
|
|
16271
|
-
return { kind: "ok", bytes: window.data, startMs: window.gopStartMs };
|
|
16272
|
-
}
|
|
16273
|
-
async listReplayRuns(input) {
|
|
16274
|
-
if (this.replayStore === null) return [];
|
|
16275
|
-
const rows = await this.replayStore.list({ deviceId: input.deviceId });
|
|
16276
|
-
for (const row of rows) this.replayRunCache.set(row.id, row);
|
|
16277
|
-
return rows;
|
|
16278
|
-
}
|
|
16279
|
-
/** `verdict` round-trips through the store as opaque JSON
|
|
16280
|
-
* (`StoredReplayRun.verdict: unknown`) — this is the ONE narrowing back
|
|
16281
|
-
* to the shape `diffReplayRuns` needs, at the ONE place that reads it
|
|
16282
|
-
* for that purpose. Same documented boundary `ReplayStore.list` itself
|
|
16283
|
-
* crosses (`replay-store.ts`): this addon is the only reader that knows
|
|
16284
|
-
* what it wrote. */
|
|
16285
|
-
static verdictTracksAndDiscarded(verdict) {
|
|
16286
|
-
return verdict;
|
|
16287
|
-
}
|
|
16288
|
-
async diffReplayRunsAction(input) {
|
|
16289
|
-
const a = this.replayRunCache.get(input.runIdA);
|
|
16290
|
-
const b = this.replayRunCache.get(input.runIdB);
|
|
16291
|
-
if (a === void 0 || b === void 0) {
|
|
16292
|
-
throw new Error(
|
|
16293
|
-
"replay: diff \u2014 one or both run ids are not loaded (call listReplayRuns first)"
|
|
16294
|
-
);
|
|
16295
|
-
}
|
|
16296
|
-
return diffReplayRuns(
|
|
16297
|
-
{
|
|
16298
|
-
clipId: a.clipId,
|
|
16299
|
-
nodeId: a.nodeId,
|
|
16300
|
-
settings: {},
|
|
16301
|
-
..._BenchmarkAddon.verdictTracksAndDiscarded(a.verdict)
|
|
16302
|
-
},
|
|
16303
|
-
{
|
|
16304
|
-
clipId: b.clipId,
|
|
16305
|
-
nodeId: b.nodeId,
|
|
16306
|
-
settings: {},
|
|
16307
|
-
..._BenchmarkAddon.verdictTracksAndDiscarded(b.verdict)
|
|
16308
|
-
}
|
|
16135
|
+
const ends = windows.map(
|
|
16136
|
+
({ ev, window }) => window.kind === "window" ? window.endMs : ev.timestamp
|
|
16309
16137
|
);
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
* collapses the N predicts into one `MSG_INFER_BATCH = 0x03` IPC
|
|
16318
|
-
* frame — per-call header + JSON envelope cost is amortised N:1.
|
|
16319
|
-
*
|
|
16320
|
-
* Returns N `FrameResult`s plus aggregate timing so the UI can
|
|
16321
|
-
* compute fps directly from the wall clock.
|
|
16322
|
-
*/
|
|
16323
|
-
async runPipelineBatchTest(input) {
|
|
16324
|
-
this.ctx.logger.debug("runPipelineBatchTest payload", {
|
|
16325
|
-
meta: {
|
|
16326
|
-
nodeId: input.nodeId ?? null,
|
|
16327
|
-
sessionId: input.sessionId ?? null,
|
|
16328
|
-
engine: input.engine ?? null,
|
|
16329
|
-
batchSize: input.batchSize,
|
|
16330
|
-
targetSize: input.targetSize ?? null,
|
|
16331
|
-
stepsCount: input.steps.length,
|
|
16332
|
-
steps: input.steps.map((s) => `${s.addonId}/${s.modelId}${s.enabled ? "" : "[off]"}`),
|
|
16333
|
-
imageBytesKB: Math.round(input.imageBase64.length / 1024)
|
|
16334
|
-
}
|
|
16335
|
-
});
|
|
16336
|
-
const { nodeId, steps, imageBase64, batchSize, sessionId, engine } = input;
|
|
16337
|
-
const targetSize = input.targetSize ?? 640;
|
|
16338
|
-
if (steps.length === 0) {
|
|
16339
|
-
throw new Error("runPipelineBatchTest: empty steps");
|
|
16340
|
-
}
|
|
16341
|
-
const sharp = (await import("sharp")).default;
|
|
16342
|
-
const jpeg = Buffer.from(imageBase64, "base64");
|
|
16343
|
-
const rgb = await sharp(jpeg).resize({ width: targetSize, height: targetSize, fit: "fill" }).raw().toColorspace("srgb").toBuffer();
|
|
16344
|
-
const rgbBytes = new Uint8Array(rgb.buffer, rgb.byteOffset, rgb.byteLength);
|
|
16345
|
-
this.ctx.logger.debug("runPipelineBatchTest dispatch", {
|
|
16346
|
-
meta: {
|
|
16347
|
-
phase: "batch",
|
|
16348
|
-
batchSize,
|
|
16349
|
-
targetSize,
|
|
16350
|
-
rgbBytes: rgb.byteLength,
|
|
16351
|
-
totalBytes: rgb.byteLength * batchSize
|
|
16352
|
-
}
|
|
16138
|
+
const tracks = events.length === 0 ? [] : await this.api.pipelineAnalytics.listTracks.query({
|
|
16139
|
+
deviceId: input.deviceId,
|
|
16140
|
+
since: Math.min(...starts),
|
|
16141
|
+
until: Math.max(...ends),
|
|
16142
|
+
projection: "slim",
|
|
16143
|
+
excludeSources: ["sensor", "audio"],
|
|
16144
|
+
limit: STORED_TRACKS_READ_LIMIT
|
|
16353
16145
|
});
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16146
|
+
return windows.map(({ ev, window }) => ({
|
|
16147
|
+
motionEventId: ev.id,
|
|
16148
|
+
deviceId: ev.deviceId,
|
|
16149
|
+
startMs: ev.timestamp,
|
|
16150
|
+
durationMs: ev.durationMs ?? null,
|
|
16151
|
+
open: ev.durationMs === null,
|
|
16152
|
+
refusal: window.kind === "refused" ? window.reason : null,
|
|
16153
|
+
storedTracks: window.kind === "window" ? tracks.filter((t) => t.firstSeen >= window.startMs && t.firstSeen < window.endMs).length : 0
|
|
16360
16154
|
}));
|
|
16361
|
-
const callStart = Date.now();
|
|
16362
|
-
const out = await withHardTimeout(
|
|
16363
|
-
this.api.pipelineExecutor.runPipelineBatch.mutate({
|
|
16364
|
-
nodeId,
|
|
16365
|
-
steps: [...steps],
|
|
16366
|
-
frames,
|
|
16367
|
-
sessionId,
|
|
16368
|
-
...engine ? { engine } : {}
|
|
16369
|
-
}),
|
|
16370
|
-
BENCH_RUN_TIMEOUT_MS,
|
|
16371
|
-
"runPipelineBatchTest"
|
|
16372
|
-
);
|
|
16373
|
-
const callMs = Date.now() - callStart;
|
|
16374
|
-
const inferMs = out.results.reduce((acc, r) => Math.max(acc, r.debug?.totalInferenceMs ?? 0), 0);
|
|
16375
|
-
return {
|
|
16376
|
-
results: out.results,
|
|
16377
|
-
callMs,
|
|
16378
|
-
inferMs
|
|
16379
|
-
};
|
|
16380
16155
|
}
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
* 1. Decode + sharp-resize the JPEG once.
|
|
16386
|
-
* 2. Build pre-fabricated `FrameInput[]` templates for the batched
|
|
16387
|
-
* and single-frame call shapes.
|
|
16388
|
-
* 3. Spawn `simulatedCameras` parallel async loops; each loop runs
|
|
16389
|
-
* `batchesPerCamera` rounds. Per round: fan out 1 batched call
|
|
16390
|
-
* + `extraSinglesPerRound` single calls via `Promise.all`.
|
|
16391
|
-
* 4. Aggregate detsPerSec from `(simulatedCameras × batchesPerCamera
|
|
16392
|
-
* × batchSize) / totalMs` so the metric is comparable to the
|
|
16393
|
-
* external detector-engine benchmark we're calibrating against.
|
|
16394
|
-
*
|
|
16395
|
-
* Because the dispatch goes through `this.api.pipelineExecutor.
|
|
16396
|
-
* runPipelineBatch.mutate(...)` and not over the wire, `localProvider
|
|
16397
|
-
* Link` short-circuits when detection-pipeline is co-located on the
|
|
16398
|
-
* same node. Removes per-call JSON marshalling, JPEG decode, and TLS.
|
|
16399
|
-
*/
|
|
16400
|
-
async runMultiCameraStressTest(input) {
|
|
16401
|
-
const targetSize = input.targetSize ?? 640;
|
|
16402
|
-
const simulatedCameras = input.simulatedCameras ?? 4;
|
|
16403
|
-
const batchesPerCamera = input.batchesPerCamera ?? 125;
|
|
16404
|
-
const batchSize = input.batchSize ?? 4;
|
|
16405
|
-
const extraSinglesPerRound = input.extraSinglesPerRound ?? 3;
|
|
16406
|
-
const { nodeId, steps, sessionId } = input;
|
|
16407
|
-
if (steps.length === 0) {
|
|
16408
|
-
throw new Error("runMultiCameraStressTest: empty steps");
|
|
16409
|
-
}
|
|
16410
|
-
this.ctx.logger.info("runMultiCameraStressTest start", {
|
|
16156
|
+
startSessionReplay(input) {
|
|
16157
|
+
const runId = `replay-${input.deviceId}-${input.motionEventId}-${Date.now().toString(36)}`;
|
|
16158
|
+
this.ctx.logger.info("session replay started", {
|
|
16159
|
+
tags: { deviceId: input.deviceId },
|
|
16411
16160
|
meta: {
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16161
|
+
runId,
|
|
16162
|
+
motionEventId: input.motionEventId,
|
|
16163
|
+
nodeId: input.nodeId,
|
|
16164
|
+
deviceKey: input.deviceKey ?? null,
|
|
16165
|
+
modelId: input.modelId ?? null,
|
|
16166
|
+
fps: input.fps ?? null,
|
|
16167
|
+
maxFrames: input.maxFrames
|
|
16419
16168
|
}
|
|
16420
16169
|
});
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
let countedDetections = 0;
|
|
16438
|
-
let totalCalls = 0;
|
|
16439
|
-
let totalFrames = 0;
|
|
16440
|
-
const dispatchOne = async (frames, isCounted, tag) => {
|
|
16441
|
-
const callStart = Date.now();
|
|
16442
|
-
try {
|
|
16443
|
-
const out = await this.api.pipelineExecutor.runPipelineBatch.mutate({
|
|
16444
|
-
nodeId,
|
|
16445
|
-
steps: [...steps],
|
|
16446
|
-
frames: [...frames],
|
|
16447
|
-
sessionId: sessionId ? `${sessionId}-${tag}` : tag
|
|
16448
|
-
});
|
|
16449
|
-
const callMs = Date.now() - callStart;
|
|
16450
|
-
if (isCounted) {
|
|
16451
|
-
callMsList.push(callMs);
|
|
16452
|
-
let maxInfer = 0;
|
|
16453
|
-
for (const r of out.results) {
|
|
16454
|
-
const ms = r.debug?.totalInferenceMs ?? 0;
|
|
16455
|
-
if (ms > maxInfer) maxInfer = ms;
|
|
16456
|
-
}
|
|
16457
|
-
inferMsList.push(maxInfer);
|
|
16458
|
-
}
|
|
16459
|
-
} catch (err) {
|
|
16460
|
-
errors++;
|
|
16461
|
-
this.ctx.logger.warn("runMultiCameraStressTest dispatch error", {
|
|
16462
|
-
meta: { tag, error: err instanceof Error ? err.message : String(err) }
|
|
16463
|
-
});
|
|
16464
|
-
}
|
|
16465
|
-
};
|
|
16466
|
-
const start = Date.now();
|
|
16467
|
-
const cameraTasks = Array.from(
|
|
16468
|
-
{ length: simulatedCameras },
|
|
16469
|
-
(_, camIdx) => (async () => {
|
|
16470
|
-
for (let i = 0; i < batchesPerCamera; i++) {
|
|
16471
|
-
const dispatches = [
|
|
16472
|
-
dispatchOne(batchedFrames, true, `c${camIdx}-r${i}-b`)
|
|
16473
|
-
];
|
|
16474
|
-
for (let j = 0; j < extraSinglesPerRound; j++) {
|
|
16475
|
-
dispatches.push(dispatchOne(singleFrames, false, `c${camIdx}-r${i}-s${j}`));
|
|
16476
|
-
}
|
|
16477
|
-
await Promise.all(dispatches);
|
|
16478
|
-
countedDetections += batchSize;
|
|
16479
|
-
totalCalls += 1 + extraSinglesPerRound;
|
|
16480
|
-
totalFrames += batchSize + extraSinglesPerRound;
|
|
16481
|
-
}
|
|
16482
|
-
})()
|
|
16170
|
+
this.replays.start(
|
|
16171
|
+
runId,
|
|
16172
|
+
(run) => runSessionReplay(
|
|
16173
|
+
this.sessionReplayDeps(),
|
|
16174
|
+
{
|
|
16175
|
+
deviceId: input.deviceId,
|
|
16176
|
+
motionEventId: input.motionEventId,
|
|
16177
|
+
motionStartMs: input.motionStartMs,
|
|
16178
|
+
nodeId: input.nodeId,
|
|
16179
|
+
maxFrames: input.maxFrames,
|
|
16180
|
+
...input.deviceKey !== void 0 ? { deviceKey: input.deviceKey } : {},
|
|
16181
|
+
...input.modelId !== void 0 ? { modelId: input.modelId } : {},
|
|
16182
|
+
...input.fps !== void 0 ? { fps: input.fps } : {}
|
|
16183
|
+
},
|
|
16184
|
+
run
|
|
16185
|
+
)
|
|
16483
16186
|
);
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
const
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16187
|
+
return { accepted: true, runId };
|
|
16188
|
+
}
|
|
16189
|
+
getSessionReplayStatus(input) {
|
|
16190
|
+
const status = this.replays.status(input.runId);
|
|
16191
|
+
if (status === null) return null;
|
|
16192
|
+
return {
|
|
16193
|
+
runId: status.runId,
|
|
16194
|
+
state: status.state,
|
|
16195
|
+
startedAtMs: status.startedAtMs,
|
|
16196
|
+
finishedAtMs: status.finishedAtMs,
|
|
16197
|
+
progress: status.progress,
|
|
16198
|
+
outcome: status.result,
|
|
16199
|
+
error: status.error
|
|
16491
16200
|
};
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
this.
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
}
|
|
16503
|
-
});
|
|
16201
|
+
}
|
|
16202
|
+
async listSessionReplays(input) {
|
|
16203
|
+
if (this.replayStore === null) return { runs: [], skipped: 0 };
|
|
16204
|
+
const { runs, skipped } = await this.replayStore.list({ deviceId: input.deviceId, limit: 50 });
|
|
16205
|
+
if (skipped > 0) {
|
|
16206
|
+
this.ctx.logger.info("session replays: rows skipped that are not session replays", {
|
|
16207
|
+
tags: { deviceId: input.deviceId },
|
|
16208
|
+
meta: { skipped }
|
|
16209
|
+
});
|
|
16210
|
+
}
|
|
16504
16211
|
return {
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16212
|
+
runs: runs.map((r) => {
|
|
16213
|
+
const { timeline: _timeline, ...report } = r.report;
|
|
16214
|
+
return {
|
|
16215
|
+
id: r.id,
|
|
16216
|
+
deviceId: r.deviceId,
|
|
16217
|
+
nodeId: r.nodeId,
|
|
16218
|
+
createdAtMs: r.createdAtMs,
|
|
16219
|
+
report
|
|
16220
|
+
};
|
|
16221
|
+
}),
|
|
16222
|
+
skipped
|
|
16515
16223
|
};
|
|
16516
16224
|
}
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
* 1. Pin ONE frame in memory: resolve `frameId` (from `cacheBenchFrame`)
|
|
16522
|
-
* or the one-shot `imageBase64`, decode + square-resize ONCE to raw
|
|
16523
|
-
* RGB. Every node + every iteration consumes the IDENTICAL buffer — no
|
|
16524
|
-
* per-iteration decode or upload.
|
|
16525
|
-
* 2. For each target node, run a `concurrency`-bounded loop of
|
|
16526
|
-
* `runPipelineBatch` (batchSize frames/call) on the node's ROOT-ONLY
|
|
16527
|
-
* steps until `durationSec` elapses (or the stop flag fires). Root-only
|
|
16528
|
-
* steps stay on the batch single-root fast path == live `plane:'frame'`
|
|
16529
|
-
* root detection.
|
|
16530
|
-
* 3. Aggregate sustained fps / det/s + latency percentiles per node on the
|
|
16531
|
-
* identical pinned frame for apples-to-apples comparison.
|
|
16532
|
-
*
|
|
16533
|
-
* Non-destructive: targets the benchmark's own cached frame (never a live
|
|
16534
|
-
* camera session), is explicitly started, and is bounded by N + the stop
|
|
16535
|
-
* button (`abortSustainedThroughput`).
|
|
16536
|
-
*/
|
|
16537
|
-
async runSustainedThroughput(input) {
|
|
16538
|
-
const { frameId, imageBase64, durationSec, batchSize, concurrency, sessionId, nodes } = input;
|
|
16539
|
-
const targetSize = input.targetSize ?? 640;
|
|
16540
|
-
const cached2 = frameId ? this.frameCache.get(frameId) : void 0;
|
|
16541
|
-
if (frameId && !cached2)
|
|
16542
|
-
throw new Error(
|
|
16543
|
-
`runSustainedThroughput: frameId ${frameId} not in cache (expired or never cached)`
|
|
16544
|
-
);
|
|
16545
|
-
const jpegSource = cached2 ? cached2.jpeg : imageBase64 ? Buffer.from(imageBase64, "base64") : null;
|
|
16546
|
-
if (!jpegSource)
|
|
16547
|
-
throw new Error("runSustainedThroughput: no frame source (need frameId or imageBase64)");
|
|
16548
|
-
const sharp = (await import("sharp")).default;
|
|
16549
|
-
const rgb = await sharp(jpegSource).resize({ width: targetSize, height: targetSize, fit: "fill" }).raw().toColorspace("srgb").toBuffer();
|
|
16550
|
-
const rgbBytes = new Uint8Array(rgb.buffer, rgb.byteOffset, rgb.byteLength);
|
|
16551
|
-
const batchFrames = Array.from({ length: batchSize }, () => ({
|
|
16552
|
-
data: rgbBytes,
|
|
16553
|
-
format: "rgb",
|
|
16554
|
-
width: targetSize,
|
|
16555
|
-
height: targetSize,
|
|
16556
|
-
timestamp: Date.now()
|
|
16557
|
-
}));
|
|
16558
|
-
const benchFrameId = Math.floor(Math.random() * 4294967294) + 1 >>> 0;
|
|
16559
|
-
const abortFlag = { aborted: false };
|
|
16560
|
-
this.sustainedAbortFlags.set(sessionId, abortFlag);
|
|
16561
|
-
this.ctx.logger.info("runSustainedThroughput start", {
|
|
16562
|
-
meta: {
|
|
16563
|
-
sessionId,
|
|
16564
|
-
durationSec,
|
|
16565
|
-
batchSize,
|
|
16566
|
-
concurrency,
|
|
16567
|
-
targetSize,
|
|
16568
|
-
nodeCount: nodes.length,
|
|
16569
|
-
nodes: nodes.map((n) => n.nodeId ?? "hub"),
|
|
16570
|
-
deviceKeys: nodes.map((n) => n.deviceKey ?? "(default)")
|
|
16571
|
-
}
|
|
16572
|
-
});
|
|
16225
|
+
async persistReplay(runId, nodeId, outcome) {
|
|
16226
|
+
const store = this.replayStore;
|
|
16227
|
+
if (store === null) return;
|
|
16228
|
+
const tags = { deviceId: outcome.report.deviceId };
|
|
16573
16229
|
try {
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
...node.engine ? { engine: node.engine } : {},
|
|
16588
|
-
...node.deviceKey ? { deviceKey: node.deviceKey } : {}
|
|
16589
|
-
});
|
|
16590
|
-
return {
|
|
16591
|
-
frameCount: out.results.length,
|
|
16592
|
-
detectionCounts: out.results.map((r) => r.detections.length)
|
|
16593
|
-
};
|
|
16594
|
-
};
|
|
16595
|
-
let raw;
|
|
16596
|
-
try {
|
|
16597
|
-
raw = await runSustainedLoop({
|
|
16598
|
-
dispatchBatch,
|
|
16599
|
-
concurrency,
|
|
16600
|
-
durationMs: durationSec * 1e3,
|
|
16601
|
-
now: () => Date.now(),
|
|
16602
|
-
shouldStop: () => abortFlag.aborted
|
|
16603
|
-
});
|
|
16604
|
-
} finally {
|
|
16605
|
-
await this.api.pipelineExecutor.uncacheFrame.mutate({ nodeId: node.nodeId, frameId: benchFrameId }).catch(() => {
|
|
16606
|
-
});
|
|
16607
|
-
}
|
|
16608
|
-
const summary = {
|
|
16609
|
-
nodeId: node.nodeId ?? null,
|
|
16610
|
-
...node.label ? { label: node.label } : {},
|
|
16611
|
-
totalFrames: raw.totalFrames,
|
|
16612
|
-
totalBatches: raw.totalBatches,
|
|
16613
|
-
wallMs: raw.wallMs,
|
|
16614
|
-
fps: computeFps(raw.totalFrames, raw.wallMs),
|
|
16615
|
-
detsPerSec: computeDetsPerSec(raw.totalDetections, raw.wallMs),
|
|
16616
|
-
avgDetections: computeAvgDetections(raw.totalDetections, raw.detectionSamples),
|
|
16617
|
-
latency: computeLatencyStats(raw.callMsList),
|
|
16618
|
-
errors: raw.errors,
|
|
16619
|
-
aborted: raw.aborted
|
|
16620
|
-
};
|
|
16621
|
-
this.ctx.logger.info("runSustainedThroughput node done", {
|
|
16622
|
-
meta: {
|
|
16623
|
-
nodeId: summary.nodeId,
|
|
16624
|
-
fps: Number(summary.fps.toFixed(2)),
|
|
16625
|
-
detsPerSec: Number(summary.detsPerSec.toFixed(2)),
|
|
16626
|
-
p95Ms: Number(summary.latency.p95.toFixed(2)),
|
|
16627
|
-
totalFrames: summary.totalFrames,
|
|
16628
|
-
errors: summary.errors,
|
|
16629
|
-
aborted: summary.aborted
|
|
16630
|
-
}
|
|
16631
|
-
});
|
|
16632
|
-
return summary;
|
|
16633
|
-
})
|
|
16634
|
-
);
|
|
16635
|
-
return { perNode, durationSec, batchSize, concurrency };
|
|
16636
|
-
} finally {
|
|
16637
|
-
this.sustainedAbortFlags.delete(sessionId);
|
|
16230
|
+
await store.save({
|
|
16231
|
+
id: runId,
|
|
16232
|
+
deviceId: outcome.report.deviceId,
|
|
16233
|
+
nodeId,
|
|
16234
|
+
createdAtMs: Date.now(),
|
|
16235
|
+
report: outcome.report
|
|
16236
|
+
});
|
|
16237
|
+
await this.pruneReplays();
|
|
16238
|
+
} catch (err) {
|
|
16239
|
+
this.ctx.logger.warn("session replay: report NOT saved", {
|
|
16240
|
+
tags,
|
|
16241
|
+
meta: { runId, error: errMsg(err) }
|
|
16242
|
+
});
|
|
16638
16243
|
}
|
|
16639
16244
|
}
|
|
16640
|
-
async
|
|
16641
|
-
const
|
|
16642
|
-
if (
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
const dir = resolveFixturesDir();
|
|
16649
|
-
if (!fs2.existsSync(dir)) {
|
|
16650
|
-
this.ctx.logger.warn("decoder fixtures directory missing", { meta: { dir } });
|
|
16651
|
-
return [];
|
|
16652
|
-
}
|
|
16653
|
-
const out = [];
|
|
16654
|
-
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
16655
|
-
if (!entry.isFile()) continue;
|
|
16656
|
-
const meta3 = FIXTURE_META[entry.name];
|
|
16657
|
-
if (!meta3) continue;
|
|
16658
|
-
const full = path2.join(dir, entry.name);
|
|
16659
|
-
const stat = fs2.statSync(full);
|
|
16660
|
-
out.push({
|
|
16661
|
-
filename: entry.name,
|
|
16662
|
-
codec: meta3.codec,
|
|
16663
|
-
resolutionLabel: meta3.resolutionLabel,
|
|
16664
|
-
sizeBytes: stat.size,
|
|
16665
|
-
description: meta3.description
|
|
16666
|
-
});
|
|
16245
|
+
async pruneReplays() {
|
|
16246
|
+
const store = this.replayStore;
|
|
16247
|
+
if (store === null) return;
|
|
16248
|
+
try {
|
|
16249
|
+
const deleted = await store.pruneBefore(Date.now() - REPLAY_RETENTION_MS);
|
|
16250
|
+
if (deleted > 0) this.ctx.logger.info("session replays pruned", { meta: { deleted } });
|
|
16251
|
+
} catch (err) {
|
|
16252
|
+
this.ctx.logger.warn("session replays: prune failed", { meta: { error: errMsg(err) } });
|
|
16667
16253
|
}
|
|
16668
|
-
out.sort((a, b) => a.sizeBytes - b.sizeBytes);
|
|
16669
|
-
return out;
|
|
16670
16254
|
}
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16255
|
+
/**
|
|
16256
|
+
* The camera's own cooldown + pre-roll switch, read from the orchestrator's
|
|
16257
|
+
* hydrated device settings (the authority, not a copy). A failed read is
|
|
16258
|
+
* `{}` — the fleet defaults apply and the report says `default`.
|
|
16259
|
+
*/
|
|
16260
|
+
async cameraSessionSettings(deviceId) {
|
|
16674
16261
|
try {
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
logger: this.ctx.logger,
|
|
16679
|
-
fixturesDir: resolveFixturesDir(),
|
|
16680
|
-
abortFlag
|
|
16262
|
+
const raw = await this.api.addonSettings.getDeviceSettings.query({
|
|
16263
|
+
addonId: "pipeline-orchestrator",
|
|
16264
|
+
deviceId
|
|
16681
16265
|
});
|
|
16682
|
-
|
|
16683
|
-
|
|
16266
|
+
return readCameraSessionSettings(raw);
|
|
16267
|
+
} catch (err) {
|
|
16268
|
+
this.ctx.logger.warn("session replay: camera session settings unreadable \u2014 fleet defaults", {
|
|
16269
|
+
tags: { deviceId },
|
|
16270
|
+
meta: { error: errMsg(err) }
|
|
16271
|
+
});
|
|
16272
|
+
return {};
|
|
16684
16273
|
}
|
|
16685
16274
|
}
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
return {
|
|
16275
|
+
/** The replay's outside world — every call through `ctx.api`, node-pinned where it matters. */
|
|
16276
|
+
sessionReplayDeps() {
|
|
16277
|
+
const api = this.api;
|
|
16278
|
+
const profile = REPLAY_RECORDING_PROFILE;
|
|
16279
|
+
return {
|
|
16280
|
+
logger: this.ctx.logger,
|
|
16281
|
+
cameraSessionSettings: (deviceId) => this.cameraSessionSettings(deviceId),
|
|
16282
|
+
findMotionSession: async (deviceId, motionEventId, motionStartMs) => {
|
|
16283
|
+
const events = await api.pipelineAnalytics.getMotionEvents.query({
|
|
16284
|
+
deviceId,
|
|
16285
|
+
since: motionStartMs - 1,
|
|
16286
|
+
until: motionStartMs + 1,
|
|
16287
|
+
limit: 50,
|
|
16288
|
+
projection: "full"
|
|
16289
|
+
});
|
|
16290
|
+
return events.find((e) => e.id === motionEventId) ?? null;
|
|
16291
|
+
},
|
|
16292
|
+
resolveCameraTree: (deviceId, nodeId) => api.pipelineOrchestrator.resolvePipeline.query({ deviceId, agentNodeId: nodeId }),
|
|
16293
|
+
validate: (steps, nodeId, deviceKey) => api.pipelineExecutor.validatePipeline.query(
|
|
16294
|
+
{ steps: [...steps], ...deviceKey !== void 0 ? { deviceKey } : {} },
|
|
16295
|
+
nodePin(nodeId)
|
|
16296
|
+
),
|
|
16297
|
+
isRecording: async (deviceId) => {
|
|
16298
|
+
const config2 = await api.recordingArchive.getDeviceConfig.query({ deviceId });
|
|
16299
|
+
return config2.enabled !== false;
|
|
16300
|
+
},
|
|
16301
|
+
listStoredTracks: async (deviceId, sinceMs, untilMs) => api.pipelineAnalytics.listTracks.query({
|
|
16302
|
+
deviceId,
|
|
16303
|
+
since: sinceMs,
|
|
16304
|
+
until: untilMs,
|
|
16305
|
+
projection: "full",
|
|
16306
|
+
excludeSources: ["sensor", "audio"],
|
|
16307
|
+
limit: STORED_TRACKS_READ_LIMIT
|
|
16308
|
+
}),
|
|
16309
|
+
listBirthDecisions: (deviceId, sinceMs) => api.pipelineAnalytics.listBirthDecisions.query({
|
|
16310
|
+
deviceId,
|
|
16311
|
+
since: sinceMs,
|
|
16312
|
+
limit: REPLAY_BIRTHS_LIMIT
|
|
16313
|
+
}),
|
|
16314
|
+
readRecording: (deviceId, instants, stepMs) => readRecordingForInstants(
|
|
16315
|
+
{
|
|
16316
|
+
locateSegment: (epochMs) => api.recordingArchive.locateSegment.query({ deviceId, profile, epochMs }),
|
|
16317
|
+
readWindowBytes: (q) => api.recordingArchive.readWindowBytes.query({ deviceId, profile, ...q })
|
|
16318
|
+
},
|
|
16319
|
+
instants,
|
|
16320
|
+
stepMs
|
|
16321
|
+
),
|
|
16322
|
+
decode: async (bytes, args) => {
|
|
16323
|
+
const mjpeg = await this.replayDecoder.decodeSequence(
|
|
16324
|
+
buildReplaySequenceArgs(args),
|
|
16325
|
+
bytes,
|
|
16326
|
+
{
|
|
16327
|
+
timeoutMs: Math.max(
|
|
16328
|
+
REPLAY_DECODE_TIMEOUT_FLOOR_MS,
|
|
16329
|
+
Math.round(args.durationSec * REPLAY_DECODE_TIMEOUT_PER_SEC_MS)
|
|
16330
|
+
)
|
|
16331
|
+
}
|
|
16332
|
+
);
|
|
16333
|
+
return splitMjpegStream(mjpeg);
|
|
16334
|
+
},
|
|
16335
|
+
runRootDetector: (call) => api.pipelineExecutor.runPipeline.mutate({
|
|
16336
|
+
nodeId: call.nodeId,
|
|
16337
|
+
steps: [...call.steps],
|
|
16338
|
+
// A fresh `Uint8Array<ArrayBuffer>`: `mjpeg-split` hands back a
|
|
16339
|
+
// `Buffer` view, and the generated cap input is typed against the
|
|
16340
|
+
// narrower `ArrayBuffer` backing.
|
|
16341
|
+
image: new Uint8Array(call.image),
|
|
16342
|
+
deviceId: call.deviceId,
|
|
16343
|
+
plane: "frame",
|
|
16344
|
+
replay: true,
|
|
16345
|
+
...call.deviceKey !== void 0 ? { deviceKey: call.deviceKey } : {}
|
|
16346
|
+
})
|
|
16347
|
+
};
|
|
16691
16348
|
}
|
|
16692
16349
|
// ── History persistence (per-addon store on the hub) ──────────────────
|
|
16693
16350
|
/**
|
|
@@ -16695,9 +16352,7 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16695
16352
|
* VERBATIM: the value under `benchmarkHistory` is a JSON **string** (not
|
|
16696
16353
|
* the array itself), so the handle is typed over the string — the
|
|
16697
16354
|
* encode/decode + entry-level recovery below stays the codec, the handle
|
|
16698
|
-
* owns store access (retry budget + validated fallback).
|
|
16699
|
-
* non-string value falls back to the empty-history encoding, exactly the
|
|
16700
|
-
* `[]` the raw read produced.
|
|
16355
|
+
* owns store access (retry budget + validated fallback).
|
|
16701
16356
|
*/
|
|
16702
16357
|
historyState() {
|
|
16703
16358
|
return this.state(KEY_BENCHMARK_HISTORY, external_exports.string(), EMPTY_HISTORY_JSON);
|
|
@@ -16711,14 +16366,10 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16711
16366
|
const arr = Array.isArray(parsed) ? parsed : [];
|
|
16712
16367
|
const accepted = [];
|
|
16713
16368
|
let rejected = 0;
|
|
16714
|
-
for (const
|
|
16715
|
-
const
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
accepted.push(parsedEntry.data);
|
|
16719
|
-
} else {
|
|
16720
|
-
rejected += 1;
|
|
16721
|
-
}
|
|
16369
|
+
for (const entry2 of arr) {
|
|
16370
|
+
const parsedEntry = HistoryEntrySchema.safeParse(entry2);
|
|
16371
|
+
if (parsedEntry.success) accepted.push(parsedEntry.data);
|
|
16372
|
+
else rejected += 1;
|
|
16722
16373
|
}
|
|
16723
16374
|
if (rejected > 0) {
|
|
16724
16375
|
this.ctx.logger.warn("benchmark history: dropped unparseable entries", {
|
|
@@ -16734,13 +16385,10 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16734
16385
|
async writeHistory(history) {
|
|
16735
16386
|
await this.historyState().set(JSON.stringify(history));
|
|
16736
16387
|
}
|
|
16737
|
-
async
|
|
16738
|
-
return this.loadHistory();
|
|
16739
|
-
}
|
|
16740
|
-
async saveBenchmarkResult(entry) {
|
|
16388
|
+
async saveBenchmarkResult(entry2) {
|
|
16741
16389
|
const history = await this.loadHistory();
|
|
16742
16390
|
const saved = {
|
|
16743
|
-
...
|
|
16391
|
+
...entry2,
|
|
16744
16392
|
kind: "pipeline",
|
|
16745
16393
|
id: `bench-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
16746
16394
|
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -16750,19 +16398,6 @@ var BenchmarkAddon = class _BenchmarkAddon extends BaseAddon {
|
|
|
16750
16398
|
await this.writeHistory(next);
|
|
16751
16399
|
return saved;
|
|
16752
16400
|
}
|
|
16753
|
-
async saveDecoderPerformanceResult(entry) {
|
|
16754
|
-
const history = await this.loadHistory();
|
|
16755
|
-
const saved = {
|
|
16756
|
-
kind: "decoder-perf",
|
|
16757
|
-
id: `decoder-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
16758
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16759
|
-
...entry
|
|
16760
|
-
};
|
|
16761
|
-
const next = [saved, ...history];
|
|
16762
|
-
if (next.length > MAX_HISTORY_ENTRIES) next.length = MAX_HISTORY_ENTRIES;
|
|
16763
|
-
await this.writeHistory(next);
|
|
16764
|
-
return saved;
|
|
16765
|
-
}
|
|
16766
16401
|
async deleteBenchmarkResult(input) {
|
|
16767
16402
|
const history = (await this.loadHistory()).filter((e) => e.id !== input.id);
|
|
16768
16403
|
await this.writeHistory(history);
|