iris: iris::input/iris::frame diagnostics, and gating the four debug! lines that already drowned the ring
Iris asked for a button to copy raw input events and per-frame timings through the same report Copy report already produces. sense::log_input_event (one line per platform pointer sample, historical samples inline on Android) and diagnostics::log_frame (one line per frame: frame number, frame clock, time since last input, layout/draw durations, redraw kind, primitives on screen, animating) both land under iris::diagnostics's trace_enabled() gate, off by default since the ring is 2000 lines/256KiB and either target at 120Hz fills it in seconds. report_to_touch.py turns a report's iris::input lines back into a .touch file for harness/desktop replay, round-tripped in transcript-fixture's input_log_roundtrip test. Folds in docs/REVIEW-2026-09-07.md's D1: four older per-frame debug! lines (android::view's two render() lines, list.rs's fling tick, text/mod.rs's text render) were unconditional at Debug and, with the ring's RingLogger recording everything the app's Debug install lets through regardless of target, filled it before Copy report ever saw anything else. All four (and sense.rs's drag-release-samples line) are now behind the same gate. The same test proves both directions: tracing off leaves zero Debug lines from a replayed flick, tracing on produces the expected iris::input/iris::frame lines with real durations. Not wired to a Diagnostics-pane button: bench_client.rs is open under another agent. set_trace(bool) is the whole surface a control needs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
729098756d
commit
992c472975
16 files changed
+910
-57
No files matched your search
+118
@@ -195,6 +195,124 @@ to the second name, runs, looks right, and reports whatever *it* was built
|
||||
with -- which is how "log upload: this build has no server configured"
|
||||
came from a build that had one.
|
||||
|
||||
### `iris::input`/`iris::frame` diagnostics, 2026-09-07
|
||||
|
||||
Iris asked for a second button (or the same "Copy report" made to say
|
||||
more): "add another button to copy input event info so that I can do some
|
||||
stuff manually and then send the event log to you so you know what events
|
||||
the code is actually receiving. You may also want to instrument a lot of
|
||||
the code with timings so I can give you time reports too through the same
|
||||
button." This is that, built on the log ring rather than a second
|
||||
mechanism.
|
||||
|
||||
**What it writes.** `iris::sense::log_input_event` -- one line per
|
||||
platform pointer sample, from Android's `on_touch_event` (once per real
|
||||
`MotionEvent`, historical samples inline), the winit backend (once per
|
||||
pointer `WindowEvent`, no batching), and `harness::Harness::touch` (once
|
||||
per `TouchScript` line, also unbatched, which is what makes a harness
|
||||
replay round-trip exactly):
|
||||
|
||||
iris input: action=down x=540.0 y=1000.0 t=0ms history=0
|
||||
iris input: action=move x=540.0 y=1196.0 t=16ms history=3 4:540.0,1040.0 8:540.0,1086.0 12:540.0,1138.0
|
||||
|
||||
`iris::diagnostics::log_frame` -- one line per frame, called from each
|
||||
backend's own frame function after the draw (or, on the harness, where a
|
||||
draw would be):
|
||||
|
||||
iris frame: n=42 now=701ms since_input=12ms layout=8.3µs draw=1.1ms redraw=Updates primitives=384 animating=true
|
||||
|
||||
`n` is `UiRenderState::frame_number` (counts every call to `update`,
|
||||
including one that redrew nothing); `now` is milliseconds since that
|
||||
render state's own construction (`UiRenderState::epoch`, set the same way
|
||||
`Harness::base` is, so it lines up with a harness's own `t_ms`);
|
||||
`since_input` is how stale the input driving this frame was, from the last
|
||||
sample `SensorUi::run_sensors` saw; `layout`/`draw` are `Instant` pairs
|
||||
around `UiRenderState::update` and the platform's own submit+present;
|
||||
`redraw` is `RedrawKind::{None,All,Updates}`; `primitives` is
|
||||
`UiRenderState::active_primitive_count` (everything currently on screen,
|
||||
not a per-frame delta -- `take_counters`' draw/rewrite/shape counts are
|
||||
that, and `iris::frame` does not duplicate them).
|
||||
|
||||
**Reading a report**: with tracing off (the default) neither target
|
||||
appears at all. With it on, the two interleave in arrival order, so a
|
||||
flick's shape reads as a run of `iris input:` lines followed by the frames
|
||||
they drove, and a release still shows the existing `iris drag release:`/
|
||||
`iris drag release samples:` lines from `sense.rs` -- those were not
|
||||
duplicated, only (the `samples:` one) brought under the same gate.
|
||||
|
||||
**Replaying one**: `iris/benches/report_to_touch.py < report.txt >
|
||||
replay.touch` reads every `iris input: action=...` line (ignoring
|
||||
everything else in the report, prefix-agnostic -- it works on a bare
|
||||
message or a full ring line with its `HH:MM:SS.mmm LEVEL target:` header)
|
||||
and expands each event's inline historical samples into their own `move`
|
||||
lines first, oldest first, exactly as Android delivers and replays them.
|
||||
The output is an ordinary `.touch` file:
|
||||
`Harness::replay(&TouchScript::parse(&text)?)` plays it back at layer 1,
|
||||
or point `run-headless.sh phone --phone --replay` at it for layer 2.
|
||||
Verified round-trip, both directions: a harness replay of
|
||||
`flick-120hz.touch` with tracing on produces exactly six `iris::input`
|
||||
lines, and piping them through the script and re-parsing reproduces the
|
||||
same six `t_ms action x y` samples (`iris/transcript-fixture/tests/
|
||||
input_log_roundtrip.rs`).
|
||||
|
||||
**The toggle**: `iris::diagnostics::set_trace(bool)`, off by default.
|
||||
**Not `log::log_enabled!`/`log::set_max_level`**: the app already installs
|
||||
its logger at `LevelFilter::Debug` (`iris/android-app/src/lib.rs`'s
|
||||
`JNI_OnLoad`), and `client_core::log_ring::RingLogger::enabled` is
|
||||
unconditionally `true` by design ("the ring wants everything"), so a
|
||||
`log::Level::Debug` line reaches the ring regardless of what this
|
||||
instrument would prefer -- the gate has to be a crate-level flag, checked
|
||||
before `log::debug!` is even reached, and that is what `trace_enabled()`
|
||||
is. **Not wired to a button yet**: the Diagnostics pane that would hold
|
||||
the switch is in `iris/android-app/src/bench_client.rs`, which another
|
||||
agent had open at the same time this was written; `set_trace` is the whole
|
||||
surface a control needs, so wiring one is a follow-up for whoever is free
|
||||
to touch that file next.
|
||||
|
||||
**Why default off, and why the ring's size is the actual constraint**: the
|
||||
ring is 2000 lines / 256 KiB
|
||||
(`client_core::log_ring::DEFAULT_MAX_LINES`/`DEFAULT_MAX_BYTES`); a 120Hz
|
||||
session logging a line per touch sample and a line per frame fills that in
|
||||
seconds, so a caller turns tracing on only for the length of whatever is
|
||||
being investigated, not for a whole session. This is also why the report
|
||||
should say at its top whether tracing was on -- a caller reading
|
||||
`iris::diagnostics::trace_enabled()` when building the report can print
|
||||
that; nothing here does it automatically since nothing here owns the
|
||||
report's own header.
|
||||
|
||||
**D1 from `docs/REVIEW-2026-09-07.md`**: the review found that this gate
|
||||
existed (as `iris/src/diagnostics.rs`, uncommitted at the time) but four
|
||||
older per-frame `debug!` lines were not wired to it --
|
||||
`android/view.rs`'s two `render():` lines, `widget/list.rs`'s `iris fling
|
||||
tick:`, and `widget/text/mod.rs`'s `iris text render:` -- each
|
||||
unconditional at `Debug`, and between them enough to fill the ring in
|
||||
under ten seconds at 120Hz before `Copy report` ever saw anything else.
|
||||
All four (and `sense.rs`'s `iris drag release samples:`, which is
|
||||
lower-volume but the same shape) are now behind
|
||||
`iris::diagnostics::trace_enabled()`, moved onto the `iris::frame`/
|
||||
`iris::input` targets where each belongs. `iris::sense`'s `iris drag
|
||||
release:` (info level, one per gesture, low volume, and the line that
|
||||
already answered "why didn't that flick fling" from Iris's phone) is
|
||||
unchanged and ungated on purpose -- it is exactly the kind of always-
|
||||
useful summary line the ring is *for*.
|
||||
|
||||
**Verification**: `iris/transcript-fixture/tests/
|
||||
input_log_roundtrip.rs`'s one test replays `flick-120hz.touch` through a
|
||||
real capturing `log::Log` twice -- tracing off, then on -- and asserts (a)
|
||||
off leaves zero `Debug`-level lines from the whole replay (`cargo test -p
|
||||
transcript-fixture` catches a regression here immediately, not just this
|
||||
one), (b) on produces exactly one `iris::input` line per replayed sample
|
||||
and at least one `iris::frame` line with a non-zero `layout=`, and (c) the
|
||||
round trip through `report_to_touch.py` reproduces the exact script. A
|
||||
throwaway (not committed) 3000-frame timing loop through the harness with
|
||||
tracing off vs on, idle after the opening layout, measured **2.97µs/frame
|
||||
off against 3.40µs/frame on with no logger even installed** -- the
|
||||
`Instant::now()` pairs and the `trace_enabled()` atomic loads that stay
|
||||
live either way. That is a layer-1 proxy, not the phone's own bench (no
|
||||
GPU work happens there at all), but it bounds the added cost at a few
|
||||
hundred nanoseconds against a 60Hz budget of 16,600 -- three orders of
|
||||
magnitude below where it could be seen.
|
||||
|
||||
### APK size (2026-09-07)
|
||||
|
||||
Iris's question: the iris bench APK is about double the Compose bench APK
|
||||
|
||||
Reference in new issue
Block a user