4.5 KiB
/spectate CPU Performance Summary
This is the /spectate companion to the / performance writeups. The testing target
was the deployed page:
https://rover.otter.land/spectate
The most useful artifact for this pass is:
perf/results/2026-06-12T04-55-16-888Z-root-runtime/root-runtime-report.json
That run used:
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results
High-Level Result
/spectate is CPU-heavy for a different reason than /.
The / page's biggest confirmed issue is global input listener churn. /spectate does
not show that same dominant pattern. The fresh /spectate audit had 379 event listeners,
mostly normal media/image listeners, and no giant add/remove loop.
The /spectate hot path is instead a live-display fan-out problem:
- several rover media players are mounted at once
- each rover card renders live HUD telemetry and a top-down SVG map
- camera/snapshot frames create new blobs and
img.srcvalues - the route subscribes to broad session/log/chat state while also rendering the whole spectator layout
- the page maintains several WebRTC players at the same time
Key Measurements
Fresh /spectate runtime audit, 30s, mobile viewport, 6x CPU throttle:
TaskDuration: 28.48s over a 30.9s sample
ScriptDuration: 12.11s
Long tasks: 81
Worst long task: 375ms
Average frame gap: 113.9ms
p95 frame gap: 233.3ms
p99 frame gap: 300.0ms
Nodes: 7,059
Layout objects: 6,150
RTCPeerConnections: 6
LayoutCount: 243
RecalcStyleCount: 248
The 8x CPU-throttled profile from the earlier route comparison was worse:
Task delta: 49.13s over a 52.85s sample
Script delta: 19.58s
Layout delta: 516
Recalc delta: 522
Node delta: +3,815
That profile also put Blob, decodeString, and setAttribute in the CPU stack, which
matches the runtime evidence for camera blobs, socket payload parsing, and SVG/image
attribute churn.
Socket And DOM Pressure
The fresh audit captured these live events during the 30s window:
sensorFrame: 1,354 events, 2,531,090 bytes
log:entry: 634 events, 150,179 bytes
unlabeled binary: 126 events, 11,048,477 bytes
roomCamera:frame: 117 events
session:sync: 54 events, 914,000 bytes
commandAck: 68 events
roverHostStats: 17 events
roverSnapshot:frame: 7 events
The most important DOM mutations were:
path d: 2,688 mutations
path fill: 2,682 mutations
img.h-full.w-full src: 117 mutations
span.h-2.w-2 class: 117 mutations
The path mutations point strongly at the per-rover TopDownMap SVG overlays. The
img.src and blink class mutations point at room camera snapshot rendering.
Ordered Issue List
- Telemetry fan-out and always-on HUD maps
- Multi-rover WebRTC media fan-out
- Room camera and snapshot object URL churn
- Broad spectator session rerenders
- Shared log/session stream pressure
- Spectator role handshake retries
Upstream Dependency Map
Fixing the / backlog first is a good plan.
Likely improved by / fixes:
- telemetry visual throttling
- log batching/gating
- chat/nickname churn
- session selector/store improvements
- generic timer cleanup
Not likely to be fixed by / alone:
- several simultaneous WHEP video/audio players
- per-rover spectator HUD maps
- room camera snapshot blob/object URL churn
- spectator role/subscribe handshake behavior
- mobile policy for how many live feeds should be active at once
Suggested Re-Test
After each /spectate fix, run:
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results
The most useful numbers to compare:
TaskDurationScriptDuration- long task count and worst duration
- frame average/p95/p99
RTCPeerConnectionsmutationTargetsforpath d,path fill, andimg srcwebSocketEventsforsensorFrame,log:entry,session:sync, and binary frame volume