Files
orca/tests/tools/benchmarks/spinner-rendering/trace-iterations.mjs
Neil ec112c6b01 perf(renderer): stop agent spinners that are scrolled out of view (#37153)
Offscreen or clipped working rings dropped their CSS animation through one
shared IntersectionObserver; revealing a ring restarts it and the existing
animationstart anchor keeps every ring on the shared phase.

The spinner bench gains an always-on comparison variant and reports
compositor frame counts.
2026-11-01 16:05:02 -08:00

56 lines
2.1 KiB
JavaScript

import { writeFileSync } from 'node:fs'
export async function traceIterations(cdp, outputPath, durationMs = 2200) {
await cdp.send('Tracing.start', {
categories: 'devtools.timeline,toplevel,cc,disabled-by-default-cc.debug.scheduler',
transferMode: 'ReturnAsStream'
})
await new Promise((resolve) => setTimeout(resolve, durationMs))
const completion = new Promise((resolve) => cdp.once('Tracing.tracingComplete', resolve))
await cdp.send('Tracing.end')
const { stream } = await completion
let json = ''
try {
while (true) {
const part = await cdp.send('IO.read', { handle: stream })
json += part.data
if (part.eof) {
break
}
}
} finally {
await cdp.send('IO.close', { handle: stream })
}
writeFileSync(outputPath, json)
const events = JSON.parse(json).traceEvents
const compositorThreads = new Set(
events
.filter((event) => event.name === 'thread_name' && event.args?.name === 'Compositor')
.map((event) => `${event.pid}:${event.tid}`)
)
const onCompositor = events.filter((event) => compositorThreads.has(`${event.pid}:${event.tid}`))
const perSecond = (count) => Math.round((count * 1000) / durationMs)
return {
durationMs,
iterationEvents: events.filter(
(event) => event.name === 'EventDispatch' && event.args?.data?.type === 'animationiteration'
).length,
styleUpdates: events.filter((event) => event.name === 'UpdateLayoutTree').length,
// A spinner that wakes the compositor every refresh shows up as many no-damage frames.
compositorFramesPerSecond: perSecond(
onCompositor.filter((event) => event.name === 'Scheduler::BeginImplFrame').length
),
noDamageFramesPerSecond: perSecond(
onCompositor.filter((event) => event.name === 'EarlyOut_NoDamage').length
),
compositorThreadMsPerSecond:
Math.round(
(onCompositor
.filter((event) => event.name === 'ThreadControllerImpl::RunTask')
.reduce((total, event) => total + (event.tdur ?? event.dur ?? 0), 0) /
durationMs) *
10
) / 10
}
}