Merge branch 'openxr-work' into claude/project-thread-asqg38

This commit is contained in:
mitch030504 authored and GitHub committed 2026-10-05 14:45:11 +02:00
commit ced586d159
2 files changed
+378

No files matched your search

+372
View File
@@ -0,0 +1,372 @@
#!/usr/bin/env bash
# Collects what a Steam Frame problem report needs into one .tar.gz on this machine: the game's run
# logs and crash files, Config.toml, the install, SteamVR's logs and OpenXR runtime, the GPU and
# Vulkan driver, and the system's state (SteamOS version, memory, thermals, kernel log, core dumps).
# It only reads on the Frame, apart from a temporary folder it removes again.
#
# Launcher/frame-diagnostics.sh --frame HOST [options]
#
# Or without downloading anything first:
#
# curl -fsSL https://raw.githubusercontent.com/mitch030504/Wiicompiled_VR_Frame/openxr-work/Launcher/frame-diagnostics.sh \
# | bash -s -- --frame steamos@<frame-ip>
#
# Options:
# --frame HOST the Frame: an SSH destination (steamos@<frame-ip>, or an ~/.ssh/config host such
# as Frame Control's "frame"), or "local" when running on the Frame (default frame)
# --runs N how many of the newest run folders to take (default 3; 0 takes them all)
# --game-dir DIR where the game is installed, relative to the Frame's home or absolute
# (default devkit-game/WiiCompiled)
# --output DIR where the bundle goes (default: the current folder)
# --with-memory also take the guest memory snapshot (mem1.bin) a crash writes
# --with-core also take the newest WiiCompiled core dump, when systemd-coredump kept one (large)
# -h, --help
#
# The bundle holds summary.txt first: the version, which startup steps the newest run reached, its
# crash files, the end of its log and its last frame-pacing lines. The disc is never copied.
set -euo pipefail
# Everything is in main, so bash has read the whole script before running any of it: when it comes
# through curl | bash, a step that reads stdin cannot eat the rest. (Not indented, for the heredocs.)
main() {
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
note() { printf ' %s\n' "$*"; }
fail() {
printf '\nframe-diagnostics.sh: error: %s\n' "$*" >&2
exit 1
}
usage() { awk 'NR == 1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$script_path"; }
script_path=${BASH_SOURCE[0]:-}
frame=frame
runs=3
game_dir=devkit-game/WiiCompiled
output_dir=.
with_memory=no
with_core=no
while [[ $# -gt 0 ]]; do
case "$1" in
--frame) frame=$2; shift 2 ;;
--runs) runs=$2; shift 2 ;;
--game-dir) game_dir=$2; shift 2 ;;
--output) output_dir=$2; shift 2 ;;
--with-memory) with_memory=yes; shift ;;
--with-core) with_core=yes; shift ;;
-h|--help)
if [[ -n "$script_path" && -f "$script_path" ]]; then usage; else echo "See the comment at the top of the script."; fi
exit 0 ;;
*) fail "unknown argument: $1 (see --help)" ;;
esac
done
[[ "$runs" =~ ^[0-9]+$ ]] || fail "--runs must be a number"
# The game folder reaches the Frame as one word of an SSH command line.
[[ "$game_dir" =~ ^[A-Za-z0-9._/~-]+$ ]] || fail "--game-dir may only hold letters, digits and . _ / ~ -"
mkdir -p "$output_dir"
output_dir=$(cd "$output_dir" && pwd)
stamp=$(date +%Y%m%d-%H%M%S)
bundle="$output_dir/frame-diagnostics-$stamp.tar.gz"
partial="$bundle.partial"
trap 'rm -f "$partial"' EXIT
if [[ "$frame" == local ]]; then
say "Collecting on this machine"
on_frame() { bash -s -- "$@"; }
else
say "Collecting from $frame over SSH"
on_frame() { ssh "$frame" bash -s -- "$@"; }
fi
# The collector runs on the Frame and writes the bundle to stdout; its progress goes to stderr.
if ! on_frame "$runs" "$game_dir" "$with_memory" "$with_core" "$stamp" <<<"$collector" >"$partial"; then
fail "collecting stopped; the output above says why. If SSH could not connect, turn on Developer
Mode and set a user password on the Frame (Steam Settings > System), then check the address."
fi
gzip -t "$partial" 2>/dev/null || fail "the Frame sent back something that is not a bundle; the output above says why"
mv "$partial" "$bundle"
say "Done"
note "$bundle ($(du -h "$bundle" | cut -f1))"
summary=$(tar -xzOf "$bundle" "frame-diagnostics-$stamp/summary.txt" 2>/dev/null || true)
if [[ -n "$summary" ]]; then
# The crash files' heads stay in the bundle; the rest is printed.
printf '\n%s\n' "$summary" | awk '/^--- crash_/ { exit } { print " " $0 }'
fi
note ""
note "Logs can hold your user name and paths; look through the bundle before posting it publicly."
}
# ---------------------------------------------------------------------------------------------
# Runs on the Frame: bash -s -- RUNS GAME_DIR WITH_MEMORY WITH_CORE STAMP
read -r -d '' collector <<'COLLECTOR' || true
set -uo pipefail
runs=$1 game_dir=$2 with_memory=$3 with_core=$4 stamp=$5
log() { printf ' %s\n' "$*" >&2; }
[[ "$game_dir" == /* ]] || game_dir="$HOME/${game_dir#\~/}"
tmp=$(mktemp -d "${TMPDIR:-/tmp}/frame-diagnostics.XXXXXX") || exit 1
trap 'rm -rf "$tmp"' EXIT
root="$tmp/frame-diagnostics-$stamp"
mkdir -p "$root"/{system,gpu,game,steamvr}
# run FILE COMMAND...: the command's output in FILE under a header naming it; a missing tool or a
# hung one is noted rather than stopping the collection.
run() {
local file=$root/$1; shift
{
printf '$ %s\n' "$*"
if ! command -v "$1" >/dev/null 2>&1; then
echo "(not installed)"
else
timeout 30 "$@" 2>&1
local status=$?
(( status == 0 )) || echo "(exit status $status)"
fi
echo
} >>"$file"
}
# grab SOURCE DEST: a copy of a file or folder, if it exists.
grab() {
[[ -e "$1" ]] || return 0
mkdir -p "$(dirname "$2")"
cp -R "$1" "$2" 2>/dev/null || log "could not copy $1"
}
# grab_tail SOURCE DEST: the last 4 MB of a log that can grow without bound.
grab_tail() {
[[ -f "$1" ]] || return 0
mkdir -p "$(dirname "$2")"
tail -c 4194304 "$1" >"$2" 2>/dev/null || log "could not copy $1"
}
# --- System -------------------------------------------------------------------------------------
log "system"
run system/os.txt cat /etc/os-release
run system/os.txt uname -a
run system/os.txt uptime
run system/os.txt date --iso-8601=seconds
run system/hardware.txt lscpu
run system/hardware.txt free -m
run system/hardware.txt cat /proc/meminfo
run system/hardware.txt cat /proc/pressure/memory
run system/storage.txt df -h "$HOME" /tmp /run/media
run system/storage.txt lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINTS,LABEL
run system/storage.txt findmnt -t ext4,btrfs,exfat,vfat,f2fs
{
echo "zone type temp(milli-C)"
for zone in /sys/class/thermal/thermal_zone*; do
[[ -r "$zone/temp" ]] && echo "${zone##*/} $(cat "$zone/type" 2>/dev/null) $(cat "$zone/temp" 2>/dev/null)"
done
echo
echo "cpu cur/min/max(kHz) governor"
for cpu in /sys/devices/system/cpu/cpu[0-9]*/cpufreq; do
[[ -r "$cpu/scaling_cur_freq" ]] || continue
echo "$(basename "$(dirname "$cpu")") $(cat "$cpu/scaling_cur_freq")/$(cat "$cpu/scaling_min_freq")/$(cat "$cpu/scaling_max_freq") $(cat "$cpu/scaling_governor" 2>/dev/null)"
done
echo
for supply in /sys/class/power_supply/*; do
[[ -r "$supply/uevent" ]] && { echo "${supply##*/}:"; sed 's/^/ /' "$supply/uevent"; }
done
} >"$root/system/thermal-power.txt" 2>&1
run system/sensors.txt sensors
run system/processes.txt ps -eo pid,ppid,etime,pcpu,pmem,rss,args --sort=-pcpu
# The kernel log holds GPU hangs and out-of-memory kills; the previous boot's covers a crash that
# took the headset down with it.
run system/kernel-this-boot.txt journalctl -k -b 0 --no-pager -n 5000
run system/kernel-previous-boot.txt journalctl -k -b -1 --no-pager -n 5000
run system/kernel-dmesg.txt dmesg
run system/user-journal.txt journalctl --user -b 0 --no-pager -n 3000
run system/coredumps.txt coredumpctl list --no-pager --since=-7d
run system/coredumps.txt coredumpctl info --no-pager WiiCompiled
# --- GPU and Vulkan -----------------------------------------------------------------------------
log "GPU and Vulkan"
run gpu/vulkaninfo-summary.txt vulkaninfo --summary
run gpu/vulkaninfo.txt vulkaninfo
run gpu/eglinfo.txt eglinfo -B
{
for card in /sys/class/drm/card*; do
[[ -e "$card/device/uevent" && "$card" != *-* ]] || continue
echo "${card##*/}:"; sed 's/^/ /' "$card/device/uevent"
for f in "$card"/device/devfreq/*/{cur_freq,max_freq,governor}; do
[[ -r "$f" ]] && echo " ${f#"$card"/device/}: $(cat "$f")"
done
done
for f in /sys/kernel/debug/dri/*/name; do [[ -r "$f" ]] && cat "$f"; done
} >"$root/gpu/drm.txt" 2>&1
for dir in /usr/share/vulkan/{icd.d,implicit_layer.d,explicit_layer.d} /etc/vulkan/{icd.d,implicit_layer.d} \
"$HOME/.local/share/vulkan/implicit_layer.d"; do
[[ -d "$dir" ]] && { echo "== $dir"; ls -l "$dir"; }
done >"$root/gpu/vulkan-manifests.txt" 2>&1
# --- The game -----------------------------------------------------------------------------------
log "the game in $game_dir"
# Where the game keeps Config.toml and Logs: UserData beside a portable.txt at or above the
# executable (four levels up at most), otherwise $XDG_DATA_HOME/WiiCompiled.
data_dir=${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled
search=$game_dir
for _ in 0 1 2 3 4; do
if [[ -f "$search/portable.txt" ]]; then data_dir=$search/UserData; break; fi
[[ "$search" == / ]] && break
search=$(dirname "$search")
done
{
echo "game dir: $game_dir"
echo "data dir: $data_dir"
echo
ls -la "$game_dir" 2>&1
echo
if [[ -f "$game_dir/WiiCompiled" ]]; then
file "$game_dir/WiiCompiled" 2>&1
sha256sum "$game_dir/WiiCompiled" 2>&1
stat -c 'modified %y' "$game_dir/WiiCompiled" 2>&1
echo
echo "libraries it loads:"
ldd "$game_dir/WiiCompiled" 2>&1
else
echo "(no WiiCompiled executable here)"
fi
echo
ls -la "$data_dir" 2>&1
} >"$root/game/install.txt"
grab "$game_dir/build-fingerprint.json" "$root/game/build-fingerprint.json"
grab "$data_dir/Config.toml" "$root/game/Config.toml"
grab "$data_dir/gamecontrollerdb.txt" "$root/game/gamecontrollerdb.txt"
grab_tail "$data_dir/wii_accel_trace.csv" "$root/game/wii_accel_trace.csv"
grab_tail "$HOME/wiicompiled-frame/build.log" "$root/game/build.log"
# The disc the config points at: is it there, whole, and the right release? Never copied.
dvd_root=$(sed -n 's/^[[:space:]]*dvd_root[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$data_dir/Config.toml" 2>/dev/null | head -n 1)
{
echo "dvd_root: ${dvd_root:-(not set in Config.toml)}"
if [[ -n "$dvd_root" ]]; then
if [[ -d "$dvd_root" ]]; then
for f in sys/boot.bin sys/main.dol files/rel/StaticR.rel; do
[[ -f "$dvd_root/$f" ]] && echo "$f: present" || echo "$f: MISSING"
done
[[ -f "$dvd_root/sys/boot.bin" ]] && echo "game ID: $(head -c 6 "$dvd_root/sys/boot.bin" | tr -dc 'A-Z0-9') (RMCP01 expected)"
echo "size: $(du -sh "$dvd_root" 2>/dev/null | cut -f1)"
findmnt -T "$dvd_root" 2>&1
else
echo "MISSING: the folder does not exist (an SD card that is not mounted?)"
fi
fi
} >"$root/game/disc.txt"
# The newest run folders (Logs/<product>_<epochSeconds>_pid<pid>/), console.log and crash files.
logs_dir=$data_dir/Logs
mapfile -t run_dirs < <(ls -1dt "$logs_dir"/*/ 2>/dev/null | sed 's:/$::')
(( runs > 0 && ${#run_dirs[@]} > runs )) && run_dirs=("${run_dirs[@]:0:runs}")
log "${#run_dirs[@]} run folder(s) from $logs_dir"
ls -lt "$logs_dir" >"$root/game/all-runs.txt" 2>&1
for dir in "${run_dirs[@]}"; do
dest=$root/game/Logs/${dir##*/}
mkdir -p "$dest"
for f in "$dir"/*; do
[[ -f "$f" ]] || continue
[[ "${f##*/}" == mem1.bin && "$with_memory" != yes ]] && continue
cp "$f" "$dest/" 2>/dev/null || log "could not copy $f"
done
done
if [[ "$with_core" == yes ]] && command -v coredumpctl >/dev/null 2>&1; then
log "the newest core dump"
timeout 120 coredumpctl dump --no-pager -o "$root/system/WiiCompiled.core" WiiCompiled >/dev/null 2>&1 ||
log "no core dump was available"
fi
# --- SteamVR and OpenXR -------------------------------------------------------------------------
log "SteamVR and OpenXR"
steam_dir=""
for candidate in "$HOME/.local/share/Steam" "$HOME/.steam/steam" "$HOME/.steam/root"; do
[[ -d "$candidate/logs" ]] && { steam_dir=$(readlink -f "$candidate"); break; }
done
if [[ -n "$steam_dir" ]]; then
for f in "$steam_dir"/logs/vr*.txt "$steam_dir"/logs/compat_log.txt "$steam_dir"/logs/console_log.txt \
"$steam_dir"/logs/content_log.txt; do
[[ -f "$f" ]] && grab_tail "$f" "$root/steamvr/logs/${f##*/}"
done
grab "$steam_dir/config/steamvr.vrsettings" "$root/steamvr/steamvr.vrsettings"
ls -la "$steam_dir/logs" >"$root/steamvr/logs-listing.txt" 2>&1
else
echo "no Steam folder with logs found" >"$root/steamvr/logs-listing.txt"
fi
for f in "${XDG_CONFIG_HOME:-$HOME/.config}/openxr/1/active_runtime.json" /etc/xdg/openxr/1/active_runtime.json \
/usr/share/openxr/1/active_runtime.json; do
[[ -f "$f" ]] && { echo "== $f"; cat "$f"; echo; }
done >"$root/steamvr/openxr-runtime.txt" 2>&1
pgrep -a 'vrserver|vrcompositor|vrmonitor|WiiCompiled' >"$root/steamvr/vr-processes.txt" 2>&1 ||
echo "none running" >>"$root/steamvr/vr-processes.txt"
# --- Summary ------------------------------------------------------------------------------------
newest=${run_dirs[0]:-}
{
echo "WiiCompiled VR diagnostics, $(date --iso-8601=seconds)"
echo "SteamOS: $(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-unknown} ${VERSION_ID:-} build ${BUILD_ID:-?}")"
echo "kernel: $(uname -r) uptime: $(uptime -p 2>/dev/null)"
echo "memory: $(free -m 2>/dev/null | awk '/^Mem:/ { print $7 " MB available of " $2 " MB" }')"
vk=$root/gpu/vulkaninfo-summary.txt
gpu=$(sed -n 's/^[[:space:]]*deviceName[[:space:]]*=[[:space:]]*//p' "$vk" | head -n 1)
driver=$(sed -n 's/^[[:space:]]*driverInfo[[:space:]]*=[[:space:]]*//p' "$vk" | head -n 1)
echo "GPU: ${gpu:-unknown (no vulkaninfo; see gpu/)}${driver:+, driver $driver}"
echo "game: $([[ -x "$game_dir/WiiCompiled" ]] && echo "installed in $game_dir" || echo "NOT FOUND in $game_dir")"
echo "disc: $(head -n 1 "$root/game/disc.txt" | sed 's/^dvd_root: //')$(grep -q MISSING "$root/game/disc.txt" && echo ' (PROBLEM: see game/disc.txt)')"
echo "SteamVR running: $(pgrep -x vrserver >/dev/null 2>&1 && echo yes || echo no)"
echo "game running: $(pgrep -x WiiCompiled >/dev/null 2>&1 && echo "yes (the newest run's log is still being written)" || echo no)"
echo
if [[ -z "$newest" ]]; then
echo "No run folders in $logs_dir: the game has not started on this Frame (or logs elsewhere)."
else
console=$newest/console.log
echo "newest run: ${newest##*/} ($(stat -c %y "$newest" 2>/dev/null | cut -d. -f1))"
version=$(head -n 1 "$console" 2>/dev/null | sed 's/^\[runtime\] //')
# Builds without a build-fingerprint.json beside the executable log no version; its date
# still tells builds apart.
[[ "$version" == *"version unknown"* && -f "$game_dir/WiiCompiled" ]] &&
version="$version (executable built $(stat -c %y "$game_dir/WiiCompiled" | cut -d. -f1))"
echo "version: $version"
echo
echo "startup steps (README, Troubleshooting); the first one missing is where it stopped:"
while IFS='|' read -r label pattern; do
if grep -qF -- "$pattern" "$console" 2>/dev/null; then mark="ok "; else mark="MISSING"; fi
echo " $mark $label"
done <<'STEPS'
OpenXR initialized|OpenXR initialized: runtime
Dawn creates its device through the runtime|Dawn will create its device through the runtime
Fragment density maps enabled|Fragment density maps: enabled
Vulkan swapchains ready|OpenXR Vulkan swapchains ready
Refresh rate requested|display refresh rate
Session focused|OpenXR session state -> FOCUSED
Controller profile bound|OpenXR interaction profiles:
Eye gaze available|OpenXR eye gaze: available
Eye gaze tracking|OpenXR eye gaze: tracking
STEPS
grep -qF "requires a Dawn built with Aurora's patches" "$console" 2>/dev/null &&
echo " NOTE: built without --dawn-package (Dawn lacks Aurora's patches)"
echo
crashes=$(cd "$newest" && ls crash_*.txt 2>/dev/null | tr '\n' ' ')
echo "crash files: ${crashes:-none}"
echo "lines mentioning error, fail, fatal or warn in console.log: $(grep -ciE 'error|fail|fatal|warn' "$console" 2>/dev/null)"
echo
echo "--- last 40 lines of console.log ---"
tail -n 40 "$console" 2>/dev/null
echo "--- end of console.log ---"
echo
echo "--- last frame-pacing lines ([diagnostics] openxr_logging = true) ---"
pacing=$(grep -h 'xr-diag\] 1.0' "$console" 2>/dev/null | tail -n 5)
echo "${pacing:-(none: this run had openxr_logging off)}"
for f in "$newest"/crash_*.txt; do
[[ -f "$f" ]] || continue
echo
echo "--- ${f##*/} (first 60 lines) ---"
head -n 60 "$f"
done
fi
} >"$root/summary.txt" 2>&1
log "packing"
tar -C "$tmp" -czf - "frame-diagnostics-$stamp"
COLLECTOR
main "$@" </dev/null