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