Repository navigation
feat(ui): give the G radar a three-second trace, and measure what drawing it costs - #211
Merged
Merged
Conversation
…wing it costs
kImuTrailCapacity was a bare 24. The trail is appended once per display
service, so at the 20 Hz display cadence that was 1.2 seconds - a capacity on
its own says nothing about how much of a drive the trace shows, which is how 24
points came to be read as "a few seconds" when it was one and a fifth of one.
The span is now the figure that gets chosen and the capacity is derived from it.
Three seconds, picked against what the trace is for rather than as a round
number: a corner in a Seven runs roughly two to four seconds from turn-in to
unwinding, so the driver sees the corner they have just taken. A static_assert
in screen_router ties the assumed cadence to the real kDisplayServicePeriodUs,
so changing the display period fails the build instead of quietly rescaling the
trace.
Render cost is now measured rather than assumed, which is what let the span be
raised safely. Two periodic reports, because the two costs scale differently and
timing only the cheap one would have been worse than not measuring at all:
radar: the object work, timed at the call site in screen_router, so the UI
component stays free of esp_timer and buildable on the host
draw: lv_timer_handler in the panel task, which is where drawing actually
happens - repositioning an object only marks an area dirty
Measured on hardware at both capacities:
24 points 60 points
object work, mean 1,727 us 2,395 us
share of the 50 ms display period 3% 4%
worst repaint 23,597-23,813 us 24,255-24,287 us
Tripling the trace cost 2.3% more repaint time. Twenty-four dots already spanned
the whole radar, so the invalidated area was already saturated and more dots
inside it are nearly free. Per-point object cost is 18.6 us against a fixed
1.28 ms for the rings, labels and markers, so the dots were never the expensive
part either. The obvious reading - 150% more objects, so expect 150% more work -
would have argued against a change that turns out to be almost free, which is
why the issue was right to ask for a measurement rather than a judgement.
The draw report is windowed by time, not by call count: the handler runs at the
minimum delay whether or not anything is dirty, so a count-based window fires
every few tens of milliseconds, buries the log, and still says nothing about how
often a real repaint happens. It now reports every ten seconds and counts
repaints past a 5 ms threshold - 144 per ten seconds in steady state.
The trail test is rewritten against the capacity rather than against the number
it currently produces, since that number is now derived and a test pinned to 24
would have broken for a reason that says nothing about the ring buffer.
Refs #136
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PurpleSentinel
added a commit
that referenced
this pull request
Oct 16, 2026
… of the wrong code path (#212) Reverts #211. The render cost was measured while the device was being moved by hand. Motion keeps GravityCalibration from completing, so resolve() stays invalid, the meter state never reaches ready, and GRadarScreen::update() takes the branch that hides every trail dot without positioning or restyling it. Every figure in #211 is the cost of drawing nothing. Measured again with the device untouched: 24 points 60 points object work, mean 4,889-6,252 us 13,658-18,303 us share of the 50 ms display period 12-16% 36-41% worst repaint 38,532-41,095 us 53,101-55,442 us repaints per 10 s 147-152 166-170 Three times the object work and 14 ms more repaint, so the increase was a regression rather than nearly free. The revert is complete, instrumentation included, to put main back to a known state. Re-landing the instrumentation on its own is worth proposing separately. The pre-existing finding stands: at 24 points a repaint already costs ~40 ms against a 25 ms service period, on the task that also dispatches the receiver poll, with a fix due every 40 ms at 25 Hz. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the two outstanding boxes on #136. The radar itself was built; these were the items left unticked.
The trace was 1.2 seconds, not "a few"
kImuTrailCapacitywas a bare24. The trail is appended once per display service, so at the 20 Hz display cadence that is 1.2 seconds. #136 predicted exactly this and asked for the capacity to be sized against the chosen update rate; the rate has since been chosen, so the number it was waiting on now exists.The span is now what gets chosen and the capacity is derived from it:
Three seconds is picked against what the trace is for: a corner in a Seven runs roughly two to four seconds from turn-in to unwinding, so the driver sees the corner they have just taken. A
static_assertinscreen_router.cpptieskImuTrailSamplePeriodUsto the realkDisplayServicePeriodUs, so changing the display period fails the build rather than silently rescaling the trace. That missing coupling is how a capacity could be read as a duration in the first place.Render cost, measured
The other unticked box, and the reason the span could be raised with any confidence. Two reports, because the two costs scale differently — timing only the cheap one would have been worse than not measuring:
radar:the object work, timed at the call site so the UI component stays free ofesp_timerand buildable on the hostdraw:lv_timer_handlerin the panel task, which is where drawing actually happens. Repositioning an object only marks an area dirty; the pixels move later.Measured on hardware at both capacities, G-ONLY on screen, device in motion throughout:
Tripling the trace cost 2.3% more repaint time. Twenty-four dots already spanned the whole radar, so the invalidated area was already saturated and more dots inside it are nearly free. Per-point object cost works out at 18.6 us against a fixed 1.28 ms for the rings, labels, markers and text, so the dots were never the expensive part either.
The obvious reading — 150% more objects, so expect 150% more work — would have argued against a change that turns out to be almost free. #136 was right to ask for a measurement rather than a judgement.
The draw report is windowed by time, not call count: the handler runs at the minimum delay whether or not anything is dirty, so a count-based window fired every ~70 ms, produced 474 log lines in 45 seconds, and still said nothing about how often a repaint happens.
A finding this measurement surfaced, not addressed here
A repaint costs ~24 ms against a 25 ms service period — 97% — and it already did at 24 points, so it is not caused by this change. At 144 repaints per 10 s that is a ~35% duty cycle on the task that also dispatches the GNSS poll, and a poll arriving mid-repaint waits up to 24 ms. At 25 Hz a fix is due every 40 ms, so nothing is lost today, but most of the jitter margin is spent. This is pre-existing and worth its own issue rather than being folded in here; I have not raised one yet because I would want to confirm what area is actually being invalidated before claiming why.
It is also the reason not to grow the trace further on the strength of these numbers alone. 5 s (100 points) would cost ~6% of the display period in object work and probably nothing in repaint, but the repaint figure is close enough to its budget that the next change there should be a reduction, not an addition.
Verification
make check→CHECK_RC=0make repo-checkpasseskImuTrailCapacityrather than the number it currently produces, and twostatic_asserts now hold the span property: that it covers at least 2 s, and that capacity and span agree. The first would have failed before this change🤖 Generated with Claude Code