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:
irisandClaude Fable 5.1 committed 2026-09-07 16:48:48 -04:00
1 parent 729098756d
commit 992c472975
16 files changed
+910 -57

No files matched your search

+43
View File
@@ -1161,6 +1161,49 @@ and per-block-row work (RUST.md's "Verification pass over Tasks A and B").
reply shapes exactly **1** text layout, the same as into a
one-paragraph one.
## 2026-09-07: `iris::diagnostics` -- a trace toggle for input/frame lines, gating four existing per-frame `debug!` calls
One new public module and one behaviour change to four existing log
lines, from Iris's "add another button to copy input event info ...
instrument a lot of the code with timings" request (RUST.md's own
section has the full account).
- **`iris::diagnostics::set_trace(bool)`/`trace_enabled() -> bool`**, a
process-global switch, off by default. It gates two new diagnostics
(`sense::log_input_event`, one line per platform pointer sample under
target `iris::input`; `diagnostics::log_frame`, one line per frame
under `iris::frame`, with the frame number, the frame clock, time
since the last input, layout/draw durations, `RedrawKind`, primitives
on screen, and whether something is animating) and, as of a same-day
review finding (D1), four *older* `debug!` lines that were previously
unconditional: `android::view`'s two `render():` lines, `widget::
list`'s `iris fling tick:`, `widget::text`'s `iris text render:`, and
`sense`'s `iris drag release samples:`. Not `log::log_enabled!`/
`log::set_max_level`, because the app installs its logger at
`LevelFilter::Debug` already and the ring records everything that
level lets through regardless of target — the gate has to live on
this side. **Not wired to a control**: the Diagnostics pane is in
`bench_client.rs`, off-limits while another agent had it open; this
is the whole surface a button needs.
- **`UiRenderState` gained `RedrawKind`, `frame_number()`, `epoch()`,
`last_layout_duration()`, `last_redraw_kind()`,
`active_primitive_count()`, `note_input(Instant)` and
`time_since_input(Instant) -> Option<Duration>`** (`iris-core`). All
read back by `log_frame`; `note_input` is called once from
`SensorUi::run_sensors`, which both backends and the harness already
share, so a frame's `since_input` is comparable across all three
without either platform doing its own bookkeeping.
- **`iris::harness::TouchAction` gained `word() -> &'static str`**, the
inverse of its own `parse` -- what a caller (here, `Harness::touch`)
hands the input logger so a `.touch` file and an `iris::input` line
agree on one spelling of each action.
- **`iris_core::Axis` gained `Debug`** — a one-line derive, needed to log
which axis a drag committed to.
- **`iris/benches/report_to_touch.py`** (new): turns a report's
`iris::input` lines back into a `.touch` file, expanding inline
historical samples into their own lines first. Round-tripped against
the harness in `iris/transcript-fixture/tests/input_log_roundtrip.rs`.
## 2026-09-07: the phone app is told which server to talk to, and pins from the link
Not an iris API change -- a client-facing one, in the crates around it,
+21 -11
View File
@@ -1046,17 +1046,27 @@ do not duplicate it there.
estimator and none of the rest. The release log gains a debug
`iris drag release samples:` line so a flick reported from the phone can
be replayed at layer 1.
- [ ] **Input-event and timing report from the phone.** Iris: "add
- [~] **Input-event and timing report from the phone.** Iris: "add
another button to copy input event info so that I can do some stuff
manually and then send the event log to you ... instrument a lot of
the code with timings so I can give you time reports through the
same button." Build on the log ring (queue: logging through Dev
Updater), not beside it: every `MotionEvent` (action, pointer
position, event time, historical sample count) and every gesture
decision (`iris drag release:` and its siblings) at debug level into
the ring; frame timings (input handled -> layout -> draw submitted,
and the fling tick's `now` against the frame clock) at debug level
into the same ring; "Copy report" already appends the ring. Add a
second button only if the report gets too long to paste -- then
"Copy input log" copies the ring alone. Says which build it came
from.
same button." **Built on the log ring, 2026-09-07** (docs/RUST.md's
own section): `iris::sense::log_input_event` (one line per platform
pointer sample -- Android's `MotionEvent`, historical samples inline;
winit's `WindowEvent`; the harness's `TouchScript` line) and
`iris::diagnostics::log_frame` (one line per frame: frame number,
the frame clock, time since the last input, layout/draw durations,
`redraw_all`/`redraw_updates`/neither, primitives on screen,
whether something is animating), both under
`iris::diagnostics::trace_enabled()`, off by default because the ring
is only 2000 lines / 256 KiB and both targets at 120Hz fill that in
seconds. `iris/benches/report_to_touch.py` turns a report's
`iris::input` lines back into a `.touch` file for layer 1/2 replay --
round-tripped in `iris/transcript-fixture/tests/
input_log_roundtrip.rs`. **Not wired to a button**: the Diagnostics
pane is `iris/android-app/src/bench_client.rs`, open under another
agent at the time this landed; `set_trace(bool)` is the whole surface
a control needs. `docs/REVIEW-2026-09-07.md`'s D1 (the ring already
drowned in per-frame `debug!` lines that predated this pass) is fixed
in the same change -- see RUST.md's section for which four call
sites.
+118
View File
@@ -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