v0.30.0 eb3068f

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"

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close