mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
performance slopping but actually very informative
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# 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.jsx`
|
||||
- `webui/src/components/TopDownMap/TopDownMapContent.jsx`
|
||||
- `webui/src/components/TopDownMap/visuals.jsx`
|
||||
- `webui/src/components/DriverVideo/index.jsx`
|
||||
- `webui/src/components/HudOverlays/*`
|
||||
- `webui/src/components/TelemetryPanel/index.jsx`
|
||||
- `webui/src/components/PiHostStatsCard/index.jsx`
|
||||
|
||||
## Evidence
|
||||
|
||||
Artifact:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```txt
|
||||
sensorFrame count: 277
|
||||
sensorFrame bytes: 520,169
|
||||
```
|
||||
|
||||
That is about 15.4 sensor frames/sec.
|
||||
|
||||
The same audit saw SVG attribute mutations:
|
||||
|
||||
```txt
|
||||
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:
|
||||
|
||||
```sh
|
||||
perf/results/2026-06-11T03-21-51-263Z-root-runtime/root-runtime-report.json
|
||||
```
|
||||
|
||||
In a 30s audit:
|
||||
|
||||
```txt
|
||||
sensorFrame count among latest sampled 1000 messages: 306
|
||||
sensorFrame bytes: 574,616
|
||||
```
|
||||
|
||||
## Current Code Path
|
||||
|
||||
`TelemetryContext.jsx` updates every incoming frame:
|
||||
|
||||
```jsx
|
||||
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:
|
||||
|
||||
```jsx
|
||||
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:
|
||||
|
||||
```jsx
|
||||
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:
|
||||
|
||||
```jsx
|
||||
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:
|
||||
|
||||
```jsx
|
||||
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:
|
||||
|
||||
```sh
|
||||
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 d` and `path fill` mutation counts should drop.
|
||||
- `LayoutCount` and `RecalcStyleCount` may drop.
|
||||
- `ScriptDuration` should 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.
|
||||
|
||||
Reference in New Issue
Block a user