Skip to content

Debug tools guide

Use these tools to capture a failure, compare game runs, or measure the Nova's display, audio and power behavior. Start with the tool for the symptom rather than changing several settings at once.

Feature overview · Device test plan · Known issues

Where the tools run

Nova commands below run in a root SSH session. Find the IP address and generated root password under Settings > About, and enable SSH in Settings. In host examples, replace nova with that address or your SSH host alias.

Host commands run from this repository's root on your computer. They need SSH/SCP; display-check also needs Python 3. Use a unique run name each time: the recording tools reuse or overwrite files with the same name.

The debug package installs the eight tools in the first group below. Unofficial builds include it by default; official builds, including official nightlies, omit it by default. DEBUG_PACKAGES=yes overrides that choice. For a local image build:

DEBUG_PACKAGES=yes make docker-SM8550

The installed tools and debug symbols are separate choices: merely including gdb does not provide source-level symbols for every emulator or library.

Tool Runs on What it answers
gdb Nova, debug package Where are the program's threads stopped?
strace Nova, debug package Which system calls block or fail?
vblank-rate Nova, debug package What is the panel's actual refresh rate?
vrr-probe Nova, debug package Does the panel follow changed modes and variable frame schedules?
present-probe Nova, debug package What does Vulkan presentation do with a known schedule?
tear-test Nova, debug package Does the display controller scan out mixed frames?
pcm-flags Nova, debug package What audio capabilities and period sizes does the driver advertise?
pcm-floor Nova, debug package Which audio periods sustain playback without underruns?
perf-probe Nova, ordinary image What clocks, temperature, charge and frame times accompanied a game run?
gamecapture Nova, ordinary image Can this stretch of play be recorded and replayed as a benchmark workload?
idle-probe Nova, ordinary image What consumes power and wakes threads while idle?
coremap-check Nova, ordinary image Do PS2 threads and background services use their assigned CPUs?
PortScope Nova, launcher What do the buttons, sticks, triggers and report stream actually produce?
MangoHud Nova, optional game overlay What are the performance statistics and frame intervals?
tools/display-trace Host, controls Nova Can I capture display events and retrieve the run?
tools/display-check Host, reads saved files Do presentation records and pixels agree with the kernel trace?
tools/device-tests Host, inspects Nova Which automatic device configuration checks pass, fail or skip?

First capture the context

On the Nova, create a directory for the report and collect the build and logs:

mkdir -p /storage/debug-report
cat /etc/os-release > /storage/debug-report/os-release.txt
uname -a > /storage/debug-report/kernel.txt
systemctl --failed > /storage/debug-report/failed-services.txt
journalctl -b --no-pager > /storage/debug-report/journal.txt
dmesg > /storage/debug-report/dmesg.txt

Record the game, scene or save, settings changed, charging state and whether VRR was enabled. Capture a baseline and then repeat the same scene with one change. Debuggers and tracers themselves affect timing; collect latency measurements separately from gdb or strace sessions.

Program failures: gdb and strace

gdb: thread backtraces

With a stuck RetroArch game still running, on the Nova:

game_pid=$(pgrep -xo retroarch)
test -n "$game_pid" && gdb -q -batch -p "$game_pid" \
  -ex 'set pagination off' -ex 'thread apply all bt' -ex detach \
  > /storage/debug-report/backtrace.txt 2>&1

Attaching pauses the process while the backtraces are taken; detach resumes it. Use the matching executable and libraries with symbols for useful function names and source lines. A backtrace from another build can be misleading. Change the process name to investigate a different program.

strace: system calls

On the Nova, attach to RetroArch and reproduce the problem:

game_pid=$(pgrep -xo retroarch)
test -n "$game_pid" && strace -f -tt -T -p "$game_pid" \
  -o /storage/debug-report/strace.txt

Press Ctrl+C to stop tracing and detach. -f includes threads and child processes, -tt adds timestamps, and -T records syscall duration. Look for repeated errors or long waits around the failure; an idle thread waiting in futex or poll is not by itself a bug.

Game performance, CPU placement and controls

MangoHud: on-screen statistics and frame logs

On the Nova, enable the overlay before launching the game:

. /etc/profile
set_setting portareos.mangohud.enabled 1

Relaunch the game; L1 + Y shows or hides the overlay. To record a run, create /storage/mangologs and set these options in /storage/.config/MangoHud/MangoHud.conf before launching:

output_folder=/storage/mangologs
autostart_log=1
log_duration=60
log_interval=0

These settings start a 60-second per-frame log after one second. Remove the automatic logging options afterward. application_interval_ms measures the spacing between presents; display_interval_ms measures when frames reach the display, when timing records are available. Blank display intervals mean no record was available, not a zero-duration frame. Keep the per-frame CSV, not just its _summary.csv companion.

perf-probe: comparable game runs

With the game and its MangoHud recording running, on the Nova:

perf-probe baseline 60
cat /storage/perf/baseline/summary.txt

The default duration is 60 seconds. Each label creates a directory containing env.txt, samples.csv, summary.txt and, when found, frametimes.csv. Settings are recorded once, machine statistics roughly once a second. Frame-time summaries distinguish application and display intervals.

The probe copies the newest non-summary CSV from /storage/mangologs; confirm that it belongs to this run. It does not start MangoHud logging. Without gawk, some percentile summaries are unavailable; retain the raw CSV. See the performance notes for comparison procedures.

gamecapture: benchmark workloads

While a RetroArch game is running, from a root SSH session (or as ssh nova gamecapture ... from the host):

gamecapture --duration 5     # record five minutes, then stop
gamecapture start            # record until stopped, ten minutes at most
gamecapture status
gamecapture stop
gamecapture verify /storage/captures/snes/super-mario-world-usa-001
gamecapture list

A capture is RetroArch's own replay: the core's state at the first frame and the input of every frame after it, taken at the libretro boundary, so it does not depend on the controller. Nothing is pressed or shown on the device and the game is not restarted. Each one is a directory, /storage/captures/SYSTEM/GAME-NNN/, holding replay.bsv (state included, no ROM or BIOS) and metadata.json: the ROM files, core and RetroArch with their sha256, the image build, frames and duration, input ports, the core's options, the launch's RetroArch settings, and validation. Every capture stops after ten minutes at most, the length of the longest benchmark run. A second start is refused; stop with nothing recording does nothing.

The replay ends with a checkpoint, a full state of its last frames; cores whose state is under 4 MiB also get one every 30 s. verify stops the launcher, starts the game through runemu.sh with its own config added through RUNEMU_APPENDCONFIG, plays the replay with PLAY_REPLAY_SLOT 1 on RetroArch's command socket, @retroarch/cmd, and compares each checkpoint with the core's state instead of loading it. The capture is validated only if playback reached its end - RetroArch pauses there - and every checkpoint matched; otherwise diverged or failed, and unsupported for benchmarking. The game's saves, and ARMSX2's memory cards, are copies during the run; verify.log keeps RetroArch's log. A benchmark runner plays a capture the same way. This needs retroarch 0023 (the socket), 0024 (compared checkpoints) and 0025 (playback starting on the frame it loads).

Measured on 2026-10-11 with 50 s of scripted input from boot, against the same run without a capture:

Core, game Replay RetroArch main thread Start Stop
snes9x, Super Mario World validated, every checkpoint 8.0 -> 8.5% of a core one 29 ms frame -
SwanStation, Skullmonkeys (4x, preemptive frames) validated 11.6 -> 11.7% 79 ms 42 ms
ARMSX2, Need for Speed Underground diverged 1.2 -> 1.7% (all threads 77 -> 79%) 258 ms 294 ms

Recording itself costs next to nothing; the hitches are the checkpoint saved on RetroArch's main thread, and grow with the state. ARMSX2 is unsupported: after 51 s of driving, 2.6 MB of the EE's 32 MB differed, because input reaches its threaded machine on a frame that varies from run to run.

coremap-check: PS2 thread layout

While a PS2 game is running, on the Nova:

coremap-check 5

It samples threads for five seconds, prints their allowed and observed CPUs, then checks EE (CPU Thread) on core 7, MTVU on 3 and GS on 4. It also checks the game's cgroup and system CPU isolation. Exit status is 0 for PASS, 1 for FAIL and 2 when no supported emulator is running. Currently the documented layout in this tool is ARMSX2 only.

PortScope: input diagnostics

Open Settings > Diagnostics on the Nova. Exercise every button, stick and trigger and inspect the report rate. Hold SELECT to inspect the raw MCU and gpio-keys path before InputPlumber; InputPlumber is temporarily stopped for that view. Use Home + Start to exit. This separates a hardware/driver input problem from one introduced by input translation.

Idle power: idle-probe

Exit games and players, unplug the charger, then run on the Nova:

idle-probe 60 > /storage/debug-report/idle.txt

Leave the device alone and keep the screen state consistent between runs. The default duration is 300 seconds. Output includes mean battery power, interrupt and context-switch rates, deep-idle entries on cores 0–2, and the most active threads. The tool refuses to run while charging or while a RetroArch game is detected. Its thread activity is derived from context switches, not a direct count of hardware wakeups.

This measures awake idle, including a blanked screen; it does not measure suspend current. See power measurements for the suspend procedure.

Display timing

vblank-rate: measure the running display

This tool can run alongside a game or the launcher; it does not need to own the display. Keep the panel awake for the entire measurement. On the Nova:

vblank-rate --seconds 30
vblank-rate --seconds 10 --intervals

For a known fixed mode, add its expected rate, for example --rate 119.652 --target 119.652. --rate only estimates how many vblanks to wait for; it does not change the display mode. --target reports error in parts per million. Use the CLOCK_MONOTONIC_RAW result for mode accuracy: the other clock can be slewed by time synchronization. Under VRR the measured rate follows the active workload. EINVAL commonly means the panel is blanked.

Exclusive display tests

tear-test, vrr-probe and present-probe take over the built-in display. Exit games and media players first, disconnect external displays, and run them over SSH. The examples below stop the launcher and arrange to restart it when the subshell exits. If a session is interrupted and the menu does not return, run systemctl start portarelauncher from a new SSH session.

tear-test: validate tearing detection

First force a tear, then run the synchronized test, on the Nova:

(
  trap 'systemctl start portarelauncher' EXIT
  systemctl stop portarelauncher || exit
  tear-test --frames 1200 --tear-live
  tear-test --frames 1200
)

The first run repaints a visible buffer mid-scanout. It must report frames matching neither reference CRC; if it cannot detect those deliberate tears, a clean second run proves nothing. A synchronized run should match frame A or B. Sequence gaps mean the CRC reader missed samples, not necessarily that the game dropped frames. CRCs describe the display controller's output; they cannot rule out a fault later in the link or panel.

--rate HZ selects a listed mode, --stripes N changes the pattern, and --card N selects the DRM card. Without --rate, the implementation selects the preferred mode. --async requests asynchronous flips, but a driver may reject them; --tear-live does not depend on that support. Exit 1 indicates torn frames or an error, 2 invalid arguments and 3 rejected async flips. The deliberate-tear run is expected to return 1; do not chain it to the clean run with &&.

vrr-probe: panel modes and variable schedules

On the Nova, run eight dynamic patterns for six seconds each:

(
  trap 'vrr-probe /dev/null 1 reset; systemctl start portarelauncher' EXIT
  systemctl stop portarelauncher || exit
  vrr-probe /storage/vrr-dynamic.csv 6 dynamic vrr
)

The positional arguments are output CSV, seconds per phase, phase group and optional vrr. dynamic uses fixed and varying frame intervals, repeated game frames and deliberately late frames. Watch the dark patches for flicker. static stretches fixed modes; all runs static and dynamic phases; idle commits once and waits; reset restores the base mode and clears VRR. fast tests 121/122 Hz modes beyond the normal range and is a panel experiment. vrr-on and vrr-off only change the VRR property and deliberately leave it set for the next client.

The CSV contains phase,kind,t_ns: S is scheduled, C committed, F the kernel vblank timestamp, f when userspace read the completion, and M/m the start/end of a mode set. Do not substitute callback arrival times for scanout times. See variable refresh for the measured patterns and the idle-floor test. This tool has no --help parser.

present-probe: known Vulkan schedules

On the Nova, exercise a fixed cadence of two, two and four refreshes:

(
  trap 'systemctl start portarelauncher' EXIT
  systemctl stop portarelauncher || exit
  MESA_VK_WSI_DISPLAY_VRR=0 present-probe /storage/present-cadence 30 observe cadence:2,2,4
)

The first argument is a file prefix: this writes .presents.csv and .records.csv. The former records submitted frames, targets and deliberate lateness; the latter records returned presentation timings. Times are CLOCK_MONOTONIC nanoseconds. Each frame draws a changing barcode for CRC tests.

Argument Purpose
observe Tag each present with a target and ID, then read timing records.
partial Like observe, but leave every tenth present untagged.
own Pace by vblank events without requesting display-timing records; requires a cadence.
cadence:2,2,4 Hold successive frames for the listed refresh counts.
rate:59.826 Target a content rate; use MESA_VK_WSI_DISPLAY_VRR=1 for VRR tests.
rate:30,60 Step through content rates in one session, switch=S seconds each (default 5).
read=N, read=count, read=none Change how timing records are retrieved.
late=600:4 Every 600th frame arrives 4 ms after its target in rate mode; cadence mode holds the frame before it 4 ms longer, in whole refreshes rounded up.
mode=119.652 Select the nearest display mode; default is the fastest.
recreate=10 Recreate the swapchain every ten seconds.
bar=16 Sweep a full-height white bar, 64 px wide, 16 px a frame, for watching a panel tear (tools/tear-tap).

Running with no arguments prints usage and exits 2. A completed probe alone does not establish correct scanout; pair it with the trace/check workflow.

Record and check display runs from the host

tools/display-trace runs a command on the Nova, records kernel display events plus Mesa commit markers, and copies NAME.* to the host's current directory. It stops/restarts the launcher and terminates the test command at the end. Exit games and players first; the script kills leftover mpv processes. It also replaces the selected run's previous device files and changes the active tracing configuration, so run it separately from other ftrace sessions.

From the repository root on the host:

tools/display-trace root@nova cadence-test 30 --crc -- \
  env MESA_VK_WSI_DISPLAY_VRR=0 \
  sh -c 'exec present-probe "$RUN" 40 observe cadence:2,2,4'
python3 tools/display-check cadence-test --cadence 2,2,4

--no-hold, after --crc, skips vblank-rate, which otherwise keeps the vblank interrupt on for the run; without it, refreshes are traced only while the client waits on a vblank, as in a game.

The probe duration exceeds the capture window so it remains active until the wrapper stops it. Keep $RUN single-quoted on the host: the wrapper supplies that prefix on the Nova. Use a capture window longer than two seconds.

For integer-multiple VRR with deliberate late frames:

tools/display-trace root@nova vrr-test 60 --crc -- \
  env MESA_VK_WSI_DISPLAY_VRR=1 \
  sh -c 'exec present-probe "$RUN" 70 observe rate:59.826 late=600:4'
python3 tools/display-check vrr-test --rate 59.826

The files are .trace (kernel events and Mesa markers), .out (command output), .presents.csv, .records.csv, and .crc when requested. An optional matching MangoHud per-frame log named NAME.mh.csv is checked against the same trace. The checker requires the .trace file, not the vrr-probe CSV. --crc comparisons assume every new frame changes pixels, as present-probe's barcode does; identical frames in a real game need a different interpretation.

Read the count of untraced vblanks and unmatched events before trusting the result. PASS/FAIL lines cover repeat buffers, cadence or rate, available presentation records, MangoHud data and CRCs. Optional data that is absent cannot be checked. Exit status is 1 when checks fail or no frames are found; 0 means the checks that ran passed. --floor-hz 60, 70, 80 or 90 is for captures on the older floors; --refresh-ms sets the shortest refresh when the base mode differs.

If capture aborts before cleanup, stop the remaining probe, disable the three events it enabled and restart the launcher on the Nova before another measurement:

pkill -TERM -x present-probe
echo 0 > /sys/kernel/tracing/tracing_on
for event in drm/drm_vblank_event dpu/dpu_crtc_complete_flip dpu/dpu_plane_set_scanout; do
  echo 0 > "/sys/kernel/tracing/events/$event/enable"
done
systemctl start portarelauncher

tear-tap: tears the CRC cannot see

The DPU's CRC covers what the display controller sends; a panel that tears inside itself sends nothing back. tools/tear-tap makes a person the detector: present-probe sweeps a full-height bar across the panel, and whoever watches taps the touchscreen at each tear they see. Run blind:

tools/tear-tap root@nova tear 60 ab

ab runs variable refresh and fixed refresh one after the other in random order, as tear-1 and tear-2, and only says which was which at the end. vrr or fixed runs one of them; rate=HZ sets the content rate (default 59.94, fixed refresh then uses the mode nearest twice it), bar=PX the bar's step a frame and mode=HZ the mode variable refresh runs on (default the fastest), and late=E:MS presents every Eth frame MS ms late (fixed holds the frame before it that much longer, in whole refreshes), a stall for the panel to hold and resume from. Each run leaves display-trace's files, present-probe's NAME.presents.csv and NAME.records.csv (the presents file carries each frame's hold on a fixed refresh, which display-check --cadence 2 checks against) and NAME.taps, the tap times in CLOCK_MONOTONIC seconds, the trace's clock, and prints the gaps between taps. A reaction lags a tear by a few hundred milliseconds, so set taps against the trace with that margin; a steady gap between taps is a beat between two clocks.

Measured this way on 2026-10-10 (#623): the il97680a tore about every 4 s with variable refresh at 59.94 and not at all at fixed refresh, while the CRC of the same runs showed every refresh whole.

Audio-driver measurements

pcm-flags: advertised capabilities

On the Nova, query the unused second playback PCM while PipeWire holds the first:

pcm-flags hw:0,1 32000
pcm-flags hw:0,1 44100
pcm-flags hw:0,1 48000

The arguments are device and sample rate; defaults are hw:0,1 and 32000. Output includes BATCH, period/buffer ranges and accepted period sizes in frames. In the tuned shared-memory path, BATCH should be no; that flag affects PipeWire's additional buffering. Divide frames by sample rate for seconds. Accepted sizes are capabilities, not proof of stable playback. An unsupported rate is printed even though the tool can exit 0, so read the output. hw:0,1 is useful for queries but has no working playback route.

pcm-floor: sustained playback periods

Exit games and players first. This tool needs exclusive access to hw:0,0, so stop the audio services and their activation sockets. On the Nova:

(
  trap 'systemctl start pipewire.socket pipewire-pulse.socket pipewire.service pipewire-pulse.service wireplumber.service' EXIT
  systemctl stop wireplumber.service pipewire-pulse.socket pipewire-pulse.service pipewire.socket pipewire.service || exit
  pcm-floor --device hw:0,0 --rate 32000 --channels 2 --seconds 3
)

Repeat with --rate 44100 and --rate 48000. Duration is per attempted period, not for the entire sweep. The tool streams silence and prints asked period, granted frames, buffer frames, sampled ALSA delay and underruns (xruns). The first sustained period is a useful starting point; longer runs under load are still needed. The printed latency is PCM queue delay, not application-to-speaker latency, and the floor may be imposed by driver parameters rather than the DSP. See audio measurements.

For the live audio graph, pw-top shows rate, quantum and errors, while wpctl status shows devices and routing. A follower's requested quantum is not necessarily the quantum the driver is running.

Automatic device checks

From the host repository root:

tools/device-tests root@nova
tools/device-tests root@nova game snes

The second command assumes a SNES game is already running; it does not launch one. The script prints PASS, FAIL or SKIP with test IDs from the test plan, then totals. A nonzero exit means a check failed.

Some expectations predate automatic VRR and newer audio changes, including fixed per-system refresh rates. Treat failures as observations to investigate against the current configuration, not a reason to restore obsolete settings. This checker does not replace game compatibility testing or human checks of picture, sound and controls.

Keep the evidence

Copy the report to your computer before reflashing. From the host:

scp -r root@nova:/storage/debug-report ./nova-debug-report
scp -r root@nova:/storage/perf/baseline ./nova-perf-baseline

Attach the build identity, exact command, raw output and reproduction steps to a report. For display runs, retain all files with the run prefix; for audio, retain device, rate, granted period and xrun count. Note what was deliberately changed and return those settings to their previous values after the experiment.