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
@@ -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
@@ -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
@@ -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