#!/usr/bin/env bash # Sample (wall clock, last captured frame) while a UI draw capture runs. # # The `xenia_re_ui_draws_NN.log` carries frame NUMBERS and no timestamps, and # Canary logs no fps, so a draw capture can say "4 frames of black" and not how # long that is. Two runs measured 13.1 and ~28 presented fps, so a nominal rate # cannot be assumed either. # # This polls the growing log and writes `epoch frame` pairs, which invert to give # any frame's wall-clock time. # # 🔴 RESOLUTION IS ONE BUFFER FLUSH, NOT ONE FRAME. The capture writes through a # C++ ofstream, so `tail` sees the log in flush-sized bursts: measured, 69 of 125 # samples showed NO advance and the rest jumped 7-15 frames at once. Interpolating # a frame's time *inside* a burst invents precision -- it made the apparent rate # swing between 0.016 and 0.032 s/frame, which is the flush, not the guest. # # Use BRACKETS: a frame's true time lies between the last sample that had not # reached it and the first that had. Two frames inside one burst (the 3-frame # black gap between the boot splashes) are not separable at all. # # It also GUARDS canary.stdout: a guest fault dumps registers without bound # (223 MB and 519 MB observed on a filesystem at 91 %), so the run is killed if # stdout passes the cap. # # frame_clock.sh [seconds] [interval] [stdout-file] [cap-MB] set -u LOG="$1"; OUT="$2"; DUR="${3:-150}"; IVAL="${4:-0.25}"; SOUT="${5:-}"; CAP="${6:-400}" : > "$OUT" end=$(( $(date +%s) + DUR )) while [ "$(date +%s)" -lt "$end" ]; do f=$(tail -c 400000 "$LOG" 2>/dev/null | grep -oE '^--- frame [0-9]+' | tail -1 | awk '{print $3}') [ -n "$f" ] && printf '%s\t%s\n' "$(date +%s.%N)" "$f" >> "$OUT" if [ -n "$SOUT" ] && [ -f "$SOUT" ]; then mb=$(( $(stat -c %s "$SOUT") / 1048576 )) if [ "$mb" -gt "$CAP" ]; then echo "STDOUT ${mb}MB > ${CAP}MB cap — guest is dumping registers, killing" >&2 pkill -9 -x xenia_canary; exit 3 fi fi sleep "$IVAL" done echo "sampled $(wc -l < "$OUT") points"