@hyperframes/producer 0.8.203 → 0.8.204
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/distributed.js +379 -30
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +17 -17
- package/dist/index.js +179 -71
- package/dist/public-server.js +149 -48
- package/dist/services/render/observability.d.ts +23 -1
- package/dist/services/render/stages/captureStage.d.ts +3 -0
- package/dist/services/renderOrchestrator.d.ts +6 -1
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -1335,6 +1335,12 @@ import {
|
|
|
1335
1335
|
prepareCaptureSessionForReuse,
|
|
1336
1336
|
spawnStreamingEncoder
|
|
1337
1337
|
} from "@hyperframes/engine";
|
|
1338
|
+
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
1339
|
+
function resolveCaptureStallTimeoutMs() {
|
|
1340
|
+
const raw = process.env.HF_DE_STALL_MS ?? process.env.HF_DE_PARALLEL_STALL_MS;
|
|
1341
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1342
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CAPTURE_STALL_MS;
|
|
1343
|
+
}
|
|
1338
1344
|
|
|
1339
1345
|
// src/services/render/segmentManifest.ts
|
|
1340
1346
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
@@ -1442,9 +1448,325 @@ async function measureCaptureCostFromSession(session, totalFrames, fps, log) {
|
|
|
1442
1448
|
|
|
1443
1449
|
// src/services/render/observability.ts
|
|
1444
1450
|
import { redactTelemetryString } from "@hyperframes/core";
|
|
1451
|
+
import { CaptureFailure as CaptureFailure2 } from "@hyperframes/engine";
|
|
1452
|
+
var MAX_EVENTS = 160;
|
|
1453
|
+
var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
1454
|
+
"browserGpuMode",
|
|
1455
|
+
"captureMode",
|
|
1456
|
+
"captureOperation",
|
|
1457
|
+
"compositionHash",
|
|
1458
|
+
"effectiveHdr",
|
|
1459
|
+
"format",
|
|
1460
|
+
"quality",
|
|
1461
|
+
"renderJobId",
|
|
1462
|
+
"requestedHdrMode",
|
|
1463
|
+
"requestedWorkers",
|
|
1464
|
+
// "calibrating" during pre-capture browser warm-up / calibration stages;
|
|
1465
|
+
// "capturing" during capture_disk / capture_streaming / capture_hdr_*
|
|
1466
|
+
// stages. Distinguishes healthy 0-frame heartbeats (browser starting up)
|
|
1467
|
+
// from actual zero-frame stalls once capture is meant to be underway.
|
|
1468
|
+
// Field signal ts=1784019503.
|
|
1469
|
+
"stagePhase",
|
|
1470
|
+
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
1471
|
+
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
1472
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
1473
|
+
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
1474
|
+
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
1475
|
+
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
1476
|
+
// characterize per-frame perf by the exact trigger. Complementary to
|
|
1477
|
+
// `deGateReason`, which is the low-cardinality bucket for aggregation —
|
|
1478
|
+
// this string preserves the fine-grained "which CSS property" for
|
|
1479
|
+
// diagnostic reads. Sanitized like any observation message; low-cardinality
|
|
1480
|
+
// by construction (bounded by the fallback-trigger enumeration).
|
|
1481
|
+
"triggerReason"
|
|
1482
|
+
]);
|
|
1483
|
+
var RESERVED_LOG_KEYS = /* @__PURE__ */ new Set([
|
|
1484
|
+
"data",
|
|
1485
|
+
"durationMs",
|
|
1486
|
+
"elapsedMs",
|
|
1487
|
+
"message",
|
|
1488
|
+
"phase",
|
|
1489
|
+
"renderJobId",
|
|
1490
|
+
"status"
|
|
1491
|
+
]);
|
|
1492
|
+
function sanitizeObservationMessage(value) {
|
|
1493
|
+
return redactTelemetryString(value);
|
|
1494
|
+
}
|
|
1495
|
+
function sanitizeObservationData(data) {
|
|
1496
|
+
if (!data) return void 0;
|
|
1497
|
+
const sanitized = {};
|
|
1498
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1499
|
+
if (RESERVED_LOG_KEYS.has(key)) continue;
|
|
1500
|
+
if (typeof value === "string") {
|
|
1501
|
+
if (!ALLOWED_STRING_DATA_KEYS.has(key)) continue;
|
|
1502
|
+
sanitized[key] = sanitizeObservationMessage(value);
|
|
1503
|
+
} else {
|
|
1504
|
+
sanitized[key] = value;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return sanitized;
|
|
1508
|
+
}
|
|
1509
|
+
function isHttpErrorDiagnostic(line) {
|
|
1510
|
+
return /\[Browser:HTTP\d{3}\]/.test(line);
|
|
1511
|
+
}
|
|
1512
|
+
function readUnsignedIntAfter(line, prefix) {
|
|
1513
|
+
const start = line.indexOf(prefix);
|
|
1514
|
+
if (start < 0) return void 0;
|
|
1515
|
+
let value = 0;
|
|
1516
|
+
let digits = 0;
|
|
1517
|
+
for (let i = start + prefix.length; i < line.length; i++) {
|
|
1518
|
+
const code = line.charCodeAt(i);
|
|
1519
|
+
if (code < 48 || code > 57) break;
|
|
1520
|
+
value = value * 10 + code - 48;
|
|
1521
|
+
digits += 1;
|
|
1522
|
+
if (value > Number.MAX_SAFE_INTEGER) return void 0;
|
|
1523
|
+
}
|
|
1524
|
+
return digits > 0 ? value : void 0;
|
|
1525
|
+
}
|
|
1526
|
+
function maxReading(current, next) {
|
|
1527
|
+
if (next === void 0) return current;
|
|
1528
|
+
return current === void 0 ? next : Math.max(current, next);
|
|
1529
|
+
}
|
|
1530
|
+
function summarizeInitObservability(lines, fallback) {
|
|
1531
|
+
let initDurationMs = fallback?.initDurationMs;
|
|
1532
|
+
let tweenCount = fallback?.tweenCount;
|
|
1533
|
+
let elementCount = fallback?.elementCount;
|
|
1534
|
+
for (const line of lines) {
|
|
1535
|
+
if (!line.includes("[FrameCapture:INIT]")) continue;
|
|
1536
|
+
initDurationMs = maxReading(initDurationMs, readUnsignedIntAfter(line, "initDurationMs="));
|
|
1537
|
+
tweenCount = maxReading(tweenCount, readUnsignedIntAfter(line, "tweenCount="));
|
|
1538
|
+
elementCount = maxReading(elementCount, readUnsignedIntAfter(line, "elementCount="));
|
|
1539
|
+
}
|
|
1540
|
+
if (initDurationMs === void 0 && tweenCount === void 0 && elementCount === void 0) {
|
|
1541
|
+
return void 0;
|
|
1542
|
+
}
|
|
1543
|
+
return { initDurationMs, tweenCount, elementCount };
|
|
1544
|
+
}
|
|
1545
|
+
function summarizeBrowserDiagnostics(lines) {
|
|
1546
|
+
let errors = 0;
|
|
1547
|
+
let pageErrors = 0;
|
|
1548
|
+
let requestFailed = 0;
|
|
1549
|
+
let httpErrors = 0;
|
|
1550
|
+
let navigationStarts = 0;
|
|
1551
|
+
let navigationFailures = 0;
|
|
1552
|
+
let consoleErrors = 0;
|
|
1553
|
+
let consoleWarnings = 0;
|
|
1554
|
+
for (const line of lines) {
|
|
1555
|
+
const isPageError = line.includes("PAGEERROR");
|
|
1556
|
+
const isRequestFailed = line.includes("REQUESTFAILED");
|
|
1557
|
+
const isHttpError = isHttpErrorDiagnostic(line);
|
|
1558
|
+
const isNavigationFailure = line.includes("[FrameCapture:ERROR] page.goto failed");
|
|
1559
|
+
const isConsoleError = line.includes("[error]");
|
|
1560
|
+
if (isPageError) pageErrors++;
|
|
1561
|
+
if (isRequestFailed) requestFailed++;
|
|
1562
|
+
if (isHttpError) httpErrors++;
|
|
1563
|
+
if (line.includes("[FrameCapture:NAV] page.goto start")) navigationStarts++;
|
|
1564
|
+
if (isNavigationFailure) navigationFailures++;
|
|
1565
|
+
if (isConsoleError) consoleErrors++;
|
|
1566
|
+
if (line.includes("[warn]")) consoleWarnings++;
|
|
1567
|
+
if (line.includes("ERROR") && !isPageError && !isRequestFailed && !isHttpError && !isNavigationFailure && !isConsoleError) {
|
|
1568
|
+
errors++;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return {
|
|
1572
|
+
total: lines.length,
|
|
1573
|
+
errors,
|
|
1574
|
+
pageErrors,
|
|
1575
|
+
requestFailed,
|
|
1576
|
+
httpErrors,
|
|
1577
|
+
navigationStarts,
|
|
1578
|
+
navigationFailures,
|
|
1579
|
+
consoleErrors,
|
|
1580
|
+
consoleWarnings
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
var RenderObservabilityRecorder = class {
|
|
1584
|
+
constructor(input) {
|
|
1585
|
+
this.input = input;
|
|
1586
|
+
}
|
|
1587
|
+
input;
|
|
1588
|
+
events = [];
|
|
1589
|
+
eventCount = 0;
|
|
1590
|
+
failedPhase;
|
|
1591
|
+
checkpoint(phase, message, data) {
|
|
1592
|
+
return this.record({
|
|
1593
|
+
phase,
|
|
1594
|
+
status: "checkpoint",
|
|
1595
|
+
elapsedMs: Date.now() - this.input.pipelineStartMs,
|
|
1596
|
+
message: sanitizeObservationMessage(message),
|
|
1597
|
+
data: sanitizeObservationData(data)
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
stageStart(phase, data) {
|
|
1601
|
+
this.record({
|
|
1602
|
+
phase,
|
|
1603
|
+
status: "start",
|
|
1604
|
+
elapsedMs: Date.now() - this.input.pipelineStartMs,
|
|
1605
|
+
data: sanitizeObservationData(data)
|
|
1606
|
+
});
|
|
1607
|
+
return Date.now();
|
|
1608
|
+
}
|
|
1609
|
+
stageEnd(phase, startedAtMs, data) {
|
|
1610
|
+
this.record({
|
|
1611
|
+
phase,
|
|
1612
|
+
status: "end",
|
|
1613
|
+
elapsedMs: Date.now() - this.input.pipelineStartMs,
|
|
1614
|
+
durationMs: Date.now() - startedAtMs,
|
|
1615
|
+
data: sanitizeObservationData(data)
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
stageError(phase, startedAtMs, error, data) {
|
|
1619
|
+
this.failedPhase = phase;
|
|
1620
|
+
this.record({
|
|
1621
|
+
phase,
|
|
1622
|
+
status: "error",
|
|
1623
|
+
elapsedMs: Date.now() - this.input.pipelineStartMs,
|
|
1624
|
+
durationMs: Date.now() - startedAtMs,
|
|
1625
|
+
message: sanitizeObservationMessage(normalizeErrorMessage(error)),
|
|
1626
|
+
data: sanitizeObservationData(data)
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
summary(input) {
|
|
1630
|
+
const lastEvent = this.events[this.events.length - 1];
|
|
1631
|
+
return {
|
|
1632
|
+
renderJobId: this.input.renderJobId,
|
|
1633
|
+
compositionHash: input.compositionHash,
|
|
1634
|
+
events: this.events.slice(),
|
|
1635
|
+
eventCount: this.eventCount,
|
|
1636
|
+
lastEvent,
|
|
1637
|
+
failedPhase: this.failedPhase,
|
|
1638
|
+
browserDiagnostics: summarizeBrowserDiagnostics(input.lastBrowserConsole),
|
|
1639
|
+
capture: { ...input.capture },
|
|
1640
|
+
extraction: input.extraction ? { ...input.extraction } : void 0,
|
|
1641
|
+
init: summarizeInitObservability(input.lastBrowserConsole, input.initFallback)
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
hasFailure() {
|
|
1645
|
+
return this.failedPhase !== void 0;
|
|
1646
|
+
}
|
|
1647
|
+
/** A phase failure that was subsequently recovered (e.g. the drawElement
|
|
1648
|
+
* self-verify fallback re-rendering via screenshot) should not brand the
|
|
1649
|
+
* whole render as failed in the summary. */
|
|
1650
|
+
clearFailure(phase) {
|
|
1651
|
+
if (this.failedPhase === phase) this.failedPhase = void 0;
|
|
1652
|
+
}
|
|
1653
|
+
record(event) {
|
|
1654
|
+
this.eventCount++;
|
|
1655
|
+
const eventWithJob = { ...event, renderJobId: this.input.renderJobId };
|
|
1656
|
+
this.events.push(eventWithJob);
|
|
1657
|
+
if (this.events.length > MAX_EVENTS) {
|
|
1658
|
+
this.events.shift();
|
|
1659
|
+
}
|
|
1660
|
+
this.input.log.info("[Render:trace]", {
|
|
1661
|
+
renderJobId: eventWithJob.renderJobId,
|
|
1662
|
+
phase: eventWithJob.phase,
|
|
1663
|
+
status: eventWithJob.status,
|
|
1664
|
+
elapsedMs: eventWithJob.elapsedMs,
|
|
1665
|
+
durationMs: eventWithJob.durationMs,
|
|
1666
|
+
message: eventWithJob.message,
|
|
1667
|
+
...eventWithJob.data
|
|
1668
|
+
});
|
|
1669
|
+
return eventWithJob;
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1445
1672
|
var HEARTBEAT_RAMP_MS = [3e4, 6e4, 12e4];
|
|
1446
1673
|
var HEARTBEAT_REPEAT_MS = 12e4;
|
|
1447
1674
|
var HEARTBEAT_RAMP_END_MS = HEARTBEAT_RAMP_MS[HEARTBEAT_RAMP_MS.length - 1] ?? HEARTBEAT_REPEAT_MS;
|
|
1675
|
+
function heartbeatTargetMs(index) {
|
|
1676
|
+
const rampTarget = HEARTBEAT_RAMP_MS[index];
|
|
1677
|
+
if (rampTarget !== void 0) return rampTarget;
|
|
1678
|
+
const overflow = index - HEARTBEAT_RAMP_MS.length + 1;
|
|
1679
|
+
return HEARTBEAT_RAMP_END_MS + overflow * HEARTBEAT_REPEAT_MS;
|
|
1680
|
+
}
|
|
1681
|
+
var RenderStageStalledError = class extends CaptureFailure2 {
|
|
1682
|
+
code = "RENDER_STAGE_STALLED";
|
|
1683
|
+
owner = "system";
|
|
1684
|
+
retryable = false;
|
|
1685
|
+
constructor(phase, silentMs, progress) {
|
|
1686
|
+
super({
|
|
1687
|
+
kind: "protocol_timeout",
|
|
1688
|
+
message: `[Render] ${phase} stalled: no progress for ${silentMs}ms (stuck at ${progress}).`
|
|
1689
|
+
});
|
|
1690
|
+
this.name = "RenderStageStalledError";
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
var STALL_POLL_MS = 5e3;
|
|
1694
|
+
function watchStall(phase, rule) {
|
|
1695
|
+
let last = rule.progress();
|
|
1696
|
+
let budgetMs = rule.firstProgressMs;
|
|
1697
|
+
let lastChangeAt = Date.now();
|
|
1698
|
+
let attempt;
|
|
1699
|
+
let timer;
|
|
1700
|
+
const restartWindow = (ms) => {
|
|
1701
|
+
last = rule.progress();
|
|
1702
|
+
budgetMs = ms;
|
|
1703
|
+
lastChangeAt = Date.now();
|
|
1704
|
+
};
|
|
1705
|
+
const tripped = new Promise((_, reject) => {
|
|
1706
|
+
timer = setInterval(
|
|
1707
|
+
() => {
|
|
1708
|
+
const current = rule.progress();
|
|
1709
|
+
if (current !== last) return restartWindow(rule.noProgressMs);
|
|
1710
|
+
if (Date.now() - lastChangeAt < budgetMs) return;
|
|
1711
|
+
const error = new RenderStageStalledError(phase, budgetMs, current);
|
|
1712
|
+
if (attempt && !attempt.signal.aborted) {
|
|
1713
|
+
attempt.abort(error);
|
|
1714
|
+
return restartWindow(rule.noProgressMs);
|
|
1715
|
+
}
|
|
1716
|
+
clearInterval(timer);
|
|
1717
|
+
reject(error);
|
|
1718
|
+
},
|
|
1719
|
+
Math.min(STALL_POLL_MS, rule.noProgressMs, rule.firstProgressMs)
|
|
1720
|
+
);
|
|
1721
|
+
});
|
|
1722
|
+
const openAttempt = (parent) => {
|
|
1723
|
+
attempt = new AbortController();
|
|
1724
|
+
restartWindow(rule.firstProgressMs);
|
|
1725
|
+
return parent ? AbortSignal.any([parent, attempt.signal]) : attempt.signal;
|
|
1726
|
+
};
|
|
1727
|
+
const stalledBy = () => attempt?.signal.aborted ? attempt.signal.reason : void 0;
|
|
1728
|
+
return { tripped, openAttempt, stalledBy, stop: () => clearInterval(timer) };
|
|
1729
|
+
}
|
|
1730
|
+
var openUnwatchedAttempt = (parent) => parent ?? new AbortController().signal;
|
|
1731
|
+
async function observeRenderStage(recorder, phase, data, fn, options = {}) {
|
|
1732
|
+
const heartbeatMessage = options.heartbeatMessage ?? "stage still running";
|
|
1733
|
+
const startedAt = recorder.stageStart(phase, data);
|
|
1734
|
+
let heartbeatCount = 0;
|
|
1735
|
+
let lastFiredAtMs = 0;
|
|
1736
|
+
let heartbeatTimer;
|
|
1737
|
+
const scheduleNextHeartbeat = () => {
|
|
1738
|
+
const targetMs = heartbeatTargetMs(heartbeatCount);
|
|
1739
|
+
heartbeatTimer = setTimeout(() => {
|
|
1740
|
+
lastFiredAtMs = targetMs;
|
|
1741
|
+
heartbeatCount += 1;
|
|
1742
|
+
recorder.checkpoint(phase, heartbeatMessage, {
|
|
1743
|
+
...data,
|
|
1744
|
+
heartbeatIndex: heartbeatCount,
|
|
1745
|
+
stageElapsedMs: Date.now() - startedAt
|
|
1746
|
+
});
|
|
1747
|
+
scheduleNextHeartbeat();
|
|
1748
|
+
}, targetMs - lastFiredAtMs);
|
|
1749
|
+
heartbeatTimer.unref?.();
|
|
1750
|
+
};
|
|
1751
|
+
scheduleNextHeartbeat();
|
|
1752
|
+
const stall = options.stall ? watchStall(phase, options.stall) : void 0;
|
|
1753
|
+
const clearHeartbeats = () => {
|
|
1754
|
+
clearTimeout(heartbeatTimer);
|
|
1755
|
+
stall?.stop();
|
|
1756
|
+
};
|
|
1757
|
+
try {
|
|
1758
|
+
const run = fn(stall?.openAttempt ?? openUnwatchedAttempt);
|
|
1759
|
+
const result = await (stall ? Promise.race([run, stall.tripped]) : run);
|
|
1760
|
+
clearHeartbeats();
|
|
1761
|
+
recorder.stageEnd(phase, startedAt, data);
|
|
1762
|
+
return result;
|
|
1763
|
+
} catch (caught) {
|
|
1764
|
+
const error = stall?.stalledBy() ?? caught;
|
|
1765
|
+
clearHeartbeats();
|
|
1766
|
+
recorder.stageError(phase, startedAt, error, data);
|
|
1767
|
+
throw error;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1448
1770
|
|
|
1449
1771
|
// src/services/render/videoFrameCoverage.ts
|
|
1450
1772
|
import { parseHTML } from "linkedom";
|
|
@@ -3477,9 +3799,10 @@ async function discoverMediaFromBrowser(page) {
|
|
|
3477
3799
|
const tagName = isImage ? "image" : htmlEl.tagName.toLowerCase() === "video" ? "video" : "audio";
|
|
3478
3800
|
const id = htmlEl.getAttribute("data-hf-render-id") || htmlEl.id || (isImage ? autoImageIds.get(htmlEl) : void 0);
|
|
3479
3801
|
if (!id) return;
|
|
3480
|
-
const
|
|
3481
|
-
const
|
|
3482
|
-
const
|
|
3802
|
+
const resolvedSrc = htmlEl.currentSrc || htmlEl.src || htmlEl.getAttribute("src") || "";
|
|
3803
|
+
const withoutQueryAndHash = (url) => url.replace(/[?#][\s\S]*$/, "");
|
|
3804
|
+
const isPageItself = withoutQueryAndHash(resolvedSrc) === withoutQueryAndHash(location.href);
|
|
3805
|
+
const src = isPageItself ? "" : resolvedSrc;
|
|
3483
3806
|
const start = parseFloat(htmlEl.getAttribute("data-start") || "0");
|
|
3484
3807
|
const endRaw = htmlEl.getAttribute("data-end");
|
|
3485
3808
|
const durationRaw = htmlEl.getAttribute("data-duration");
|
|
@@ -5793,6 +6116,7 @@ async function runCaptureStage(input) {
|
|
|
5793
6116
|
interleaveFrames: input.interleaveFrames,
|
|
5794
6117
|
dedupPerfs,
|
|
5795
6118
|
pageErrorLines: workerPageErrors,
|
|
6119
|
+
openAttempt: input.openAttempt,
|
|
5796
6120
|
onProgress: (progress) => {
|
|
5797
6121
|
if (progress.latestWorkerPhase) {
|
|
5798
6122
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -5864,6 +6188,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
5864
6188
|
let transientRetriesUsed = 0;
|
|
5865
6189
|
let resumeFrom = 0;
|
|
5866
6190
|
while (true) {
|
|
6191
|
+
const attempt = input.openAttempt?.();
|
|
6192
|
+
const assertAttemptActive = () => {
|
|
6193
|
+
assertNotAborted();
|
|
6194
|
+
attempt?.throwIfAborted();
|
|
6195
|
+
};
|
|
5867
6196
|
const videoInjector = createRenderVideoFrameInjector();
|
|
5868
6197
|
const session = probeSession ?? await createCaptureSession5(
|
|
5869
6198
|
fileServer.url,
|
|
@@ -5879,10 +6208,10 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
5879
6208
|
probeSession = null;
|
|
5880
6209
|
}
|
|
5881
6210
|
await ensureSessionReady(session);
|
|
5882
|
-
|
|
6211
|
+
assertAttemptActive();
|
|
5883
6212
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
5884
6213
|
await captureSessionFrames(session, {
|
|
5885
|
-
input,
|
|
6214
|
+
input: { ...input, assertNotAborted: assertAttemptActive },
|
|
5886
6215
|
rangeStart,
|
|
5887
6216
|
rangeEnd,
|
|
5888
6217
|
resumeFrom,
|
|
@@ -6566,7 +6895,17 @@ function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
|
6566
6895
|
function captureAttemptMadeProgress(attemptTargetFrameCount, remainingFrameCount) {
|
|
6567
6896
|
return remainingFrameCount < attemptTargetFrameCount;
|
|
6568
6897
|
}
|
|
6898
|
+
var FIRST_FRAME_STALL_MS = 3e5;
|
|
6899
|
+
function frameStallRule(job) {
|
|
6900
|
+
const noProgressMs = resolveCaptureStallTimeoutMs();
|
|
6901
|
+
return {
|
|
6902
|
+
progress: () => job.framesRendered ?? 0,
|
|
6903
|
+
noProgressMs,
|
|
6904
|
+
firstProgressMs: Math.max(FIRST_FRAME_STALL_MS, noProgressMs)
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6569
6907
|
function isRecoverableParallelCaptureError(error) {
|
|
6908
|
+
if (error instanceof RenderStageStalledError) return true;
|
|
6570
6909
|
const message = normalizeErrorMessage(error);
|
|
6571
6910
|
if (!message.includes("[Parallel] Capture failed")) return false;
|
|
6572
6911
|
const kind = classifyCaptureFailure3(error).kind;
|
|
@@ -6618,8 +6957,10 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6618
6957
|
)
|
|
6619
6958
|
];
|
|
6620
6959
|
options.dedupPerfs.length = 0;
|
|
6960
|
+
let attemptSignal = options.abortSignal;
|
|
6621
6961
|
try {
|
|
6622
6962
|
for (const tasks of batches) {
|
|
6963
|
+
attemptSignal = options.openAttempt?.(options.abortSignal) ?? options.abortSignal;
|
|
6623
6964
|
const capturedBeforeBatch = countCapturedFrames(
|
|
6624
6965
|
options.totalFrames,
|
|
6625
6966
|
options.framesDir,
|
|
@@ -6632,7 +6973,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6632
6973
|
tasks,
|
|
6633
6974
|
options.captureOptions,
|
|
6634
6975
|
options.createBeforeCaptureHook,
|
|
6635
|
-
|
|
6976
|
+
attemptSignal,
|
|
6636
6977
|
options.onProgress ? (progress) => {
|
|
6637
6978
|
options.onProgress?.({
|
|
6638
6979
|
...progress,
|
|
@@ -6680,7 +7021,8 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6680
7021
|
currentWorkers = nextWorkers;
|
|
6681
7022
|
missingRanges = remaining;
|
|
6682
7023
|
attempt++;
|
|
6683
|
-
} catch (
|
|
7024
|
+
} catch (caught) {
|
|
7025
|
+
const error = attemptSignal?.aborted && !options.abortSignal?.aborted ? attemptSignal.reason : caught;
|
|
6684
7026
|
const failure = classifyCaptureFailure3(error, { signal: options.abortSignal });
|
|
6685
7027
|
if (failure.kind === "cancelled") {
|
|
6686
7028
|
throw error;
|
|
@@ -11091,29 +11433,36 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11091
11433
|
const captureSession = session;
|
|
11092
11434
|
session = null;
|
|
11093
11435
|
overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
|
|
11094
|
-
await
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11436
|
+
await observeRenderStage(
|
|
11437
|
+
new RenderObservabilityRecorder({ pipelineStartMs: start, log }),
|
|
11438
|
+
"capture_chunk",
|
|
11439
|
+
{ chunkIndex },
|
|
11440
|
+
(openAttempt) => runCaptureStage({
|
|
11441
|
+
fileServer,
|
|
11442
|
+
workDir,
|
|
11443
|
+
framesDir,
|
|
11444
|
+
job,
|
|
11445
|
+
totalFrames: framesInChunk,
|
|
11446
|
+
cfg: captureCfg,
|
|
11447
|
+
plan: capturePlan,
|
|
11448
|
+
log,
|
|
11449
|
+
probeSession: captureSession,
|
|
11450
|
+
captureAttempts,
|
|
11451
|
+
// This sink records each worker's effective capture mode so a
|
|
11452
|
+
// fallback remains observable to adapters and smoke tests.
|
|
11453
|
+
dedupPerfs: capturePerfs,
|
|
11454
|
+
buildCaptureOptions: () => captureOptions,
|
|
11455
|
+
createRenderVideoFrameInjector: createChunkVideoFrameInjector,
|
|
11456
|
+
abortSignal: void 0,
|
|
11457
|
+
assertNotAborted: () => {
|
|
11458
|
+
},
|
|
11459
|
+
frameRange: range,
|
|
11460
|
+
onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame),
|
|
11461
|
+
interleaveFrames: interleavesCapture(overlap.current !== null, options),
|
|
11462
|
+
openAttempt
|
|
11463
|
+
}),
|
|
11464
|
+
{ stall: frameStallRule(job) }
|
|
11465
|
+
).catch(async (captureError) => {
|
|
11117
11466
|
await overlap.current?.abort();
|
|
11118
11467
|
overlap.current = null;
|
|
11119
11468
|
throw captureError;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"buildId": "dev",
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "7d7e26ef5f6c62ee5fd433020813c7253231a9ddaf89a3bcde67fd2d0ed33c66",
|
|
5
5
|
"artifacts": {
|
|
6
6
|
"iife": "hyperframe.runtime.iife.js",
|
|
7
7
|
"esm": "hyperframe.runtime.mjs"
|