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>
97 lines
4.0 KiB
Python
Executable File
97 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Turns `iris::input` debug lines -- from a phone's diagnostics report, or
|
|
from a report the layer-1 harness produced with tracing on
|
|
(`iris::diagnostics::set_trace(true)`) -- back into a `TouchScript` file
|
|
`iris::harness::Harness::replay` can play back at layer 1.
|
|
|
|
Why this exists: `docs/RUST.md`'s "Three test layers" box says the cheapest
|
|
layer that can answer a question wins, and a gesture that misbehaves on
|
|
Iris's phone is otherwise only describable in words. `iris::sense::
|
|
log_input_event`'s one line per platform event (Android's on_touch_event
|
|
once per `MotionEvent`, with historical samples inline; winit's once per
|
|
pointer `WindowEvent`; the harness's `touch`, once per script line) already
|
|
carries everything a `.touch` file's `t_ms action x y` needs -- this just
|
|
reads it back out and reconstructs the samples in order, expanding each
|
|
event's inline historical samples into their own `move` lines first (they
|
|
are always intermediate positions of a move, and Android documents them as
|
|
oldest first, which is also the order they appear in the line).
|
|
|
|
Usage:
|
|
report_to_touch.py < report.txt > replay.touch
|
|
report_to_touch.py report.txt > replay.touch
|
|
|
|
Only lines containing "iris input: action=..." are read; everything else in
|
|
the report (insets, frame timings, drag-release summaries) is ignored, so
|
|
this can be pointed at Copy report's whole clipboard text directly.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
# The message half of `sense::log_input_event`'s format string, prefix-
|
|
# agnostic: a real report line also carries the ring's own
|
|
# `HH:MM:SS.mmm LEVEL target:` header (`LogLine::format`) or, forwarded
|
|
# through `ai_server::client_log`, a `[<source> <clock> #<seq>]` tag ahead
|
|
# of that -- neither of which this needs to understand, since `search`
|
|
# (not `match`) finds the marker wherever it starts.
|
|
LINE_RE = re.compile(
|
|
r"iris input: action=(?P<action>\w+) x=(?P<x>-?[0-9.]+) y=(?P<y>-?[0-9.]+) "
|
|
r"t=(?P<t>[0-9]+)ms history=(?P<hist>[0-9]+)(?P<rest>.*)$"
|
|
)
|
|
# One historical sample inside `rest`: `t:x,y`, space-separated, oldest first
|
|
# -- see `log_input_event`'s own doc for why order matters.
|
|
HIST_RE = re.compile(r"(?P<t>[0-9]+):(?P<x>-?[0-9.]+),(?P<y>-?[0-9.]+)")
|
|
|
|
|
|
def _fmt(value: float) -> str:
|
|
"""The number as `TouchScript::parse`'s own `f32::parse` would round-trip
|
|
it -- an integer without a trailing `.0` where the source was one
|
|
(every coordinate here is a physical pixel), `{:g}` otherwise so a
|
|
fractional value from a real device is not silently truncated."""
|
|
if value == int(value):
|
|
return str(int(value))
|
|
return f"{value:g}"
|
|
|
|
|
|
def convert(lines):
|
|
"""Every `iris::input` line, oldest first, expanded to one `(t_ms,
|
|
action, x, y)` tuple per touch sample -- a historical sample is always
|
|
an intermediate `move`, and the event's own sample keeps its real
|
|
action (`down`/`move`/`up`/`cancel`)."""
|
|
rows = []
|
|
for line in lines:
|
|
m = LINE_RE.search(line)
|
|
if not m:
|
|
continue
|
|
hist_count = int(m.group("hist"))
|
|
hist_matches = list(HIST_RE.finditer(m.group("rest")))
|
|
if len(hist_matches) != hist_count:
|
|
print(
|
|
f"report_to_touch: {line.strip()!r} says history={hist_count} but "
|
|
f"holds {len(hist_matches)} samples -- skipped",
|
|
file=sys.stderr,
|
|
)
|
|
continue
|
|
for hm in hist_matches:
|
|
rows.append(
|
|
(int(hm.group("t")), "move", float(hm.group("x")), float(hm.group("y")))
|
|
)
|
|
rows.append(
|
|
(int(m.group("t")), m.group("action"), float(m.group("x")), float(m.group("y")))
|
|
)
|
|
return rows
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) > 2:
|
|
print("usage: report_to_touch.py [report.txt] < report.txt", file=sys.stderr)
|
|
return 2
|
|
text = open(sys.argv[1]) if len(sys.argv) == 2 else sys.stdin
|
|
for t_ms, action, x, y in convert(text):
|
|
print(f"{t_ms} {action} {_fmt(x)} {_fmt(y)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|