#!/usr/bin/env bash # Launch benilla with the measurement instruments armed, so a play-test answers "where did the # frame go?" instead of only "it felt slow". # # ./measure.sh # the standard set: per-system ms, the UI tick split, per handler # ./measure.sh --stalls # the above plus the per-binding bill, which is noisier and ~2% of a # # frame; only worth it when a handler's own time is already named # ./measure.sh --frames # the above plus the per-frame phase split and the render thread's # # own stages, for a spike no per-system number accounts for; both # # perturb what they measure, so this is not the baseline set # # Everything ./run.sh does, it does: it builds, it logs to logs/latest.log and logs/errors.log. # The instruments print to stderr, which run.sh already captures, so the numbers land in the log # beside the errors. # # Why a script and not a line of environment variables: the set is long, and a run with one of # them missing looks exactly like a run that found nothing. This prints what it armed. set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Per-system milliseconds once a second, and the stall watchdog that names a system still running # after WOW_STALL_MS. This is the one that says whether a frame went to the UI or the world. export WOW_SYSTEM_TIMING=1 # The UI *pass*, split into its phases: tick, the FontString measure round-trip and layout # resolve, extract, convert, the mesh rebuild. The one that distinguishes "the addons are slow" # from "the layout is resolving a dirty graph", which the tick split below cannot see — the first # session with this armed found 11.7 ms a frame outside the tick in a run whose handler profile was # clean. export WOW_UI_PHASES=5 # The UI tick, split into its eleven engine phases, with `C_Timer` fires credited to the line that # armed them. Inside WOW_UI_PHASES' `tick` row. export WOW_UI_TICK=5 # Per event: what one fire of each event costs across every handler it reaches, and which frame # took the most of the worst one. The handler report below keys on (frame, script), which is the # wrong key for an OnEvent, because every addon library registers one frame for every event it # wants and fans out in Lua — so a 72 ms hitch reads as `AceEvent30Frame:OnEvent`, naming a # library's shared frame and no addon, no event and no cause. export WOW_UI_EVENTS=5 # Per handler: self and total time, calls, and the worst single call — the column that names a # handler stalling one frame in five hundred, which no mean can show. export WOW_UI_HANDLERS=5 # The frame's own cost, split main thread against GPU, so a dip says which side it is on. export WOW_FLASH_TRACE=1 export WOW_GPU_MS=1 if [ "${1:-}" = "--frames" ]; then # For a spike that `[sys-timing]` cannot see. A frame's wall time is `max(main app, render # app)` because bevy pipelines the render app onto its own thread, so when the render app is # the longer of the two, `wall` climbs while `cpu`, `main`, `gpu` and every per-system `worst` # all read healthy. grom's 2026-10-21 session had exactly that: a 161 ms frame in a second # whose costliest system worst was 2.9 ms over 105 runs. # # `WOW_FRAME_PHASES` prints one line per frame over the threshold, with everything outside # `Main` as its `render+present` remainder — that is the fork: Main or not. `WOW_RENDER_MS` # then says which render stage, if it is not. # # Its own mode because both perturb what they measure: the phase stamps are exclusive systems # between `Update`'s four `WorldStage` sets, and the render stamps add nine sync points to the # render thread. Use it to answer a spike question, not to measure a baseline. export WOW_FRAME_PHASES=50 export WOW_RENDER_MS=1 shift fi if [ "${1:-}" = "--stalls" ]; then # Per Lua binding. It wraps the bindings, not the globals — wrapping the globals is what # unloaded a third of the interface once (`setfenv` is level-relative, see # `benilla-ui/src/script/verb_prof.rs`). export WOW_UI_VERBS=5 shift fi echo "measure: armed —" env | grep -E '^WOW_(SYSTEM_TIMING|UI_PHASES|UI_TICK|UI_EVENTS|UI_HANDLERS|UI_VERBS|FLASH_TRACE|GPU_MS|FRAME_PHASES|RENDER_MS)=' | sort | sed 's/^/ /' exec "$root/run.sh" "$@"