5.0 KiB
FIXED!
Issue 004: Sensor Telemetry Render Frequency
Summary
The / page receives frequent sensorFrame messages and renders multiple telemetry/HUD
consumers from those frames. On throttled mobile CPU, this shows up as repeated SVG path
attribute mutations and significant live DOM churn.
Severity
Medium-high.
This is likely the biggest non-input-manager source of steady rendering work.
Affected Files
webui/src/context/TelemetryContext.jsxwebui/src/components/TopDownMap/TopDownMapContent.jsxwebui/src/components/TopDownMap/visuals.jsxwebui/src/components/DriverVideo/index.jsxwebui/src/components/HudOverlays/*webui/src/components/TelemetryPanel/index.jsxwebui/src/components/PiHostStatsCard/index.jsx
Evidence
Artifact:
perf/results/2026-06-11T03-22-44-714Z-root-runtime/root-runtime-report.json
In an 18s / audit at mobile viewport with 6x CPU throttle:
sensorFrame count: 277
sensorFrame bytes: 520,169
That is about 15.4 sensor frames/sec.
The same audit saw SVG attribute mutations:
path d mutations: 621
path fill mutations: 619
These map strongly to TopDownMapContent.jsx and visuals.jsx, where sensor values are
converted into SVG arcs, cones, wheels, brush visuals, and colors.
Artifact:
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
In a 30s audit:
sensorFrame count among latest sampled 1000 messages: 306
sensorFrame bytes: 574,616
Current Code Path
TelemetryContext.jsx updates every incoming frame:
function handleSensorFrame({ roverId, sensors = {}, frame = {} }) {
if (!roverId) return;
const previous = framesRef.current[roverId] ?? {};
framesRef.current = {
...framesRef.current,
[roverId]: {
...previous,
roverId,
sensors,
raw: frame?.data || null,
receivedAt: Date.now(),
},
};
notifyRover(roverId);
}
Consumers subscribe by rover:
useTelemetryFrame(roverId)
Every notifyRover(roverId) wakes all consumers for that rover.
Why This Matters
Telemetry itself may need to remain high-frequency for control safety or debugging, but the visual UI usually does not need to render at full sensor frequency.
The expensive part is not receiving the socket message. It is causing React/SVG/HUD updates and DOM attribute changes at the same cadence.
Fix Strategies
Option A: Throttle Render Notifications
Keep the latest frame in framesRef immediately, but notify subscribers at a lower rate.
Example:
const pendingRoversRef = useRef(new Set());
const notifyScheduledRef = useRef(false);
function scheduleNotify(roverId) {
pendingRoversRef.current.add(roverId);
if (notifyScheduledRef.current) return;
notifyScheduledRef.current = true;
setTimeout(() => {
notifyScheduledRef.current = false;
const rovers = [...pendingRoversRef.current];
pendingRoversRef.current.clear();
rovers.forEach((id) => notifyRover(id));
}, 100); // 10Hz visual updates
}
Then handleSensorFrame writes latest data immediately but calls scheduleNotify(roverId).
Option B: Separate Raw Telemetry From Visual Telemetry
Expose:
useTelemetryFrameRaw(roverId) // high-frequency, only for critical logic
useTelemetryFrameVisual(roverId) // throttled for UI rendering
Use throttled visual telemetry in:
- TopDownMap
- HUD overlays
- telemetry panels
- battery bars
- host stats display
Keep raw telemetry for safety mechanisms if needed.
Option C: Memoize/Split Sensor Consumers by Field
Do not re-render the entire map or all overlays when only one sensor field changed.
Examples:
- Battery components subscribe only to battery fields.
- Light bump bars subscribe only to light bump fields.
- Overcurrent overlay subscribes only to overcurrent fields.
This is more architectural but can reduce unnecessary work.
Option D: Render TopDownMap Less Often
At the component level:
const visualSensors = useThrottledValue(sensors, 100);
This is easier than changing TelemetryContext, but less central.
Recommended Path
Start with a throttled visual notification path in TelemetryContext. It gives the largest
blast-radius reduction without changing every consumer immediately.
A reasonable target:
- Desktop: 10-15Hz visual updates
- Mobile: 5-10Hz visual updates
Validation
Run:
CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=18000 node perf/live-root-runtime-audit.mjs https://rover.otter.land/ perf/results
Expected improvements:
path dandpath fillmutation counts should drop.LayoutCountandRecalcStyleCountmay drop.ScriptDurationshould drop.- Frame p95 should improve.
Socket sensorFrame count may remain the same if only rendering is throttled.
Risks
- Do not throttle logic that protects the rover or sends control commands unless you are sure it is only display logic.
- Operators may expect smooth sensor visuals, but 10Hz is usually enough for dashboards.
- Make sure low battery/overcurrent warnings still feel responsive.