#!/bin/sh # Streams a message into the sandbox session on screen and prints the app's # render report for the time it took -- the standard measurement for "is a # reply that is still arriving cheap", the way transcript-bench.sh is for # scrolling one that has settled. # # ./stream-bench.sh FILE restart the app, open the session the # sandbox lists first, stream FILE into # it, report # ./stream-bench.sh -k FILE keep whatever session is open now # # The session is the first one the sandbox lists (ui-sandbox.sh spawn makes # one), and the file is echoed back a word at a time, which is the shape a # real reply arrives in. The numbers to read are `markdown reparsed while # streaming` -- how many times, and how long each -- and the worst # `record: one block`; both are proportional to how much of the reply the # tail reparse has to cover, which is what the live parse exists to bound. set -eu cd "$(dirname "$0")" . ./android-env.sh >/dev/null 2>&1 . ./bench-lib.sh keep="" while getopts k flag; do case $flag in k) keep=1 ;; *) exit 2 ;; esac done shift $((OPTIND - 1)) file=${1:?usage: stream-bench.sh [-k] FILE} sid=$(./ui-sandbox.sh api /sessions | python3 -c 'import json,sys; print(json.load(sys.stdin)[0]["id"])') if [ -z "$keep" ]; then title=$(first_session_title) [ -n "$title" ] || { echo "stream-bench: no sessions to open" >&2; exit 1; } open_session "$title" fi # Pinned to the newest end before anything is sent. The transcript never # moves under a reader who is further back (see TranscriptList), so a reply # streaming into a session parked at an older row arrives entirely # off-screen: nothing recomposes, nothing draws, and the report comes back # with two recompositions in it and no streaming counters at all. That # reads exactly like a build where the work vanished. The control is the # app's own "Jump to latest", which is only there while the newest message # is off screen -- so when it is absent the list is already where it needs # to be. Looked for before it is pressed rather than pressed blindly: a tap # by name fails the whole run when its label is missing, and missing is the # ordinary case here. ui-trace record -d 1200 -o /tmp/bench-jump.txt >/dev/null 2>&1 if ui-trace show /tmp/bench-jump.txt -m 'Jump to latest' --field box | grep -q '[0-9],[0-9]'; then ui-trace record -d 2500 --do "tap 'Jump to latest'" --do 'wait 1000' \ -o /tmp/bench-tolatest.txt >/dev/null fi # The first copy resets the report's window; see transcript-bench.sh. copy_render_report /tmp/bench-reset.txt adb logcat -c ./ui-sandbox.sh send "$sid" "@$file" >/dev/null # Until the reply has finished, measured by the transcript rather than by # the status. A session is idle at both ends of a turn, and polling for # "idle" answers on the first poll -- before the turn has even started -- # so the report then covers the moment between the send and the first # delta, and prints a window with nothing in it. The event count only # grows, so "it stopped growing" is the one signal that cannot be true # before the work begins. events() { ./ui-sandbox.sh api "/sessions/$sid/transcript?limit=1" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d[-1]["seq"] if d else 0)' } last="" still=0 i=0 while [ "$i" -lt 180 ]; do now=$(events) if [ "$now" = "$last" ]; then still=$((still + 1)) [ "$still" -ge 2 ] && break else still=0 fi last=$now sleep 2 i=$((i + 1)) done sleep 1 copy_render_report /tmp/bench-report.txt sleep 1 adb logcat -d -s ai-app:I | sed -n '/ai-app render report/,$p' | sed 's/^.*ai-app : //'