summaryrefslogtreecommitdiffstats
path: root/tools/cuda-ab/perf/perf.sh
blob: dc8a6286404c5d6879cafe6f88263e23d09671ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#!/bin/bash
# SPDX-License-Identifier: BSD-3-Clause
#
# perf.sh - near-native performance harness for the Open-IOV emulated vGPU.
#
# Runs the same perf_workload binary on the host (bare metal, the oracle) and
# in the guest (emulated vGPU, via scripts/vgpu_vm.py), captures the host-side
# vgsp evidence that matches each run, and compares guest/host ratios.
#
# Usage:
#   perf.sh check                 # prerequisites; pings the VM if reachable,
#                                 # never touches GPU/module/QEMU
#   perf.sh host                  # build + oracle runs -> results/<stamp>-host/
#   perf.sh guest                 # push + measured runs -> results/<stamp>-guest/
#   perf.sh all                   # host + guest + compare
#   perf.sh compare [HOST_DIR] [GUEST_DIR]   # default: newest of each
#   perf.sh push                  # push the built binary only
#
# Sequencing note: the bare-metal oracle needs the physical GPU on the stock
# distribution driver, so run `host` while no campaign cycle holds the device
# (before insmod of vgsp, or after release). `guest` runs only in a green VM
# under the supervised campaign rules. See perf/PLAN-AND-HARNESS.md.
#
# Environment:
#   PERF_RUNS      process runs per side (default 5; protocol wants 10)
#   PERF_N_WARM    warm launches per run (default 50; protocol 100)
#   PERF_N_THRU    throughput launches per run (default 100)
#   PERF_TIMEOUT   guest exec timeout in seconds (default 900)
#   PERF_VMEXEC    guest executor (default scripts/vgpu_vm.py)
#   PERF_STAMP     shared stamp for host+guest pairing
#   PERF_ORACLE    host results.json for guest/compare without a fresh host run
set -u

HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="$(cd "$HERE/../../.." && pwd)"
VMEXEC="${PERF_VMEXEC:-$REPO/scripts/vgpu_vm.py}"
RUN_ROOT="${PERF_RUN_ROOT:-$HERE/results}"
BIN="${PERF_BIN:-$HERE/bin/perf_workload}"
SRC="$HERE/perf_workload.cu"
COLLECT="$HERE/collect.py"
STAMP="${PERF_STAMP:-$(date +%Y%m%d-%H%M%S)}"
RUNS="${PERF_RUNS:-5}"
N_WARM="${PERF_N_WARM:-50}"
N_THRU="${PERF_N_THRU:-100}"
GUEST_TIMEOUT="${PERF_TIMEOUT:-900}"
STATE_FILE="/sys/kernel/debug/vgsp/gsp-phys-state"
GR_GREP='vgsp-gr:|work_gr:|sema-gr:|vgsp-sema: sema-svc:'

build() {
	command -v nvcc >/dev/null || {
		echo "nvcc not found (install nvidia-cuda-toolkit)" >&2
		return 2
	}
	mkdir -p "$HERE/bin"
	echo "[*] build $BIN"
	nvcc -O2 -arch=sm_75 --cudart=static -o "$BIN" "$SRC"
}

run_binary() {
	"$BIN" --n-warm="$N_WARM" --n-thru="$N_THRU"
}

meta_common() {
	local out="$1" role="$2"

	{
		echo "stamp: $STAMP"
		echo "role: $role"
		echo "date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
		echo "uname: $(uname -r)"
		echo "runs: $RUNS n_warm: $N_WARM n_thru: $N_THRU"
		echo "module_ko_sha256: $(sha256sum "$REPO/kernel/vgsp.ko" 2>/dev/null | awk '{print $1}' || echo unavailable)"
	} > "$out"
}

meta_clocks_host() {
	local out="$1"

	{
		echo "--- nvidia-smi -q -d CLOCK (host) ---"
		nvidia-smi -q -d CLOCK 2>&1 || echo "nvidia-smi unavailable"
	} >> "$out"
}

capture_state() {
	local out="$1"

	{
		echo "--- gsp-phys-state $(date -u +%Y-%m-%dT%H:%M:%SZ) ---"
		cat "$STATE_FILE" 2>/dev/null ||
			sudo -n cat "$STATE_FILE" 2>/dev/null ||
			echo "unavailable (debugfs needs root)"
	} > "$out"
}

capture_dmesg() {
	local out="$1"

	{
		dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || echo "unavailable"
	} | grep -E "$GR_GREP" | tail -n 3000 > "$out"
}

capture_dmesg_marker() {
	local out="$1"

	{
		dmesg 2>/dev/null || sudo -n dmesg 2>/dev/null || echo "unavailable"
	} | tail -n 1 > "$out"
}

run_host() {
	local dir="$RUN_ROOT/$STAMP-host"

	build || return $?
	mkdir -p "$dir"
	meta_common "$dir/meta.txt" host
	meta_clocks_host "$dir/meta.txt"

	if ! nvidia-smi -L >/dev/null 2>&1; then
		echo "ERROR: host GPU not on the distribution driver (claimed or unavailable)." >&2
		echo "       Run 'perf.sh host' while no vgsp cycle holds the device." >&2
		return 2
	fi

	local i rc=0

	for i in $(seq 1 "$RUNS"); do
		echo "[*] host run $i/$RUNS"
		run_binary > "$dir/run-$i.txt" 2>&1 || rc=1
	done
	cat "$dir"/run-[0-9]*.txt > "$dir/run.txt"
	python3 "$COLLECT" --role host --guest "$dir"/run-[0-9]*.txt \
		--meta "$dir/meta.txt" --out "$dir/results.json" || return 1
	if [ "$rc" -ne 0 ]; then
		echo "WARNING: at least one host run failed self-verification" >&2
	fi
	echo "[*] host results: $dir/results.json"
}

push_bin() {
	local b64 chunk off total size

	[ -x "$BIN" ] || build || return 2
	b64=$(base64 -w0 "$BIN")
	total=${#b64}
	off=0
	size=70000
	echo "[*] pushing $(stat -c%s "$BIN") bytes to guest"
	"$VMEXEC" exec "rm -f /tmp/perf_workload.b64" >/dev/null 2>&1
	while [ "$off" -lt "$total" ]; do
		chunk=${b64:$off:$size}
		"$VMEXEC" exec "echo -n '$chunk' >> /tmp/perf_workload.b64" >/dev/null 2>&1
		off=$((off + size))
	done
	"$VMEXEC" exec "base64 -d /tmp/perf_workload.b64 > /tmp/perf_workload && chmod +x /tmp/perf_workload && rm -f /tmp/perf_workload.b64 && echo PUSH_OK"
}

run_guest() {
	local dir="$RUN_ROOT/$STAMP-guest"

	build || return $?
	mkdir -p "$dir"
	meta_common "$dir/meta.txt" guest

	local ping

	ping="$("$VMEXEC" exec "echo VMPING" 2>&1 || true)"
	if [[ "$ping" != *VMPING* ]]; then
		echo "ERROR: guest not reachable via $VMEXEC (is the VM running?)" >&2
		return 2
	fi

	push_bin || return 1
	capture_state "$dir/host-state-before.txt"
	capture_dmesg_marker "$dir/dmesg-marker.txt"

	{
		echo "--- guest driver/GPU (via guest agent) ---"
		"$VMEXEC" exec "nvidia-smi --query-gpu=name,driver_version --format=csv" 2>&1
		"$VMEXEC" exec "nvidia-smi -q -d CLOCK" 2>&1
	} > "$dir/meta-guest.txt"

	local i rc=0

	for i in $(seq 1 "$RUNS"); do
		echo "[*] guest run $i/$RUNS (timeout ${GUEST_TIMEOUT}s)"
		"$VMEXEC" exec --timeout "$GUEST_TIMEOUT" \
			"/tmp/perf_workload --n-warm=$N_WARM --n-thru=$N_THRU" \
			> "$dir/run-$i.txt" 2>&1
		[ $? -ne 0 ] && rc=1
	done
	cat "$dir"/run-[0-9]*.txt > "$dir/run.txt"

	capture_state "$dir/host-state-after.txt"
	capture_dmesg "$dir/host-gr-dmesg.txt"
	python3 "$COLLECT" --role guest --guest "$dir"/run-[0-9]*.txt \
		--host-gr "$dir/host-gr-dmesg.txt" \
		--host-gr-marker "$dir/dmesg-marker.txt" \
		--state-before "$dir/host-state-before.txt" \
		--state-after "$dir/host-state-after.txt" \
		--meta "$dir/meta.txt" \
		--out "$dir/results.json" || return 1
	if [ "$rc" -ne 0 ]; then
		echo "WARNING: at least one guest run failed or timed out" >&2
	fi
	echo "[*] guest results: $dir/results.json"
}

latest_dir() {
	ls -dt "$RUN_ROOT"/*-"$1" 2>/dev/null | head -n1
}

run_compare() {
	local hdir="${1:-}" gdir="${2:-}" hostjson guestjson

	if [ -z "$hdir" ]; then
		hdir=$(latest_dir host)
		[ -n "$PERF_ORACLE" ] && hdir=$(dirname "$PERF_ORACLE")
	fi
	[ -z "$gdir" ] && gdir=$(latest_dir guest)
	if [ -n "$PERF_ORACLE" ]; then
		hostjson="$PERF_ORACLE"
	else
		hostjson="$hdir/results.json"
	fi
	guestjson="$gdir/results.json"

	[ -f "$hostjson" ] || {
		echo "missing host oracle: $hostjson" >&2
		return 2
	}
	[ -f "$guestjson" ] || {
		echo "missing guest results: $guestjson" >&2
		return 2
	}
	local out="$gdir/compare.json"
	local -a extra=()

	if [ -n "${PERF_THRESHOLDS:-}" ]; then
		extra=(--thresholds "$PERF_THRESHOLDS")
	fi
	python3 "$COLLECT" --compare "$hostjson" "$guestjson" --out "$out" \
		"${extra[@]}"
	local rc=$?

	echo "[*] compare: $out"
	return $rc
}

check() {
	echo "== perf harness check =="
	command -v nvcc >/dev/null && echo "nvcc: $(command -v nvcc)" || echo "nvcc: MISSING"
	[ -f "$VMEXEC" ] && echo "vmexec: $VMEXEC" || echo "vmexec: MISSING ($VMEXEC)"
	echo "runs=$RUNS n_warm=$N_WARM n_thru=$N_THRU timeout=$GUEST_TIMEOUT"
	[ -r "$STATE_FILE" ] && echo "gsp-phys-state: readable" ||
		echo "gsp-phys-state: not readable as user (sudo -n is used when available)"
	if nvidia-smi -L >/dev/null 2>&1; then
		echo "host GPU: available (oracle can run now)"
	else
		echo "host GPU: NOT available (claimed/unavailable; run oracle first/after release)"
	fi
	ping=""
	if [ -f "$VMEXEC" ]; then
		ping="$("$VMEXEC" exec "echo VMPING" 2>&1 || true)"
	fi
	[[ "$ping" == *VMPING* ]] && echo "guest: reachable" || echo "guest: not reachable"
}

case "${1:-all}" in
check)		check ;;
build)		build ;;
host)		run_host ;;
guest)		run_guest ;;
compare)	shift; run_compare "$@" ;;
all)
	run_host || echo "WARNING: host oracle unavailable; continuing with guest" >&2
	run_guest || exit 1
	run_compare || exit 1
	;;
push)		push_bin ;;
*)		echo "usage: $0 [check|build|host|guest|compare|all|push]" >&2
		exit 2 ;;
esac