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