From b42a8e4e0317e737446169702fc26c32c8f60069 Mon Sep 17 00:00:00 2001 From: James Russo Date: Fri, 24 Apr 2026 00:40:44 -0400 Subject: [PATCH] feat(cli): forward perf breakdown + tmpPeakBytes to PostHog telemetry (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Forwards the new per-phase extraction breakdown and `tmpPeakBytes` fields from `RenderPerfSummary` (added in #444 and #446) to PostHog via the CLI's existing `render_complete` telemetry event. ## Why The CLI already ships `render_complete` events to PostHog (`packages/cli/src/telemetry/client.ts`), but `events.ts:trackRenderComplete` only carried a subset of `RenderPerfSummary` — top-level timings, composition dims, and memory snapshots. After #444 added per-phase extraction breakdown (`videoExtractBreakdown`) and #446 added cache hit/miss counters, the data lives on `job.perfSummary` at render-complete but never reaches PostHog dashboards. Without this, any PostHog insight built around "how often are we hitting the cache?", "what's the median HDR preflight cost?", or "where in the extract phase do compositions spend time?" has to be answered by Datadog log scraping instead. ## How - **`packages/cli/src/telemetry/events.ts`** — extend `trackRenderComplete` props with 17 new optional fields: `tmpPeakBytes`, the six named stage timings, and the ten `videoExtractBreakdown` fields. All sent as flat properties (`extract_cache_hits`, `stage_capture_ms`, etc.) — PostHog insights query flat keys more ergonomically than nested objects. - **`packages/cli/src/commands/render.ts`** — wire `job.perfSummary.videoExtractBreakdown` / `stages` / `tmpPeakBytes` into the `trackRenderMetrics` → `trackRenderComplete` hand-off. - Naming: `extract_phase3_ms` deliberately disambiguates from `stage_video_extract_ms` — the former is just the parallel ffmpeg extract inside Phase 3; the latter is the full stage (resolve + probe + preflight + extract). - All new fields are optional. The Docker-subprocess branch of `render.ts` that doesn't have a local `perfSummary` still compiles and ships events without them. ## Test plan - [x] `bun run --cwd packages/cli test` — 161/161 pass - [x] `bunx tsc -p packages/cli/tsconfig.json --noEmit` — no errors - [x] `bunx oxlint` + `bunx oxfmt` — clean - [ ] Once merged, verify PostHog receives the new properties on a real render event (run `hyperframes render` against a fixture and watch PostHog ingestion — telemetry auto-disables in CI, so this requires a local dev render with `HYPERFRAMES_NO_TELEMETRY` unset). ## Stack Depends on #444 (adds the `videoExtractBreakdown` + `tmpPeakBytes` fields to `RenderPerfSummary`) and transitively on #445 → #446. ## Future work (not in this PR) - The HeyGen internal producer server (`hyperframes-internal/packages/producer/src/server.ts`) logs `perfSummary` to Datadog via `log.info` but has no PostHog integration. Production renders are the bulk of the traffic — separate PR to either ship perfSummary to PostHog from the internal server, or materialize Datadog log-based metrics for per-phase timings. --- packages/cli/src/commands/render.ts | 20 ++++++++++++++++ packages/cli/src/telemetry/events.ts | 36 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/cli/src/commands/render.ts b/packages/cli/src/commands/render.ts index 7c3f9d5ec..b123c3972 100644 --- a/packages/cli/src/commands/render.ts +++ b/packages/cli/src/commands/render.ts @@ -597,6 +597,9 @@ function trackRenderMetrics( ? Math.round((compositionDurationMs / elapsedMs) * 100) / 100 : undefined; + const stages = perf?.stages ?? {}; + const extract = perf?.videoExtractBreakdown; + trackRenderComplete({ durationMs: elapsedMs, fps: options.fps, @@ -611,6 +614,23 @@ function trackRenderMetrics( speedRatio, captureAvgMs: perf?.captureAvgMs, capturePeakMs: perf?.capturePeakMs, + tmpPeakBytes: perf?.tmpPeakBytes, + stageCompileMs: stages.compileMs, + stageVideoExtractMs: stages.videoExtractMs, + stageAudioProcessMs: stages.audioProcessMs, + stageCaptureMs: stages.captureMs, + stageEncodeMs: stages.encodeMs, + stageAssembleMs: stages.assembleMs, + 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, ...getMemorySnapshot(), }); } diff --git a/packages/cli/src/telemetry/events.ts b/packages/cli/src/telemetry/events.ts index f7db477e9..c89016a0b 100644 --- a/packages/cli/src/telemetry/events.ts +++ b/packages/cli/src/telemetry/events.ts @@ -23,6 +23,25 @@ export function trackRenderComplete(props: { // Resource usage peakMemoryMb?: number; memoryFreeMb?: number; + tmpPeakBytes?: number; + // Per-stage timings (subset of RenderPerfSummary.stages) + stageCompileMs?: number; + stageVideoExtractMs?: number; + stageAudioProcessMs?: number; + stageCaptureMs?: number; + stageEncodeMs?: number; + stageAssembleMs?: number; + // Video-extraction breakdown (from RenderPerfSummary.videoExtractBreakdown) + extractResolveMs?: number; + extractHdrProbeMs?: number; + extractHdrPreflightMs?: number; + extractHdrPreflightCount?: number; + extractVfrProbeMs?: number; + extractVfrPreflightMs?: number; + extractVfrPreflightCount?: number; + extractPhase3Ms?: number; + extractCacheHits?: number; + extractCacheMisses?: number; }): void { trackEvent("render_complete", { duration_ms: props.durationMs, @@ -40,6 +59,23 @@ export function trackRenderComplete(props: { capture_peak_ms: props.capturePeakMs, peak_memory_mb: props.peakMemoryMb, memory_free_mb: props.memoryFreeMb, + tmp_peak_bytes: props.tmpPeakBytes, + stage_compile_ms: props.stageCompileMs, + stage_video_extract_ms: props.stageVideoExtractMs, + stage_audio_process_ms: props.stageAudioProcessMs, + stage_capture_ms: props.stageCaptureMs, + stage_encode_ms: props.stageEncodeMs, + stage_assemble_ms: props.stageAssembleMs, + extract_resolve_ms: props.extractResolveMs, + extract_hdr_probe_ms: props.extractHdrProbeMs, + extract_hdr_preflight_ms: props.extractHdrPreflightMs, + extract_hdr_preflight_count: props.extractHdrPreflightCount, + extract_vfr_probe_ms: props.extractVfrProbeMs, + extract_vfr_preflight_ms: props.extractVfrPreflightMs, + extract_vfr_preflight_count: props.extractVfrPreflightCount, + extract_phase3_ms: props.extractPhase3Ms, + extract_cache_hits: props.extractCacheHits, + extract_cache_misses: props.extractCacheMisses, }); }