cef-gpu-diagnostics.sh RawHistoryFolder
1#!/usr/bin/env bash
2set -euo pipefail
3
4usage() {
5 cat <<'EOF'
6Usage: scripts/cef-gpu-diagnostics.sh [options]
7
8Capture a short, repeatable Dumber/CEF GPU-video diagnostic bundle while a
9YouTube video is playing.
10
11Options:
12 -p, --pid PID Dumber browser/root PID. Defaults to oldest/root dumber process.
13 -o, --out DIR Output directory. Default: .dev/dumber/gpu-diagnostics/<timestamp>
14 -n, --samples N amdgpu_top JSON samples. Default: 10
15 -i, --interval-ms MS amdgpu_top sample interval. Default: 1000
16 --session-log PATH Copy a Dumber session log into the bundle.
17 --codec-stats TEXT Record manually copied YouTube "Stats for nerds" text.
18 --codec-stats-file PATH Record YouTube "Stats for nerds" text from a file.
19 --devtools-port PORT Save http://127.0.0.1:PORT/json/version and /json tabs if enabled.
20 -h, --help Show this help.
21
22Recommended run shape:
23 1. Start Dumber with CEF/VAAPI/profiling enabled.
24 2. Open YouTube Stats for nerds and copy the Codecs line (or full panel text).
25 3. Run this script while playback is active.
26
27The bundle includes process/thread CPU, process command lines, /proc DRM fdinfo,
28amdgpu_top JSON capture, kernel DRM node metadata, optional DevTools tab metadata,
29and optional YouTube codec stats text.
30EOF
31}
32
33pid=""
34out_dir=""
35samples=10
36interval_ms=1000
37session_log=""
38codec_stats=""
39codec_stats_file=""
40devtools_port=""
41
42require_value() {
43 local opt="$1"
44 local value="${2:-}"
45 if [[ -z "$value" || "$value" == -* ]]; then
46 echo "$opt requires a value" >&2
47 usage >&2
48 exit 2
49 fi
50}
51
52validate_devtools_port() {
53 local value="$1"
54 if [[ ! "$value" =~ ^[1-9][0-9]*$ ]] || (( value < 1 || value > 65535 )); then
55 echo "--devtools-port must be an integer between 1 and 65535" >&2
56 exit 2
57 fi
58}
59
60while [[ $# -gt 0 ]]; do
61 case "$1" in
62 -p|--pid) require_value "$1" "${2:-}"; pid="$2"; shift 2 ;;
63 -o|--out) require_value "$1" "${2:-}"; out_dir="$2"; shift 2 ;;
64 -n|--samples) require_value "$1" "${2:-}"; samples="$2"; shift 2 ;;
65 -i|--interval-ms) require_value "$1" "${2:-}"; interval_ms="$2"; shift 2 ;;
66 --session-log) require_value "$1" "${2:-}"; session_log="$2"; shift 2 ;;
67 --codec-stats) require_value "$1" "${2:-}"; codec_stats="$2"; shift 2 ;;
68 --codec-stats-file) require_value "$1" "${2:-}"; codec_stats_file="$2"; shift 2 ;;
69 --devtools-port) require_value "$1" "${2:-}"; validate_devtools_port "$2"; devtools_port="$2"; shift 2 ;;
70 -h|--help) usage; exit 0 ;;
71 *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
72 esac
73done
74
75if [[ -z "$pid" ]]; then
76 pid="$(pgrep -o -x dumber || true)"
77fi
78if [[ -n "$pid" && ! "$pid" =~ ^[1-9][0-9]*$ ]]; then
79 echo "--pid must be a positive integer" >&2
80 exit 2
81fi
82if [[ -z "$pid" || ! -d "/proc/$pid" ]]; then
83 echo "could not find a live dumber PID; pass --pid" >&2
84 exit 1
85fi
86if ! [[ "$samples" =~ ^[1-9][0-9]*$ && "$interval_ms" =~ ^[1-9][0-9]*$ ]]; then
87 echo "--samples and --interval-ms must be positive integers" >&2
88 exit 2
89fi
90
91if [[ -z "$out_dir" ]]; then
92 out_dir=".dev/dumber/gpu-diagnostics/$(date +%Y%m%d_%H%M%S)_pid${pid}"
93fi
94mkdir -p "$out_dir"
95
96log() { printf '[cef-gpu-diagnostics] %s\n' "$*" >&2; }
97
98collect_children() {
99 local root="$1"
100 local child
101 printf '%s\n' "$root"
102 for child in $(pgrep -P "$root" || true); do
103 collect_children "$child"
104 done
105}
106
107mapfile -t pids < <(collect_children "$pid" | awk '!seen[$0]++')
108printf '%s\n' "${pids[@]}" > "$out_dir/pids.txt"
109
110{
111 echo "timestamp=$(date --iso-8601=seconds)"
112 echo "root_pid=$pid"
113 echo "samples=$samples"
114 echo "interval_ms=$interval_ms"
115 echo "uname=$(uname -a)"
116 echo "wayland_display=${WAYLAND_DISPLAY:-}"
117 echo "display=${DISPLAY:-}"
118 command -v amdgpu_top >/dev/null && amdgpu_top --version 2>/dev/null || true
119} > "$out_dir/metadata.txt"
120
121log "writing process command lines and thread CPU"
122: > "$out_dir/cmdlines.txt"
123: > "$out_dir/thread_cpu.txt"
124for p in "${pids[@]}"; do
125 [[ -d "/proc/$p" ]] || continue
126 {
127 echo "--- pid=$p comm=$(cat "/proc/$p/comm" 2>/dev/null || true)"
128 tr '\0' ' ' < "/proc/$p/cmdline" || true
129 echo
130 } >> "$out_dir/cmdlines.txt"
131 ps -L -p "$p" -o pid,tid,ppid,pcpu,pmem,stat,comm,args --sort=-pcpu >> "$out_dir/thread_cpu.txt" 2>/dev/null || true
132done
133
134log "capturing DRM fdinfo"
135mkdir -p "$out_dir/fdinfo"
136for p in "${pids[@]}"; do
137 [[ -d "/proc/$p/fdinfo" ]] || continue
138 out="$out_dir/fdinfo/pid_${p}.txt"
139 : > "$out"
140 for fd in "/proc/${p}/fdinfo"/*; do
141 [[ -r "$fd" ]] || continue
142 if grep -Eq 'drm-|amdgpu|engine|vram|gtt|cycles' "$fd" 2>/dev/null; then
143 echo "--- $fd" >> "$out"
144 cat "$fd" >> "$out" 2>/dev/null || true
145 fi
146 done
147 [[ -s "$out" ]] || rm -f "$out"
148done
149
150log "capturing DRM device metadata"
151mkdir -p "$out_dir/dri"
152for card in /sys/class/drm/card*; do
153 [[ -e "$card" && ! "$card" =~ - ]] || continue
154 name="$(basename "$card")"
155 {
156 echo "--- $card"
157 readlink -f "$card/device" || true
158 for f in vendor device revision subsystem_vendor subsystem_device; do
159 [[ -r "$card/device/$f" ]] && printf '%s=' "$f" && cat "$card/device/$f"
160 done
161 } > "$out_dir/dri/${name}.txt"
162done
163
164if command -v amdgpu_top >/dev/null; then
165 log "capturing amdgpu_top JSON (${samples} samples @ ${interval_ms}ms)"
166 amdgpu_top -J -s "$interval_ms" -n "$samples" -p -gm > "$out_dir/amdgpu_top.json" 2> "$out_dir/amdgpu_top.stderr" || true
167 if command -v jq >/dev/null; then
168 jq '{period, devices: [.devices[]? | {name: .Info.DeviceName, render: .Info.DevicePath.render, activity: .gpu_activity, total_fdinfo: .["Total fdinfo"], processes: (.fdinfo // {})}]}' \
169 "$out_dir/amdgpu_top.json" > "$out_dir/amdgpu_top.summary.json" 2>/dev/null || true
170 fi
171else
172 log "amdgpu_top not found; skipping GPU samples"
173fi
174
175if [[ -n "$session_log" ]]; then
176 if [[ ! -r "$session_log" ]]; then
177 log "--session-log is not readable: $session_log"
178 exit 2
179 fi
180 cp -- "$session_log" "$out_dir/$(basename -- "$session_log")"
181fi
182if [[ -n "$codec_stats_file" ]]; then
183 if [[ ! -r "$codec_stats_file" ]]; then
184 log "--codec-stats-file is not readable: $codec_stats_file"
185 exit 2
186 fi
187 cp -- "$codec_stats_file" "$out_dir/youtube_stats_for_nerds.txt"
188elif [[ -n "$codec_stats" ]]; then
189 printf '%s\n' "$codec_stats" > "$out_dir/youtube_stats_for_nerds.txt"
190else
191 cat > "$out_dir/youtube_stats_for_nerds.README.txt" <<'EOF'
192No YouTube codec stats were supplied. Re-run with --codec-stats or
193--codec-stats-file after copying the YouTube "Stats for nerds" panel,
194especially the Codecs line (e.g. vp09/av01/h264) and viewport/resolution.
195EOF
196fi
197
198if [[ -n "$devtools_port" ]]; then
199 validate_devtools_port "$devtools_port"
200 log "capturing DevTools metadata from port $devtools_port"
201 if ! curl --connect-timeout 1 --max-time 3 -fsS "http://127.0.0.1:${devtools_port}/json/version" > "$out_dir/devtools_version.json" 2> "$out_dir/devtools.stderr"; then
202 log "failed to fetch DevTools version metadata from port $devtools_port"
203 exit 1
204 fi
205 if ! curl --connect-timeout 1 --max-time 3 -fsS "http://127.0.0.1:${devtools_port}/json" > "$out_dir/devtools_tabs.json" 2>> "$out_dir/devtools.stderr"; then
206 log "failed to fetch DevTools tab metadata from port $devtools_port"
207 exit 1
208 fi
209fi
210
211log "bundle complete: $out_dir"
212printf '%s\n' "$out_dir"