mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
feat(engine): measure live DOM size on every render, not just probed ones
The short-comp routing gate can only read a live element count when a
probe session exists, and the first v0.7.83 data shows that is far rarer
than estimated: 17% of renders (86/503), not the ">=28%" the video-presence
proxy suggested. The other 83% fall back to a static source scan, which
is exactly blind to the shape that motivated the live count — small
markup, thousands of script-created nodes.
That leaves the fleet element-count distribution unknowable for most
renders, and the observed distribution is already surprising: p99 ~900,
max 1,420 against a 2,500 ceiling calibrated on 7k/20k/40k synthetic
nodes. Either the ceiling is close to irrelevant, or the large-DOM tail
is hiding in the 83% we cannot see. Both readings change what PR B
should do, and neither is decidable from probed renders alone (they are
a biased sample — they got a probe *because* they carry media or
unresolved compositions).
So measure it where every render already goes: capture-session init.
`collectSessionInitTelemetry` gains a querySelectorAll("*") count beside
the tween count it already collects, riding the same channel to
`observability_init_element_count`. This is observational only — capture
has begun, far too late to route on — and it deliberately does not feed
the gate. It answers the distribution question the gate cannot.
Coverage for this channel is proven rather than assumed: the tween-count
fix that shipped in v0.7.83 took the clamped-parallel bucket from 0/272
renders to 217/217, and 23.1% -> 100% overall.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5244dde5f1
commit
d74afc7b7d
@@ -416,18 +416,33 @@ describe("init observability fallback (parallel workers)", () => {
|
||||
const summary = makeRecorder().summary({
|
||||
lastBrowserConsole: ["[FrameCapture:NAV] page.goto start"],
|
||||
capture: { forceScreenshot: false, captureMode: "screenshot" },
|
||||
initFallback: { initDurationMs: 850, tweenCount: 1200 },
|
||||
initFallback: { initDurationMs: 850, tweenCount: 1200, elementCount: 3400 },
|
||||
});
|
||||
expect(summary.init).toEqual({ initDurationMs: 850, tweenCount: 1200 });
|
||||
expect(summary.init).toEqual({ initDurationMs: 850, tweenCount: 1200, elementCount: 3400 });
|
||||
});
|
||||
|
||||
it("max-merges console INIT lines over the fallback, matching multi-session semantics", () => {
|
||||
const summary = makeRecorder().summary({
|
||||
lastBrowserConsole: ["[FrameCapture:INIT] complete initDurationMs=1234 tweenCount=42"],
|
||||
lastBrowserConsole: [
|
||||
"[FrameCapture:INIT] complete initDurationMs=1234 tweenCount=42 elementCount=5000",
|
||||
],
|
||||
capture: { forceScreenshot: false, captureMode: "screenshot" },
|
||||
initFallback: { initDurationMs: 850, tweenCount: 1200 },
|
||||
initFallback: { initDurationMs: 850, tweenCount: 1200, elementCount: 3400 },
|
||||
});
|
||||
expect(summary.init).toEqual({ initDurationMs: 1234, tweenCount: 1200 });
|
||||
expect(summary.init).toEqual({ initDurationMs: 1234, tweenCount: 1200, elementCount: 5000 });
|
||||
});
|
||||
|
||||
// The single-session path has no structured fallback — it parses the console
|
||||
// line only. This is the path that covers renders the routing gate cannot
|
||||
// measure, so the element count must survive it.
|
||||
it("parses elementCount from the console INIT line with no fallback at all", () => {
|
||||
const summary = makeRecorder().summary({
|
||||
lastBrowserConsole: [
|
||||
"[FrameCapture:INIT] complete initDurationMs=90 tweenCount=7 elementCount=1420",
|
||||
],
|
||||
capture: { forceScreenshot: false, captureMode: "screenshot" },
|
||||
});
|
||||
expect(summary.init).toEqual({ initDurationMs: 90, tweenCount: 7, elementCount: 1420 });
|
||||
});
|
||||
|
||||
it("stays undefined when neither source has anything", () => {
|
||||
|
||||
@@ -182,6 +182,15 @@ export interface RenderExtractionObservability {
|
||||
export interface RenderInitObservability {
|
||||
initDurationMs?: number;
|
||||
tweenCount?: number;
|
||||
/**
|
||||
* Live DOM element count at end of capture-session init. Observational:
|
||||
* measured after routing has already been decided, so it cannot gate — it
|
||||
* exists because the routing gate's own count is only available on the
|
||||
* ~17% of renders that get a probe session, leaving the fleet
|
||||
* element-count distribution (and any large-runtime-DOM tail) unreadable
|
||||
* for the rest.
|
||||
*/
|
||||
elementCount?: number;
|
||||
}
|
||||
|
||||
export interface RenderObservabilitySummary {
|
||||
@@ -299,13 +308,17 @@ function summarizeInitObservability(
|
||||
// let the console parse (same max semantics) refine it.
|
||||
let initDurationMs: number | undefined = fallback?.initDurationMs;
|
||||
let tweenCount: number | undefined = fallback?.tweenCount;
|
||||
let elementCount: number | undefined = fallback?.elementCount;
|
||||
for (const line of lines) {
|
||||
if (!line.includes("[FrameCapture:INIT]")) continue;
|
||||
initDurationMs = maxReading(initDurationMs, readUnsignedIntAfter(line, "initDurationMs="));
|
||||
tweenCount = maxReading(tweenCount, readUnsignedIntAfter(line, "tweenCount="));
|
||||
elementCount = maxReading(elementCount, readUnsignedIntAfter(line, "elementCount="));
|
||||
}
|
||||
if (initDurationMs === undefined && tweenCount === undefined) return undefined;
|
||||
return { initDurationMs, tweenCount };
|
||||
if (initDurationMs === undefined && tweenCount === undefined && elementCount === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return { initDurationMs, tweenCount, elementCount };
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
|
||||
@@ -1936,17 +1936,25 @@ describe("shouldPreferSingleWorkerDrawElement (DE priority inversion)", () => {
|
||||
it("max-merges across workers and ignores workers that reported nothing", () => {
|
||||
expect(
|
||||
mergeWorkerInitObservability([
|
||||
{ initDurationMs: 400, initTweenCount: 900 },
|
||||
{ initDurationMs: 400, initTweenCount: 900, initElementCount: 1200 },
|
||||
{},
|
||||
{ initDurationMs: 1250, initTweenCount: 880 },
|
||||
{ initDurationMs: 1250, initTweenCount: 880, initElementCount: 1190 },
|
||||
]),
|
||||
).toEqual({ initDurationMs: 1250, tweenCount: 900 });
|
||||
).toEqual({ initDurationMs: 1250, tweenCount: 900, elementCount: 1200 });
|
||||
});
|
||||
|
||||
it("returns undefined when no worker reported — summary.init must stay absent, not zeroed", () => {
|
||||
expect(mergeWorkerInitObservability([])).toBeUndefined();
|
||||
expect(mergeWorkerInitObservability([{}, {}])).toBeUndefined();
|
||||
});
|
||||
|
||||
it("surfaces an element count even when a worker reported nothing else", () => {
|
||||
expect(mergeWorkerInitObservability([{ initElementCount: 4000 }])).toEqual({
|
||||
initDurationMs: undefined,
|
||||
tweenCount: undefined,
|
||||
elementCount: 4000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("countElementTags", () => {
|
||||
|
||||
@@ -1345,10 +1345,15 @@ export async function resolveCompositionElementCount(
|
||||
* initializes before the timeline is fully wired, not an expected disagreement.
|
||||
*/
|
||||
export function mergeWorkerInitObservability(
|
||||
perfs: ReadonlyArray<{ initDurationMs?: number; initTweenCount?: number }>,
|
||||
): { initDurationMs?: number; tweenCount?: number } | undefined {
|
||||
perfs: ReadonlyArray<{
|
||||
initDurationMs?: number;
|
||||
initTweenCount?: number;
|
||||
initElementCount?: number;
|
||||
}>,
|
||||
): { initDurationMs?: number; tweenCount?: number; elementCount?: number } | undefined {
|
||||
let initDurationMs: number | undefined;
|
||||
let tweenCount: number | undefined;
|
||||
let elementCount: number | undefined;
|
||||
for (const perf of perfs) {
|
||||
if (perf.initDurationMs !== undefined) {
|
||||
initDurationMs =
|
||||
@@ -1360,9 +1365,20 @@ export function mergeWorkerInitObservability(
|
||||
tweenCount =
|
||||
tweenCount === undefined ? perf.initTweenCount : Math.max(tweenCount, perf.initTweenCount);
|
||||
}
|
||||
// Max across workers: every worker loads the same composition, so they
|
||||
// should agree — max is defensive against a worker sampled before its
|
||||
// init script finished populating the DOM.
|
||||
if (perf.initElementCount !== undefined) {
|
||||
elementCount =
|
||||
elementCount === undefined
|
||||
? perf.initElementCount
|
||||
: Math.max(elementCount, perf.initElementCount);
|
||||
}
|
||||
}
|
||||
if (initDurationMs === undefined && tweenCount === undefined) return undefined;
|
||||
return { initDurationMs, tweenCount };
|
||||
if (initDurationMs === undefined && tweenCount === undefined && elementCount === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return { initDurationMs, tweenCount, elementCount };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user