@camstack/addon-pipeline 1.2.201 → 1.2.203
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/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-nKgB-_mg.js → default-detection-model-CfIEdJop.js} +1 -1
- package/dist/{default-detection-model-fUMLQHB2.mjs → default-detection-model-ClY8fk-K.mjs} +1 -1
- package/dist/detection-pipeline/index.js +5 -5
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-D2jcEuZD.mjs → dist-B5x2ogXC.mjs} +19 -2
- package/dist/{dist-Bd875pfj.js → dist-Czm3CW8M.js} +19 -2
- package/dist/{lazy-sharp-B4KrLp0u.js → lazy-sharp-AhHg_Hb-.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-CmfXSgr3.mjs → node-Bx76sJbU.mjs} +1 -1
- package/dist/{node-dwhLC9Fh.js → node-u0M7kQe-.js} +1 -1
- package/dist/pipeline-runner/index.js +41 -26
- package/dist/pipeline-runner/index.mjs +40 -25
- package/dist/{process-memory-BDRJK5Vq.js → process-memory-D5JcMSDA.js} +1 -1
- package/dist/{process-memory-CH5qO7AX.mjs → process-memory-iiE4Q2mt.mjs} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/{segment-demux-js-BHTuEBhC.mjs → segment-demux-js-BcbB_hcJ.mjs} +1 -1
- package/dist/{segment-demux-js-cmfLROII.js → segment-demux-js-CSt1T2Vi.js} +1 -1
- package/dist/session-decode/decode-worker-child.js +234 -24
- package/dist/session-decode/decode-worker-child.mjs +233 -23
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DZcCQR5t.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BSWELhoJ.mjs} +3 -3
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-CZt5Lq_M.mjs → hostInit-BJlv_j8i.mjs} +3 -3
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol--PcFsRrE.js → worker-protocol-B4C4seRh.js} +1 -1
- package/dist/{worker-protocol-D4GyRNnY.mjs → worker-protocol-ClwpoGoP.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_worker_protocol = require("../worker-protocol
|
|
3
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
2
|
+
const require_worker_protocol = require("../worker-protocol-B4C4seRh.js");
|
|
3
|
+
const require_lazy_sharp = require("../lazy-sharp-AhHg_Hb-.js");
|
|
4
4
|
//#region src/session-decode/color-conversion.ts
|
|
5
5
|
/**
|
|
6
6
|
* Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
|
|
@@ -656,20 +656,6 @@ var NativeLeaseStore = class {
|
|
|
656
656
|
}
|
|
657
657
|
};
|
|
658
658
|
//#endregion
|
|
659
|
-
//#region src/session-decode/to-buffer-miss.ts
|
|
660
|
-
/** Which rung lost the frame, from what the worker knows at the miss. */
|
|
661
|
-
function toBufferMissRung(facts) {
|
|
662
|
-
if (facts.reservedFrameId !== null && facts.frameId > facts.reservedFrameId) return "not-yet-delivered";
|
|
663
|
-
if (!facts.marked) return "never-leased";
|
|
664
|
-
if (facts.leaseFrames >= facts.holdFrames) return "hold-full";
|
|
665
|
-
return "released";
|
|
666
|
-
}
|
|
667
|
-
/** The error line the runner sees: the rung first, the numbers after it. */
|
|
668
|
-
function describeToBufferMiss(facts) {
|
|
669
|
-
const rung = toBufferMissRung(facts);
|
|
670
|
-
return `decode-worker-child: toBuffer miss for frameId ${facts.frameId} — ${rung} {reserved=${facts.reservedFrameId ?? "none"} marked=${facts.marked} leaseFrames=${facts.leaseFrames}/${facts.holdFrames} holdOverflow=${facts.holdOverflow}${facts.unmarkedBy !== void 0 ? ` unmarkedBy=${facts.unmarkedBy}` : ""}}`;
|
|
671
|
-
}
|
|
672
|
-
//#endregion
|
|
673
659
|
//#region src/session-decode/native-tile-geometry.ts
|
|
674
660
|
/**
|
|
675
661
|
* Pure geometry for the **native subject tile** — the compressed, subject-sized
|
|
@@ -921,6 +907,127 @@ var NativeTileStore = class {
|
|
|
921
907
|
this.bytes -= entry.bytes;
|
|
922
908
|
}
|
|
923
909
|
};
|
|
910
|
+
/**
|
|
911
|
+
* The set of tile cuts currently between "I have this frame's pixels" and
|
|
912
|
+
* "the stores can answer for it", plus the latency of the ones that finished.
|
|
913
|
+
*/
|
|
914
|
+
var PendingCuts = class {
|
|
915
|
+
waitMs;
|
|
916
|
+
now;
|
|
917
|
+
latencySampleCap;
|
|
918
|
+
cuts = /* @__PURE__ */ new Map();
|
|
919
|
+
/** Insertion-ordered ring of completed cut latencies (ms). */
|
|
920
|
+
latencies = [];
|
|
921
|
+
constructor(options) {
|
|
922
|
+
this.waitMs = Math.max(0, options.waitMs);
|
|
923
|
+
this.now = options.now ?? Date.now;
|
|
924
|
+
this.latencySampleCap = Math.max(1, options.latencySamples ?? 256);
|
|
925
|
+
}
|
|
926
|
+
/** Cuts currently in flight (tests / metrics). */
|
|
927
|
+
get inFlight() {
|
|
928
|
+
return this.cuts.size;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* A cut for `frameId` has started. Call BEFORE the first `await` in the cut,
|
|
932
|
+
* so no request can observe the gap this module exists to cover.
|
|
933
|
+
*
|
|
934
|
+
* A duplicate id should never occur (frame ids are monotonic); settling the
|
|
935
|
+
* prior entry rather than replacing it keeps any waiter on it from hanging.
|
|
936
|
+
*/
|
|
937
|
+
begin(frameId) {
|
|
938
|
+
this.settle(frameId);
|
|
939
|
+
let finish = () => {};
|
|
940
|
+
const done = new Promise((resolve) => {
|
|
941
|
+
finish = resolve;
|
|
942
|
+
});
|
|
943
|
+
this.cuts.set(frameId, {
|
|
944
|
+
startedAt: this.now(),
|
|
945
|
+
done,
|
|
946
|
+
finish
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* The cut for `frameId` is over — the stores now hold whatever it produced.
|
|
951
|
+
* Call from the cut's `finally`, so a throwing encode releases waiters too.
|
|
952
|
+
* Idempotent.
|
|
953
|
+
*/
|
|
954
|
+
settle(frameId) {
|
|
955
|
+
const cut = this.cuts.get(frameId);
|
|
956
|
+
if (!cut) return;
|
|
957
|
+
this.cuts.delete(frameId);
|
|
958
|
+
this.noteLatency(this.now() - cut.startedAt);
|
|
959
|
+
cut.finish();
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Wait for `frameId`'s cut to land, bounded.
|
|
963
|
+
*
|
|
964
|
+
* `none` means there was nothing in flight, and the caller must NOT read that
|
|
965
|
+
* as "wait exhausted" — it is the ordinary answer for a frame whose cut has
|
|
966
|
+
* already finished or never ran.
|
|
967
|
+
*/
|
|
968
|
+
async waitFor(frameId) {
|
|
969
|
+
const cut = this.cuts.get(frameId);
|
|
970
|
+
if (!cut) return "none";
|
|
971
|
+
if (this.waitMs === 0) return "timeout";
|
|
972
|
+
let timer;
|
|
973
|
+
const expired = new Promise((resolve) => {
|
|
974
|
+
timer = setTimeout(() => resolve("timeout"), this.waitMs);
|
|
975
|
+
timer.unref?.();
|
|
976
|
+
});
|
|
977
|
+
try {
|
|
978
|
+
return await Promise.race([cut.done.then(() => "settled"), expired]);
|
|
979
|
+
} finally {
|
|
980
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Release every waiter and forget every in-flight cut — re-dial and teardown.
|
|
985
|
+
* A waiter left behind here would outlive the worker that could settle it.
|
|
986
|
+
*/
|
|
987
|
+
clear() {
|
|
988
|
+
for (const cut of this.cuts.values()) cut.finish();
|
|
989
|
+
this.cuts.clear();
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* The `percentile`-th cut latency in ms over the retained samples, or `null`
|
|
993
|
+
* when nothing has completed yet. `null` rather than 0: a worker that has cut
|
|
994
|
+
* nothing and a worker that cuts instantly must not read the same.
|
|
995
|
+
*/
|
|
996
|
+
latencyMs(percentile) {
|
|
997
|
+
if (this.latencies.length === 0) return null;
|
|
998
|
+
const sorted = [...this.latencies].sort((a, b) => a - b);
|
|
999
|
+
const clamped = Math.min(100, Math.max(0, percentile));
|
|
1000
|
+
const index = Math.min(sorted.length - 1, Math.ceil(clamped / 100 * sorted.length) - 1);
|
|
1001
|
+
return sorted[Math.max(0, index)] ?? null;
|
|
1002
|
+
}
|
|
1003
|
+
noteLatency(ms) {
|
|
1004
|
+
this.latencies.push(Math.max(0, ms));
|
|
1005
|
+
while (this.latencies.length > this.latencySampleCap) this.latencies.shift();
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
/**
|
|
1009
|
+
* Serve a frame's retained tiles, waiting for an in-flight cut ONLY when
|
|
1010
|
+
* waiting could change the answer.
|
|
1011
|
+
*
|
|
1012
|
+
* The order is the whole point. Anything already retained is served at once —
|
|
1013
|
+
* a cut in flight for a LATER subject of the same frame must not delay a
|
|
1014
|
+
* request the stores can already answer. Only an empty store consults
|
|
1015
|
+
* {@link PendingCuts}, and only then is a success attributable to the wait.
|
|
1016
|
+
*/
|
|
1017
|
+
async function attemptTileServe(pending, frameId, source) {
|
|
1018
|
+
if (source.hasRetained()) return {
|
|
1019
|
+
served: await source.serve(),
|
|
1020
|
+
wait: "none",
|
|
1021
|
+
resolvedByWait: false
|
|
1022
|
+
};
|
|
1023
|
+
const wait = await pending.waitFor(frameId);
|
|
1024
|
+
const served = await source.serve();
|
|
1025
|
+
return {
|
|
1026
|
+
served,
|
|
1027
|
+
wait,
|
|
1028
|
+
resolvedByWait: served !== null && wait === "settled"
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
924
1031
|
//#endregion
|
|
925
1032
|
//#region src/session-decode/planar-crop-source.ts
|
|
926
1033
|
/** Layout a detection `toBuffer` can read, or `null` when it must name the
|
|
@@ -950,6 +1057,19 @@ function nativeCropSourceTag(format) {
|
|
|
950
1057
|
case "rgb24": return "rgb:";
|
|
951
1058
|
}
|
|
952
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Bytes a crop of `rows` rows actually reads, measured from the crop's ORIGIN.
|
|
1062
|
+
*
|
|
1063
|
+
* The LAST row needs only its own `rowBytes`, never a whole stride — the stride
|
|
1064
|
+
* is the distance to the NEXT row, and for the last row there is no next row.
|
|
1065
|
+
* Demanding `stride * rows` instead overshoots the plane's end by exactly the
|
|
1066
|
+
* horizontal offset, which is invisible everywhere except where the crop's
|
|
1067
|
+
* bottom IS the frame's bottom. See {@link resolvePlanarCropSource}.
|
|
1068
|
+
*/
|
|
1069
|
+
function planarSpan(rows, stride, rowBytes) {
|
|
1070
|
+
if (rows <= 0) return 0;
|
|
1071
|
+
return (rows - 1) * stride + rowBytes;
|
|
1072
|
+
}
|
|
953
1073
|
/** A plane view starting at `offset`, or `null` when the plane is too short. */
|
|
954
1074
|
function viewAt(plane, offset, requiredSpan) {
|
|
955
1075
|
if (!plane) return null;
|
|
@@ -968,6 +1088,28 @@ function viewAt(plane, offset, requiredSpan) {
|
|
|
968
1088
|
* An odd origin is floored onto the chroma grid rather than rejected: the
|
|
969
1089
|
* geometry helper already aligns to even, and flooring reads the chroma sample
|
|
970
1090
|
* that CONTAINS the origin instead of inventing a half sample.
|
|
1091
|
+
*
|
|
1092
|
+
* ## The bottom edge (2026-09-07)
|
|
1093
|
+
*
|
|
1094
|
+
* The span demanded from each plane used to be `stride * region.height` from
|
|
1095
|
+
* the crop ORIGIN. A stride is the distance to the NEXT row, and the last row
|
|
1096
|
+
* of a crop has no next row — it needs only `left + width` bytes. So the demand
|
|
1097
|
+
* overshot the plane's end by exactly `left`, which is invisible everywhere
|
|
1098
|
+
* except when the crop's bottom IS the frame's last row. `resolveNativeTileRegion`
|
|
1099
|
+
* clamps a padded box to precisely that, and `clampEven` floors both `top` and
|
|
1100
|
+
* `height`, so a bottom-clamped tile lands flush every time.
|
|
1101
|
+
*
|
|
1102
|
+
* The result was that every subject standing near the camera — the biggest, the
|
|
1103
|
+
* closest, the best shot — lost its native tile, and the hold path threw for
|
|
1104
|
+
* the same rectangles. Measured over 6 h on the live cluster: **100 of 100**
|
|
1105
|
+
* logged `missing plane data` geometries had `top + height === frameHeight`
|
|
1106
|
+
* with `left > 0`, 101 of 102 on dev:590 (salone, 3840x2160).
|
|
1107
|
+
*
|
|
1108
|
+
* The check is now the region's real extent, so a crop that genuinely runs past
|
|
1109
|
+
* a plane — one row too tall, or a last row reaching past the stride — is still
|
|
1110
|
+
* refused. The views handed back are unchanged: `subarray(offset)` always ran
|
|
1111
|
+
* to the plane's true end, so nothing here ever sized a buffer, and tightening
|
|
1112
|
+
* the predicate cannot shorten one.
|
|
971
1113
|
*/
|
|
972
1114
|
function resolvePlanarCropSource(kind, planes, strides, region) {
|
|
973
1115
|
if (!planes || planes.length < PLANE_COUNT[kind]) return null;
|
|
@@ -978,10 +1120,11 @@ function resolvePlanarCropSource(kind, planes, strides, region) {
|
|
|
978
1120
|
const left = Math.max(0, region.left);
|
|
979
1121
|
const chromaRow = Math.floor(top / 2);
|
|
980
1122
|
const chromaHeight = Math.ceil(region.height / 2);
|
|
981
|
-
const
|
|
1123
|
+
const chromaWidth = Math.ceil(Math.max(0, region.width) / 2);
|
|
1124
|
+
const y = viewAt(planes[0], top * yStride + left, planarSpan(region.height, yStride, region.width));
|
|
982
1125
|
if (kind === "nv12") {
|
|
983
1126
|
const uvOffset = chromaRow * cStride + Math.floor(left / 2) * 2;
|
|
984
|
-
const uv = viewAt(planes[1], uvOffset, cStride *
|
|
1127
|
+
const uv = viewAt(planes[1], uvOffset, planarSpan(chromaHeight, cStride, chromaWidth * 2));
|
|
985
1128
|
if (!y || !uv) return null;
|
|
986
1129
|
return {
|
|
987
1130
|
planes: [y, uv],
|
|
@@ -990,8 +1133,8 @@ function resolvePlanarCropSource(kind, planes, strides, region) {
|
|
|
990
1133
|
}
|
|
991
1134
|
const vStride = strides?.[2] ?? cStride;
|
|
992
1135
|
const chromaOffset = chromaRow * cStride + Math.floor(left / 2);
|
|
993
|
-
const u = viewAt(planes[1], chromaOffset, cStride
|
|
994
|
-
const v = viewAt(planes[2], chromaRow * vStride + Math.floor(left / 2), vStride
|
|
1136
|
+
const u = viewAt(planes[1], chromaOffset, planarSpan(chromaHeight, cStride, chromaWidth));
|
|
1137
|
+
const v = viewAt(planes[2], chromaRow * vStride + Math.floor(left / 2), planarSpan(chromaHeight, vStride, chromaWidth));
|
|
995
1138
|
if (!y || !u || !v) return null;
|
|
996
1139
|
return {
|
|
997
1140
|
planes: [
|
|
@@ -1331,6 +1474,20 @@ async function cutSubjectTiles(frame, bboxes, ports) {
|
|
|
1331
1474
|
return tiles;
|
|
1332
1475
|
}
|
|
1333
1476
|
//#endregion
|
|
1477
|
+
//#region src/session-decode/to-buffer-miss.ts
|
|
1478
|
+
/** Which rung lost the frame, from what the worker knows at the miss. */
|
|
1479
|
+
function toBufferMissRung(facts) {
|
|
1480
|
+
if (facts.reservedFrameId !== null && facts.frameId > facts.reservedFrameId) return "not-yet-delivered";
|
|
1481
|
+
if (!facts.marked) return "never-leased";
|
|
1482
|
+
if (facts.leaseFrames >= facts.holdFrames) return "hold-full";
|
|
1483
|
+
return "released";
|
|
1484
|
+
}
|
|
1485
|
+
/** The error line the runner sees: the rung first, the numbers after it. */
|
|
1486
|
+
function describeToBufferMiss(facts) {
|
|
1487
|
+
const rung = toBufferMissRung(facts);
|
|
1488
|
+
return `decode-worker-child: toBuffer miss for frameId ${facts.frameId} — ${rung} {reserved=${facts.reservedFrameId ?? "none"} marked=${facts.marked} leaseFrames=${facts.leaseFrames}/${facts.holdFrames} holdOverflow=${facts.holdOverflow}${facts.unmarkedBy !== void 0 ? ` unmarkedBy=${facts.unmarkedBy}` : ""}}`;
|
|
1489
|
+
}
|
|
1490
|
+
//#endregion
|
|
1334
1491
|
//#region src/session-decode/decode-worker-child.ts
|
|
1335
1492
|
/** Wrap a software YUV420P frame as a lease entry (byte size ≈ w×h×1.5). */
|
|
1336
1493
|
function toLeasedNavFrame(frame) {
|
|
@@ -1458,6 +1615,28 @@ var NATIVE_TILE_MAX_PER_FRAME = (() => {
|
|
|
1458
1615
|
/** JPEG quality for a retained tile. High: this is a MODEL INPUT, not a preview. */
|
|
1459
1616
|
var NATIVE_TILE_JPEG_QUALITY = 90;
|
|
1460
1617
|
/**
|
|
1618
|
+
* How long a native-crop request waits for a tile cut that is still ENCODING.
|
|
1619
|
+
*
|
|
1620
|
+
* The cut reads its pixels synchronously at `FrameResult` time but only writes
|
|
1621
|
+
* the stores after N JPEG encodes; a request landing in between reads two empty
|
|
1622
|
+
* stores and is told `no retained tile for this frame` — the same sentence a
|
|
1623
|
+
* frame that was never cut gets. Measured 2026-09-07: misses onset at ~100 ms
|
|
1624
|
+
* (only 0.1% below it) and peak at 500-1000 ms, on cameras that cut a tile for
|
|
1625
|
+
* 99.7% of their inferred frames, so the window — not any lifetime — is what
|
|
1626
|
+
* they are landing in.
|
|
1627
|
+
*
|
|
1628
|
+
* 500 ms deliberately, not more: the confirmation gate bounds each crop attempt
|
|
1629
|
+
* at 300 ms and the capture scheduler drops an enrichment request older than
|
|
1630
|
+
* 3 s, so a miss held too long stops being a fast retryable `null` and becomes
|
|
1631
|
+
* an upstream timeout. Widen it via `CAMSTACK_SESSION_TILE_CUT_WAIT_MS` only
|
|
1632
|
+
* once `cutLatencyMs` on the metrics line says what the encode window really
|
|
1633
|
+
* is; `0` disables the wait and restores the pre-2026-09-07 miss profile.
|
|
1634
|
+
*/
|
|
1635
|
+
var TILE_CUT_WAIT_MS = (() => {
|
|
1636
|
+
const raw = Number(process.env["CAMSTACK_SESSION_TILE_CUT_WAIT_MS"]);
|
|
1637
|
+
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 500;
|
|
1638
|
+
})();
|
|
1639
|
+
/**
|
|
1461
1640
|
* Demand window (ms) for the lease-capture {@link LeaseActivityGate}: eager
|
|
1462
1641
|
* per-frame native downloads run only within this window of the last
|
|
1463
1642
|
* native-crop request (or dial start). `0` disables the GATE (legacy always-on
|
|
@@ -1598,6 +1777,14 @@ function errMessage(err) {
|
|
|
1598
1777
|
return String(err);
|
|
1599
1778
|
}
|
|
1600
1779
|
/**
|
|
1780
|
+
* Render a cut-latency percentile for the metrics line. `-` when nothing has
|
|
1781
|
+
* been cut yet: a worker that cut nothing and one that cuts in 0 ms must not
|
|
1782
|
+
* read the same, or the number that sizes {@link TILE_CUT_WAIT_MS} is a guess.
|
|
1783
|
+
*/
|
|
1784
|
+
function formatLatency(ms) {
|
|
1785
|
+
return ms === null ? "-" : String(Math.round(ms));
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1601
1788
|
* RTSP low-latency demuxer options — mirrors
|
|
1602
1789
|
* `addon-decoder-nodeav/src/pull-demuxer-options.ts` (duplicated here rather
|
|
1603
1790
|
* than cross-imported: this file is a standalone forked entry point, not an
|
|
@@ -1802,6 +1989,19 @@ var DecodeWorkerChild = class {
|
|
|
1802
1989
|
tileHits = 0;
|
|
1803
1990
|
tileMisses = 0;
|
|
1804
1991
|
tileCutFailures = 0;
|
|
1992
|
+
/**
|
|
1993
|
+
* Serves that produced pixels ONLY because the request waited for a cut that
|
|
1994
|
+
* was still encoding — i.e. misses before {@link TILE_CUT_WAIT_MS} existed.
|
|
1995
|
+
*
|
|
1996
|
+
* This is the counter the write-gap hypothesis stands or falls on. If it
|
|
1997
|
+
* stays near zero while `tileMisses` does not move, the misses were never a
|
|
1998
|
+
* timing race and the question becomes WHICH frames get cut at all.
|
|
1999
|
+
*/
|
|
2000
|
+
tileMissesResolvedByWait = 0;
|
|
2001
|
+
/** Requests that gave up on an in-flight cut at the bound — dropped work. */
|
|
2002
|
+
tileWaitTimeouts = 0;
|
|
2003
|
+
/** Cuts between "I have the pixels" and "the stores can answer". */
|
|
2004
|
+
pendingCuts = new PendingCuts({ waitMs: TILE_CUT_WAIT_MS });
|
|
1805
2005
|
/** Scene tiles cut / served, on the throughput line. */
|
|
1806
2006
|
scenesCut = 0;
|
|
1807
2007
|
sceneHits = 0;
|
|
@@ -2058,7 +2258,7 @@ var DecodeWorkerChild = class {
|
|
|
2058
2258
|
const skipped = this.framesSkipped + this.frames.droppedCount;
|
|
2059
2259
|
const rssMb = Math.round(process.memoryUsage().rss / 1048576);
|
|
2060
2260
|
const ageS = Math.round((now - this.startedAt) / 1e3);
|
|
2061
|
-
this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, leaseOffered:${this.admission.offered}, leaseAdmitted:${this.admission.admitted}, leaseMarks:${this.admission.marks}, leaseUnmarkedCrops:${this.admission.unmarkedCrops}, leaseAdmission:${NATIVE_LEASE_KNOBS.values.admission}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, holdOverflow:${this.leaseStore.overflowCount}, leaseReleased:${this.leaseReleased}, leaseReleaseMisses:${this.leaseReleaseMisses}, leaseReleasedEarly:${this.leaseReleasedEarly}, tilesCut:${this.tilesCut}, tileHits:${this.tileHits}, tileMisses:${this.tileMisses}, tileCutFailures:${this.tileCutFailures}, tileMb:${Math.round(this.tileStore.totalBytes / 1048576)}, tileFrames:${this.tileStore.frameCount}, scenesCut:${this.scenesCut}, sceneHits:${this.sceneHits}, sceneRefused:${this.sceneRefused}, sceneUnclassified:${this.sceneUnclassified}, sceneEvicted:${this.sceneStore.evictedCount}, sceneMb:${Math.round(this.sceneStore.totalBytes / 1048576)}, sceneFrames:${this.sceneStore.frameCount}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
|
|
2261
|
+
this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}, leaseOffered:${this.admission.offered}, leaseAdmitted:${this.admission.admitted}, leaseMarks:${this.admission.marks}, leaseUnmarkedCrops:${this.admission.unmarkedCrops}, leaseAdmission:${NATIVE_LEASE_KNOBS.values.admission}, leaseMb:${Math.round(this.leaseStore.totalBytes / 1048576)}, leaseFrames:${this.leaseStore.size}, holdOverflow:${this.leaseStore.overflowCount}, leaseReleased:${this.leaseReleased}, leaseReleaseMisses:${this.leaseReleaseMisses}, leaseReleasedEarly:${this.leaseReleasedEarly}, tilesCut:${this.tilesCut}, tileHits:${this.tileHits}, tileMisses:${this.tileMisses}, tileCutFailures:${this.tileCutFailures}, tileMissesResolvedByWait:${this.tileMissesResolvedByWait}, tileWaitTimeouts:${this.tileWaitTimeouts}, cutLatencyMsP50:${formatLatency(this.pendingCuts.latencyMs(50))}, cutLatencyMsP95:${formatLatency(this.pendingCuts.latencyMs(95))}, cutsInFlight:${this.pendingCuts.inFlight}, tileMb:${Math.round(this.tileStore.totalBytes / 1048576)}, tileFrames:${this.tileStore.frameCount}, scenesCut:${this.scenesCut}, sceneHits:${this.sceneHits}, sceneRefused:${this.sceneRefused}, sceneUnclassified:${this.sceneUnclassified}, sceneEvicted:${this.sceneStore.evictedCount}, sceneMb:${Math.round(this.sceneStore.totalBytes / 1048576)}, sceneFrames:${this.sceneStore.frameCount}, rssMb:${rssMb}, ageS:${ageS}}${final ? " (final)" : ""}\n`);
|
|
2062
2262
|
this.lastMetricsAt = now;
|
|
2063
2263
|
this.lastDeliveredSnapshot = this.framesDelivered;
|
|
2064
2264
|
if (!this.warnedLarge && rssMb >= WORKER_RSS_WARN_MB) {
|
|
@@ -2210,10 +2410,16 @@ var DecodeWorkerChild = class {
|
|
|
2210
2410
|
*/
|
|
2211
2411
|
async serveTileFallback(requestId, frameId, bbox, maxWidth) {
|
|
2212
2412
|
try {
|
|
2213
|
-
const
|
|
2413
|
+
const attempt = await attemptTileServe(this.pendingCuts, frameId, {
|
|
2414
|
+
hasRetained: () => this.tileStore.get(frameId).length > 0 || this.sceneStore.get(frameId).length > 0,
|
|
2415
|
+
serve: () => this.serveFromTile(frameId, bbox, maxWidth)
|
|
2416
|
+
});
|
|
2417
|
+
const served = attempt.served;
|
|
2418
|
+
if (attempt.wait === "timeout") this.tileWaitTimeouts += 1;
|
|
2214
2419
|
if (served) {
|
|
2215
2420
|
this.tileHits += 1;
|
|
2216
2421
|
this.nativeCropHits++;
|
|
2422
|
+
if (attempt.resolvedByWait) this.tileMissesResolvedByWait += 1;
|
|
2217
2423
|
this.send({
|
|
2218
2424
|
kind: "nativeCropResult",
|
|
2219
2425
|
requestId,
|
|
@@ -2229,7 +2435,7 @@ var DecodeWorkerChild = class {
|
|
|
2229
2435
|
this.send({
|
|
2230
2436
|
kind: "nativeCropMiss",
|
|
2231
2437
|
requestId,
|
|
2232
|
-
reason: this.tileMissReason(frameId)
|
|
2438
|
+
reason: this.tileMissReason(frameId, attempt.wait)
|
|
2233
2439
|
});
|
|
2234
2440
|
} catch (err) {
|
|
2235
2441
|
this.tileMisses += 1;
|
|
@@ -2249,7 +2455,8 @@ var DecodeWorkerChild = class {
|
|
|
2249
2455
|
* "the rectangle is outside every container" are three different sentences,
|
|
2250
2456
|
* and one "not retained" hid all of them.
|
|
2251
2457
|
*/
|
|
2252
|
-
tileMissReason(frameId) {
|
|
2458
|
+
tileMissReason(frameId, wait) {
|
|
2459
|
+
if (wait === "timeout") return `frame ${frameId} released and its tile cut did not finish within ${TILE_CUT_WAIT_MS} ms — the request outran the encode, not the retention`;
|
|
2253
2460
|
if (!this.tileStore.enabled && !this.sceneStore.enabled) return `frame ${frameId} released and retention is DISABLED (tileBudgetMb 0, sceneBudgetMb 0)`;
|
|
2254
2461
|
if (!this.sceneStore.enabled) return `frame ${frameId} released and no retained subject tile contains the requested rectangle; scene tiles are DISABLED (sceneBudgetMb 0), so a full-frame request cannot be served at all`;
|
|
2255
2462
|
if (!this.tileStore.enabled) return `frame ${frameId} released and subject tiles are DISABLED (tileBudgetMb 0)`;
|
|
@@ -2281,6 +2488,7 @@ var DecodeWorkerChild = class {
|
|
|
2281
2488
|
* explanation, which is the failure this whole redesign exists to remove.
|
|
2282
2489
|
*/
|
|
2283
2490
|
async handleCutTiles(frameId, bboxes) {
|
|
2491
|
+
this.pendingCuts.begin(frameId);
|
|
2284
2492
|
try {
|
|
2285
2493
|
if (!this.tileStore.enabled && !this.sceneStore.enabled || bboxes.length === 0) return;
|
|
2286
2494
|
const wanted = this.tileStore.enabled ? bboxes.slice(0, NATIVE_TILE_MAX_PER_FRAME) : [];
|
|
@@ -2362,6 +2570,7 @@ var DecodeWorkerChild = class {
|
|
|
2362
2570
|
this.sceneStore.put(frameId, [scene]);
|
|
2363
2571
|
}
|
|
2364
2572
|
} finally {
|
|
2573
|
+
this.pendingCuts.settle(frameId);
|
|
2365
2574
|
this.noteLeaseRelease(frameId, this.leaseStore.release(frameId), "cut");
|
|
2366
2575
|
}
|
|
2367
2576
|
}
|
|
@@ -3144,6 +3353,7 @@ var DecodeWorkerChild = class {
|
|
|
3144
3353
|
this.leaseStore.clear();
|
|
3145
3354
|
this.tileStore.clear();
|
|
3146
3355
|
this.sceneStore.clear();
|
|
3356
|
+
this.pendingCuts.clear();
|
|
3147
3357
|
this.tileFrameWidth = 0;
|
|
3148
3358
|
this.tileFrameHeight = 0;
|
|
3149
3359
|
this.nativeLeaseDownloadFilter?.close();
|