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:
Miguel Ángel
2026-07-10 23:59:08 -04:00
committed by GitHub
parent d51fa7eba2
commit 87618eef4c
8 changed files with 383 additions and 23 deletions
+58
View File
@@ -41,6 +41,7 @@ const trackingState = vi.hoisted(() => ({
// `.ts` source under vitest — mocked here so the CLI-trial tests can
// control it directly instead of inheriting that environment quirk.
shouldTrack: true,
renderObservations: [] as Array<Record<string, unknown>>,
}));
const preflightState = vi.hoisted(() => ({
@@ -110,6 +111,9 @@ vi.mock("../telemetry/client.js", () => ({
vi.mock("../telemetry/events.js", () => ({
trackRenderComplete: vi.fn(),
trackRenderError: vi.fn(),
trackRenderObservation: vi.fn((props: Record<string, unknown>) => {
trackingState.renderObservations.push(props);
}),
}));
vi.mock("../browser/ffmpeg.js", () => ({
@@ -165,6 +169,7 @@ describe("renderLocal browser GPU config", () => {
configState.failWrites = 0;
configState.writeConfigCalls = [];
trackingState.shouldTrack = true;
trackingState.renderObservations = [];
resetTrialState();
savedEnv.clear();
savedEnv.set("HYPERFRAMES_FFMPEG_PATH", process.env.HYPERFRAMES_FFMPEG_PATH);
@@ -210,6 +215,59 @@ describe("renderLocal browser GPU config", () => {
});
}, 15_000);
it("forwards render stage start and end lifecycle events to telemetry", async () => {
producerState.executeImpl = async (job) => {
const logger = (job.config as { logger: { info: (message: string, meta: object) => void } })
.logger;
logger.info("[Render:trace]", {
renderJobId: "render-lifecycle",
phase: "capture_streaming",
status: "start",
elapsedMs: 100,
workerCount: 1,
captureMode: "screenshot",
captureOperation: "captureScreenshot",
framesCompleted: 12,
totalFrames: 900,
});
logger.info("[Render:trace]", {
renderJobId: "render-lifecycle",
phase: "capture_streaming",
status: "end",
elapsedMs: 250,
durationMs: 150,
});
};
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: { num: 30, den: 1 },
quality: "standard",
format: "mp4",
gpu: false,
browserGpuMode: "software",
hdrMode: "auto",
quiet: true,
skipFeedback: true,
});
expect(trackingState.renderObservations).toEqual([
expect.objectContaining({
renderJobId: "render-lifecycle",
phase: "capture_streaming",
status: "start",
captureOperation: "captureScreenshot",
framesCompleted: 12,
totalFrames: 900,
}),
expect.objectContaining({
renderJobId: "render-lifecycle",
phase: "capture_streaming",
status: "end",
durationMs: 150,
}),
]);
});
it("forwards browserGpuMode='auto' into producer config (probe-then-choose)", async () => {
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: { num: 30, den: 1 },