mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(telemetry): expose stalled render stages (#2220)
* fix(telemetry): expose stalled render stages * fix(telemetry): preserve capture data on terminal stage events * fix(telemetry): fix calibration TDZ crash, tag encode/assemble, extend heartbeat cadence capture_calibration referenced captureStageObservationData before its declaration (later in the same scope), which would throw a ReferenceError for any render hitting the calibration path. Hoist the closure and split workerCount's declaration from its resolution so calibration can safely read it as undefined before capture strategy resolves worker count. Also address the two non-blocking review items: wire encode/assemble stages through captureStageObservationData for consistent tagging, and extend the heartbeat schedule to repeat every 120s after the initial 30/60/120s ramp instead of going dark on stalls beyond two minutes.
This commit is contained in:
@@ -118,6 +118,7 @@ const MAX_EVENTS = 160;
|
||||
const ALLOWED_STRING_DATA_KEYS = new Set([
|
||||
"browserGpuMode",
|
||||
"captureMode",
|
||||
"captureOperation",
|
||||
"compositionHash",
|
||||
"effectiveHdr",
|
||||
"format",
|
||||
@@ -363,6 +364,20 @@ export class RenderObservabilityRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
/** Heartbeat ramp before falling back to a steady repeat cadence. */
|
||||
const HEARTBEAT_RAMP_MS = [30_000, 60_000, 120_000];
|
||||
const HEARTBEAT_REPEAT_MS = 120_000;
|
||||
const HEARTBEAT_RAMP_END_MS =
|
||||
HEARTBEAT_RAMP_MS[HEARTBEAT_RAMP_MS.length - 1] ?? HEARTBEAT_REPEAT_MS;
|
||||
|
||||
/** Target elapsed-ms for the Nth heartbeat (0-indexed): ramp, then steady repeat so long stalls keep emitting breadcrumbs instead of going dark after the ramp. */
|
||||
function heartbeatTargetMs(index: number): number {
|
||||
const rampTarget = HEARTBEAT_RAMP_MS[index];
|
||||
if (rampTarget !== undefined) return rampTarget;
|
||||
const overflow = index - HEARTBEAT_RAMP_MS.length + 1;
|
||||
return HEARTBEAT_RAMP_END_MS + overflow * HEARTBEAT_REPEAT_MS;
|
||||
}
|
||||
|
||||
export async function observeRenderStage<T>(
|
||||
recorder: RenderObservabilityRecorder,
|
||||
phase: string,
|
||||
@@ -370,12 +385,35 @@ export async function observeRenderStage<T>(
|
||||
fn: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const startedAt = recorder.stageStart(phase, data);
|
||||
let heartbeatCount = 0;
|
||||
let lastFiredAtMs = 0;
|
||||
let heartbeatTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
const scheduleNextHeartbeat = () => {
|
||||
const targetMs = heartbeatTargetMs(heartbeatCount);
|
||||
heartbeatTimer = setTimeout(() => {
|
||||
lastFiredAtMs = targetMs;
|
||||
heartbeatCount += 1;
|
||||
recorder.checkpoint(phase, "stage still running", {
|
||||
...data,
|
||||
heartbeatIndex: heartbeatCount,
|
||||
stageElapsedMs: Date.now() - startedAt,
|
||||
});
|
||||
scheduleNextHeartbeat();
|
||||
}, targetMs - lastFiredAtMs);
|
||||
heartbeatTimer.unref?.();
|
||||
};
|
||||
scheduleNextHeartbeat();
|
||||
const clearHeartbeats = () => {
|
||||
clearTimeout(heartbeatTimer);
|
||||
};
|
||||
try {
|
||||
const result = await fn();
|
||||
recorder.stageEnd(phase, startedAt);
|
||||
clearHeartbeats();
|
||||
recorder.stageEnd(phase, startedAt, data);
|
||||
return result;
|
||||
} catch (error) {
|
||||
recorder.stageError(phase, startedAt, error);
|
||||
clearHeartbeats();
|
||||
recorder.stageError(phase, startedAt, error, data);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user