feat/data-dnd e05cb15

bench.sh RawHistoryFolder

1#!/usr/bin/env bash
2# Measures NeferWL's CPU cost per presented frame on the headless backend
3# while chromium animates a small region of a page.
4#
5# usage: tools/bench/bench.sh <page.html> <seconds>
6# env:   BENCH_X11=1      run chromium through XWayland instead of Wayland
7#        BENCH_DEBUG=cat  extra --debug categories (comma separated)
8#        BENCH_KEEP=1     leave neferwl and chromium running (prints PIDs)
9#        BENCH_PPROF=addr pprof listen address (default localhost:6061)
10#        BENCH_ROOT=dir   build neferwl from another worktree (A/B runs)
11#        BENCH_IDLE=1     static page: no frames expected, per-second numbers only
12set -euo pipefail
13
14page=${1:?usage: bench.sh <page.html> <seconds>}
15secs=${2:?usage: bench.sh <page.html> <seconds>}
16here=$(cd "$(dirname "$0")" && pwd)
17root=${BENCH_ROOT:-$(cd "$here/../.." && pwd)}
18tmp=$(mktemp -d)
19pids=()
20
21cleanup() {
22	if [ -z "${BENCH_KEEP:-}" ]; then
23		for p in "${pids[@]}"; do kill -- "-$p" 2>/dev/null || true; done
24		sleep 1
25		rm -rf "$tmp"
26	fi
27}
28trap cleanup EXIT
29
30(cd "$root" && CGO_ENABLED=0 go build -o "$tmp/nwl" ./cmd/neferwl)
31mkdir -p "$tmp/rt" "$tmp/cfg" "$tmp/state"
32chmod 700 "$tmp/rt"
33
34debug=render${BENCH_DEBUG:+,$BENCH_DEBUG}
35xflag=--no-xwayland
36[ -n "${BENCH_X11:-}" ] && xflag=
37env XDG_RUNTIME_DIR="$tmp/rt" XDG_CONFIG_HOME="$tmp/cfg" XDG_STATE_HOME="$tmp/state" WAYLAND_DISPLAY= \
38	setsid "$tmp/nwl" --backend=headless --size=5120x2160 --no-terminal $xflag \
39	--debug="$debug" --pprof="${BENCH_PPROF:-localhost:6061}" >"$tmp/nwl.out" 2>&1 </dev/null &
40nwl=$!
41pids+=("$nwl")
42log=$tmp/state/neferwl/runs/headless/latest.log
43for _ in $(seq 50); do [ -S "$tmp/rt/wayland-1" ] && break; sleep 0.1; done
44if [ ! -S "$tmp/rt/wayland-1" ]; then
45	echo "neferwl did not start: no wayland-1 socket; its output:" >&2
46	cat "$tmp/nwl.out" >&2
47	exit 1
48fi
49
50if [ -n "${BENCH_X11:-}" ]; then
51	disp=
52	for _ in $(seq 50); do
53		disp=$(grep -o '"DISPLAY":"[^"]*"' "$log" 2>/dev/null | head -1 | cut -d'"' -f4) || true
54		[ -n "$disp" ] && break
55		sleep 0.1
56	done
57	[ -n "$disp" ] || { echo "no X11 display in log" >&2; exit 1; }
58	env XDG_RUNTIME_DIR="$tmp/rt" WAYLAND_DISPLAY= DISPLAY="$disp" setsid chromium --ozone-platform=x11 \
59		--user-data-dir="$tmp/chr" --no-first-run --app="file://$here/$page" >"$tmp/chr.out" 2>&1 </dev/null &
60else
61	env XDG_RUNTIME_DIR="$tmp/rt" WAYLAND_DISPLAY=wayland-1 setsid chromium --ozone-platform=wayland \
62		--user-data-dir="$tmp/chr" --no-first-run --app="file://$here/$page" >"$tmp/chr.out" 2>&1 </dev/null &
63fi
64pids+=("$!")
65sleep 8
66
67cs() { cat /proc/"$nwl"/task/*/status | awk '/^voluntary_ctxt/{v+=$2} END{print v}'; }
68# CPU time in ns summed over threads (schedstat is finer than stat's ticks).
69cpu_ns() { cat /proc/"$nwl"/task/*/schedstat | awk '{v+=$1} END{printf "%d\n", v}'; }
70frames() { grep -c '"message":"frame"' "$log" || true; }
71
72# Per-thread "tid sched_in_count voluntary_switches" (schedstat field 3 = times scheduled in).
73threads() { for d in /proc/"$nwl"/task/*; do
74	echo "${d##*/} $(awk '{print $3}' "$d/schedstat" 2>/dev/null) $(awk '/^voluntary_ctxt/{print $2}' "$d/status" 2>/dev/null)"
75done; }
76
77th0=$(threads); t0=$(cpu_ns); f0=$(frames); c0=$(cs)
78sleep "$secs"
79th1=$(threads); t1=$(cpu_ns); f1=$(frames); c1=$(cs)
80
81if [ -z "${BENCH_IDLE:-}" ] && [ "$((f1 - f0))" -le 0 ]; then
82	echo "no frame presented in ${secs}s: the page did not animate or neferwl stalled" >&2
83	tail -n 20 "$tmp/nwl.out" >&2
84	exit 1
85fi
86
87python3 - "$((t1 - t0))" "$((f1 - f0))" "$((c1 - c0))" "$secs" "$page" "$th0" "$th1" <<'EOF'
88import sys
89t, f, c, s, page = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]), float(sys.argv[4]), sys.argv[5]
90def parse(x):
91    return {l.split()[0]: tuple(int(v) for v in l.split()[1:]) for l in x.splitlines() if len(l.split()) == 3}
92a, b = parse(sys.argv[6]), parse(sys.argv[7])
93d = {k: (b[k][0] - a.get(k, (0, 0))[0], b[k][1] - a.get(k, (0, 0))[1]) for k in b}
94top = max(d, key=lambda k: d[k][1])
95cpu = t / 1e9 / s * 100
96fr = max(f, 1)
97print(f"{page} cpu%={cpu:.2f} fps={f / s:.1f} ms/frame={t / 1e6 / fr:.3f} cs/frame={c / fr:.0f} cs/s={c / s:.0f} top_tid={top} top_wakes/s={d[top][0] / s:.0f} top_vol/s={d[top][1] / s:.0f}")
98EOF
99
100if [ -n "${BENCH_KEEP:-}" ]; then
101	echo "kept: dir=$tmp pids=${pids[*]} log=$log"
102fi

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 25

Tags 10

↑↓ moveenter openesc close