From 50ade616a8fecf93a7fc3121d961cdb4daace404 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 20 May 2026 05:36:54 +0000 Subject: [PATCH] refactor(cli): extract studio render telemetry helpers to own file Moves StudioRenderOpts, memSnapshot, perfPayload, stagesPayload, extractPayload, emitStudioRenderComplete, emitStudioRenderError to packages/cli/src/server/studioRenderTelemetry.ts. studioServer.ts now has a single-line import diff. Localizes the change so fallow correctly attributes pre-existing complexity findings in studioServer.ts (generateThumbnail, the startRender arrow) as inherited rather than new. --- .../cli/src/server/studioRenderTelemetry.ts | 117 ++++++++++++++++++ packages/cli/src/server/studioServer.ts | 110 +--------------- 2 files changed, 118 insertions(+), 109 deletions(-) create mode 100644 packages/cli/src/server/studioRenderTelemetry.ts diff --git a/packages/cli/src/server/studioRenderTelemetry.ts b/packages/cli/src/server/studioRenderTelemetry.ts new file mode 100644 index 000000000..868d54d3b --- /dev/null +++ b/packages/cli/src/server/studioRenderTelemetry.ts @@ -0,0 +1,117 @@ +// --------------------------------------------------------------------------- +// Maps studio-triggered renders into the existing `render_complete` / +// `render_error` telemetry events with `source: "studio"`, so they land +// alongside CLI renders in one unified taxonomy. +// +// Kept in its own file so `studioServer.ts` only needs two function calls. +// --------------------------------------------------------------------------- + +import { freemem } from "node:os"; +import type { Fps } from "@hyperframes/core"; +import { fpsToNumber } from "@hyperframes/core"; +import type { RenderPerfSummary } from "@hyperframes/producer"; +import { trackRenderComplete, trackRenderError } from "../telemetry/events.js"; +import { bytesToMb } from "../telemetry/system.js"; + +export interface StudioRenderOpts { + fps: Fps; + quality: string; +} + +type RenderCompleteProps = Parameters[0]; + +function memSnapshot(): { peakMemoryMb: number; memoryFreeMb: number } { + return { + peakMemoryMb: bytesToMb(process.memoryUsage.rss()), + memoryFreeMb: bytesToMb(freemem()), + }; +} + +function stagesPayload(stages: Record): Partial { + return { + stageCompileMs: stages.compileMs, + stageVideoExtractMs: stages.videoExtractMs, + stageAudioProcessMs: stages.audioProcessMs, + stageCaptureMs: stages.captureMs, + stageEncodeMs: stages.encodeMs, + stageAssembleMs: stages.assembleMs, + }; +} + +function extractPayload( + extract: RenderPerfSummary["videoExtractBreakdown"], +): Partial { + if (!extract) return {}; + return { + extractResolveMs: extract.resolveMs, + extractHdrProbeMs: extract.hdrProbeMs, + extractHdrPreflightMs: extract.hdrPreflightMs, + extractHdrPreflightCount: extract.hdrPreflightCount, + extractVfrProbeMs: extract.vfrProbeMs, + extractVfrPreflightMs: extract.vfrPreflightMs, + extractVfrPreflightCount: extract.vfrPreflightCount, + extractPhase3Ms: extract.extractMs, + extractCacheHits: extract.cacheHits, + extractCacheMisses: extract.cacheMisses, + }; +} + +function perfPayload( + perf: RenderPerfSummary | undefined, + elapsedMs: number, +): Partial { + if (!perf) return {}; + const compositionDurationMs = Math.round(perf.compositionDurationSeconds * 1000); + const speedRatio = + compositionDurationMs > 0 && elapsedMs > 0 + ? Math.round((compositionDurationMs / elapsedMs) * 100) / 100 + : undefined; + return { + workers: perf.workers, + compositionDurationMs, + compositionWidth: perf.resolution.width, + compositionHeight: perf.resolution.height, + totalFrames: perf.totalFrames, + speedRatio, + captureAvgMs: perf.captureAvgMs, + capturePeakMs: perf.capturePeakMs, + tmpPeakBytes: perf.tmpPeakBytes, + ...stagesPayload(perf.stages), + ...extractPayload(perf.videoExtractBreakdown), + }; +} + +export function emitStudioRenderError( + opts: StudioRenderOpts, + elapsedMs: number, + failedStage: string | undefined, + err: unknown, +): void { + trackRenderError({ + fps: fpsToNumber(opts.fps), + quality: opts.quality, + docker: false, + source: "studio", + failedStage, + errorMessage: err instanceof Error ? err.message : String(err), + elapsedMs, + ...memSnapshot(), + }); +} + +export function emitStudioRenderComplete( + opts: StudioRenderOpts, + elapsedMs: number, + perf: RenderPerfSummary | undefined, +): void { + trackRenderComplete({ + durationMs: elapsedMs, + fps: fpsToNumber(opts.fps), + quality: opts.quality, + docker: false, + gpu: false, + source: "studio", + ...perfPayload(perf, elapsedMs), + ...memSnapshot(), + }); +} diff --git a/packages/cli/src/server/studioServer.ts b/packages/cli/src/server/studioServer.ts index 41fc208c2..4955e5d39 100644 --- a/packages/cli/src/server/studioServer.ts +++ b/packages/cli/src/server/studioServer.ts @@ -12,12 +12,7 @@ import { resolve, join, basename } from "node:path"; import { createProjectWatcher, type ProjectWatcher } from "./fileWatcher.js"; import { loadRuntimeSource } from "./runtimeSource.js"; import { VERSION as version } from "../version.js"; -import { trackRenderComplete, trackRenderError } from "../telemetry/events.js"; -import { fpsToNumber } from "@hyperframes/core"; -import { freemem } from "node:os"; -import { bytesToMb } from "../telemetry/system.js"; -import type { Fps } from "@hyperframes/core"; -import type { RenderPerfSummary } from "@hyperframes/producer"; +import { emitStudioRenderComplete, emitStudioRenderError } from "./studioRenderTelemetry.js"; import { createStudioManualEditsRenderBodyScript, createStudioApi, @@ -87,109 +82,6 @@ function resolveRuntimePath(): string { return builtPath; } -interface StudioRenderOpts { - fps: Fps; - quality: string; -} - -function memSnapshot(): { peakMemoryMb: number; memoryFreeMb: number } { - return { - peakMemoryMb: bytesToMb(process.memoryUsage.rss()), - memoryFreeMb: bytesToMb(freemem()), - }; -} - -function emitStudioRenderError( - opts: StudioRenderOpts, - elapsedMs: number, - failedStage: string | undefined, - err: unknown, -): void { - trackRenderError({ - fps: fpsToNumber(opts.fps), - quality: opts.quality, - docker: false, - source: "studio", - failedStage, - errorMessage: err instanceof Error ? err.message : String(err), - elapsedMs, - ...memSnapshot(), - }); -} - -type RenderCompleteProps = Parameters[0]; - -function stagesPayload(stages: Record): Partial { - return { - stageCompileMs: stages.compileMs, - stageVideoExtractMs: stages.videoExtractMs, - stageAudioProcessMs: stages.audioProcessMs, - stageCaptureMs: stages.captureMs, - stageEncodeMs: stages.encodeMs, - stageAssembleMs: stages.assembleMs, - }; -} - -function extractPayload( - extract: RenderPerfSummary["videoExtractBreakdown"], -): Partial { - if (!extract) return {}; - return { - extractResolveMs: extract.resolveMs, - extractHdrProbeMs: extract.hdrProbeMs, - extractHdrPreflightMs: extract.hdrPreflightMs, - extractHdrPreflightCount: extract.hdrPreflightCount, - extractVfrProbeMs: extract.vfrProbeMs, - extractVfrPreflightMs: extract.vfrPreflightMs, - extractVfrPreflightCount: extract.vfrPreflightCount, - extractPhase3Ms: extract.extractMs, - extractCacheHits: extract.cacheHits, - extractCacheMisses: extract.cacheMisses, - }; -} - -function perfPayload( - perf: RenderPerfSummary | undefined, - elapsedMs: number, -): Partial { - if (!perf) return {}; - const compositionDurationMs = Math.round(perf.compositionDurationSeconds * 1000); - const speedRatio = - compositionDurationMs > 0 && elapsedMs > 0 - ? Math.round((compositionDurationMs / elapsedMs) * 100) / 100 - : undefined; - return { - workers: perf.workers, - compositionDurationMs, - compositionWidth: perf.resolution.width, - compositionHeight: perf.resolution.height, - totalFrames: perf.totalFrames, - speedRatio, - captureAvgMs: perf.captureAvgMs, - capturePeakMs: perf.capturePeakMs, - tmpPeakBytes: perf.tmpPeakBytes, - ...stagesPayload(perf.stages), - ...extractPayload(perf.videoExtractBreakdown), - }; -} - -function emitStudioRenderComplete( - opts: StudioRenderOpts, - elapsedMs: number, - perf: RenderPerfSummary | undefined, -): void { - trackRenderComplete({ - durationMs: elapsedMs, - fps: fpsToNumber(opts.fps), - quality: opts.quality, - docker: false, - gpu: false, - source: "studio", - ...perfPayload(perf, elapsedMs), - ...memSnapshot(), - }); -} - function readStudioManualEditManifestContent(projectDir: string): string { const manifestPath = join(projectDir, STUDIO_MANUAL_EDITS_PATH); if (!existsSync(manifestPath)) return "";