Re: [PATCH v10 00/12] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff

From: Shrikanth Hegde

Date: Sat Aug 22 2026 - 00:12:25 EST


Hi Yury.

On 8/22/26 3:57 AM, Yury Norov wrote:
========================================
Performance Numbers (powerpc, x86, s390)
========================================

PowerPC:
===================
VM1: 60VP/30EC and VM2: 30VP/20EC
Shared physical CPU pool size: 50 Cores. Each core is SMT8.
(VP - Virtual Core, EC - Entitles Core) - PowerVM terminologies of SPLPAR[2]

Default parameter values: 1000ms, 200 low threshold, 500 high threshold
Both the VMs are running the same workload. Total throughput/time of VM1+VM2
is being mentioned in all cases.

Hackbench
baseline steal_governor steal_governor
disabled enabled
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

10 groups 5.20 | 5.40 (-3.85%) | 4.65 (+10.58%)
20 groups 11.39 | 12.01 (-5.44%) | 7.09 (+37.75%)
40 groups 20.32 | 19.80 (+2.56%) | 11.31 (+44.34%)
10 groups(-p) 2.37 | 2.26 (+4.64%) | 2.06 (+13.08%)
20 groups(-p) 3.34 | 3.28 (+1.80%) | 3.20 (+4.19%)
40 groups(-p) 4.46 | 4.83 (-8.30%) | 4.26 (+4.48%)
Remarks: Net improvement with steal_governor specially high load points.

schbench ( -L -n 0 -r 30 -s 0)
baseline steal_governor steal_governor
disabled enabled
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-m 1 -t 128 2475162 | 2621246 (+5.90%) | 2527299 (+2.11%)
-m 1 -t 256 1467350 | 1470032 (+0.18%) | 1492372 (+1.71%)
-m 1 -t 512 1408813 | 1454687 (+3.26%) | 1437605 (+2.04%)
Remarks: Effectively means no-improvements or regressions

kernbench baseline steal_governor steal_governor
(elapsed time) disabled enabled
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-j nr_cpus 231 | 235 (-1.7%) | 199 (+14%)
Remarks: Net improvement in elapsed time.

Daytrader - A real life work which is a proxy for trading based
on db2[3]
baseline steal_governor steal_governor
disabled enabled
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Load@30% 1x | 0.96x | 1.53x
Load@60% 1x | 0.94x | 1.41x
Remarks: Good improvement seen at different load points.

When there is no steal time (such as dedicated LPAR, or only VM2
is running) throughput was same with steal_governor enabled/disabled
which indicates minimal overhead of steal_governor.

I have run v10 also on a smaller powerpc LPAR system and it shows
good improvements.

=======================================================================

Data from x86,s390 KVM which Ilya Leoshkevich carried out during OSPM26
time. *This was based on v2*. Idea is still the name, numbers are
expected to be better in v10 as some of the overhead has been removed.
Note: Other variations of the benchmark shows no observable
difference.

x86:
====
cascade-lake: 32 threads = 16 cores
Benchmark #VMs #CPUs/VM ΔRPS (%std)
===============================================
hackbench 8 16 90.73% ± 9.97%
hackbench 4 24 52.67% ± 7.43%
hackbench 4 16 37.96% ± 11.19%
hackbench 4 32 37.82% ± 4.38%
hackbench 12 8 36.90% ± 4.74%
hackbench 8 8 35.30% ± 3.61%
pgbench 16 4 31.77% ± 2.44%
hackbench 2 24 25.85% ± 8.63%
hackbench 16 8 24.87% ± 3.46%
pgbench 16 8 21.83% ± 2.20%
pgbench 12 8 21.35% ± 2.15%
pgbench 8 8 18.46% ± 1.01%
hackbench 2 32 15.56% ± 4.53%
pgbench 12 4 14.28% ± 2.04%
hackbench 16 4 14.07% ± 2.90%
hackbench 12 4 9.60% ± 3.49%
[...]
pgbench 4 8 -1.16% ± 3.60%
hackbench 4 4 -1.80% ± 9.55%
sysbench 12 4 -2.19% ± 0.78%
pgbench 4 24 -2.43% ± 4.38%
pgbench 4 32 -3.21% ± 0.79%
sysbench 16 4 -3.22% ± 1.09%

S390:
=====
z16: 16 threads = 8 cores (SMT-2)
Benchmark #VMs #CPUs/VM ΔRPS (std%)
===============================================
pgbench 2 8 73.50% ± 35.91%
pgbench 16 4 61.30% ± 4.09%
hackbench 16 4 54.11% ± 4.38%
hackbench 12 4 36.34% ± 4.63%
pgbench 12 4 34.83% ± 2.57%
hackbench 8 4 29.75% ± 5.86%
hackbench 8 8 25.98% ± 5.09%
pgbench 2 4 23.31% ± 33.44%
pgbench 2 16 19.95% ± 17.12%
hackbench 4 8 19.43% ± 9.33%
pgbench 8 4 19.32% ± 4.50%
[...]
schbench 8 8 -0.79% ± 0.33%
sysbench 8 8 -0.81% ± 0.39%
hackbench 4 16 -1.11% ± 5.82%
sysbench 8 4 -1.62% ± 0.49%
sysbench 16 4 -2.70% ± 0.58%
schbench 16 4 -2.73% ± 0.91%
sysbench 12 4 -2.91% ± 0.61%
hackbench 2 24 -4.99% ± 3.31%

Summary:
- Many improvement across archs specially with real life workloads.
- No major regressions observed.
- Overhead of steal_governor looks minimal when there is no steal time.
- Overhead when STEAL_GOVERNOR=n is negligible.

OK, I gave it some testing on my laptop.


Hi Yury, thanks for trying.

The results are pretty consistent: the steal ratio is converged to a
number withing the threshold, but the overall performance is 3-5% worse
comparing to baseline. I tried 2-5% and 1.5-15% boundaries.

It's 4 VMs, each running 8 vCPUs on 8 pCPU machine, the payload is
running for 2 minutes.

Steal governor off:
VM THROUGHPUT AVG STEAL%
-------- ------------ ----------------
0 13802 74.59
1 11929 76.35
2 13470 74.61
3 10259 78.67
-------- ------------ ----------------
TOTAL 49460 -
Steal governor on:
VM THROUGHPUT AVG STEAL%
-------- ------------ ----------------
0 12921 5.72
1 12762 6.11
2 10160 5.99
3 12098 6.00
-------- ------------ ----------------
TOTAL 47941 -


The test is attached below. The results are quite differ from the numbers
above, so maybe I misconfigured something? I didn't use hackbench or
similar benchmarks, just a basic math.


If cputime is all that matters to workload, i.e. if workload is just math, or
without any locks or critical sections you may not see gains.
I have seen similar observation with stress-ng --cpu --metrics IIRC. Not much different.

The reason being, previously it was vCPU but thet vCPU is preempted 75% time.
But now, it is on vCPU but it is sharing that vCPU with 3 more tasks. Effectively
it is preempted 75% time still. Plus context switch overhead will show up.
That likley accounts for your 3-5% regression.

Can you give a try with hackbench, or pgbench, etc if possible?

Shrikanth, can you please check my test and results? Is there something
that I have missed?

I think this series should include some testing. The scripts below look
bulky and they depend on virtme, but they allow to build the proper
kernel and run tests with a single command.


Ok, I will check the scripts you have attached. If they are just math, they may not be
the right benchmark to see gains for the reason explained above.


Thanks,
Yury

From 27e548d65bbe0ec01308d3329017619e165d0488 Mon Sep 17 00:00:00 2001
From: Yury Norov <ynorov@xxxxxxxxxx>
Date: Wed, 19 Aug 2026 16:31:33 -0400
Subject: [PATCH] steal governor: add testing harness based on vng

Signed-off-by: Yury Norov <ynorov@xxxxxxxxxx>
---
.../virtme-steal-payload.sh | 220 ++++++++++++
.../steal_governor_test/virtme-steal-time.sh | 321 ++++++++++++++++++
2 files changed, 541 insertions(+)
create mode 100755 drivers/virt/steal_governor_test/virtme-steal-payload.sh
create mode 100755 drivers/virt/steal_governor_test/virtme-steal-time.sh

diff --git a/drivers/virt/steal_governor_test/virtme-steal-payload.sh b/drivers/virt/steal_governor_test/virtme-steal-payload.sh
new file mode 100755
index 000000000000..02a245a900b8
--- /dev/null
+++ b/drivers/virt/steal_governor_test/virtme-steal-payload.sh
@@ -0,0 +1,220 @@
+#!/bin/sh
+# Guest-side measurable workload for the steal governor test.
+
+set -eu
+
+case $# in
+2|3|5) ;;
+*)
+ echo "usage: ${0##*/} VM_ID DURATION_SECONDS [DRIVER [LOW_THRESHOLD HIGH_THRESHOLD]]" >&2
+ exit 2
+ ;;
+esac
+
+vm_id=$1
+duration=$2
+driver=${3-}
+low_threshold=${4-}
+high_threshold=${5-}
+
+verify_parameter()
+{
+ parameter=$1
+ expected=$2
+ parameter_file=/sys/module/$driver_sysfs/parameters/$parameter
+
+ if [ ! -r "$parameter_file" ]; then
+ echo "error: driver $driver has no readable $parameter parameter" >&2
+ exit 1
+ fi
+ actual=$(cat "$parameter_file")
+ if [ "$actual" != "$expected" ]; then
+ echo "error: driver $driver $parameter is $actual, expected $expected" >&2
+ exit 1
+ fi
+}
+
+case $vm_id in
+*[!0-9]*|'')
+ echo "error: VM_ID must be a non-negative integer" >&2
+ exit 2
+ ;;
+esac
+case $duration in
+*[!0-9]*|'')
+ echo "error: DURATION_SECONDS must be a positive integer" >&2
+ exit 2
+ ;;
+esac
+if [ "$duration" -eq 0 ]; then
+ echo "error: DURATION_SECONDS must be greater than zero" >&2
+ exit 2
+fi
+if [ -n "$driver" ]; then
+ case $driver in
+ *[!A-Za-z0-9_.-]*)
+ echo "error: invalid driver module name: $driver" >&2
+ exit 2
+ ;;
+ esac
+ if [ -n "$low_threshold" ]; then
+ case $low_threshold:$high_threshold in
+ *[!0-9:]*|:*|*:)
+ echo "error: thresholds must be non-negative integers in percent * 100" >&2
+ exit 2
+ ;;
+ esac
+ if [ "$low_threshold" -ge "$high_threshold" ] ||
+ [ "$high_threshold" -ge 10000 ]; then
+ echo "error: thresholds must satisfy 0 <= LOW < HIGH < 10000" >&2
+ exit 2
+ fi
+ fi
+ if ! command -v modprobe >/dev/null; then
+ echo "error: modprobe is required to load driver $driver" >&2
+ exit 1
+ fi
+ driver_sysfs=$(printf '%s\n' "$driver" | tr '-' '_')
+ if [ ! -d "/sys/module/$driver_sysfs" ]; then
+ if [ -n "$low_threshold" ]; then
+ modprobe "$driver" \
+ low_threshold="$low_threshold" \
+ high_threshold="$high_threshold"
+ else
+ modprobe "$driver"
+ fi
+ fi
+ if [ ! -d "/sys/module/$driver_sysfs" ]; then
+ echo "error: driver $driver has no /sys/module/$driver_sysfs entry after modprobe" >&2
+ exit 1
+ fi
+ if [ -n "$low_threshold" ]; then
+ verify_parameter low_threshold "$low_threshold"
+ verify_parameter high_threshold "$high_threshold"
+ fi
+fi
+
+tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/virtme-steal-payload.XXXXXXXX")
+before=$tmpdir/before
+after=$tmpdir/after
+pids=
+
+stop_workers()
+{
+ for pid in $pids; do
+ kill "$pid" 2>/dev/null || :
+ done
+ for pid in $pids; do
+ wait "$pid" 2>/dev/null || :
+ done
+ pids=
+}
+
+cleanup()
+{
+ stop_workers
+ rm -rf -- "$tmpdir"
+}
+trap cleanup EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
+
+awk '$1 ~ /^cpu[0-9]+$/ { print $1, $9 }' /proc/stat >"$before"
+start_time=$(awk '{ print $1 }' /proc/uptime)
+deadline=$(awk -v start="$start_time" -v seconds="$duration" \
+ 'BEGIN { printf "%.2f", start + seconds }')
+workers=$(awk '$1 ~ /^cpu[0-9]+$/ { n++ } END { print n }' /proc/stat)
+
+i=0
+while [ "$i" -lt "$workers" ]; do
+ result=$tmpdir/work-$i
+ awk -v deadline="$deadline" -v result="$result" '
+ function uptime( line, fields) {
+ getline line < "/proc/uptime"
+ close("/proc/uptime")
+ split(line, fields)
+ return fields[1]
+ }
+ BEGIN {
+ batch = 1000
+ units = 0
+ value = 1
+ while (uptime() < deadline) {
+ for (iteration = 0; iteration < batch; iteration++)
+ value = (value * 1103515245 + 12345) % 2147483647
+ units++
+ }
+ printf "%.0f %.0f\n", units, units * batch > result
+ }
+ ' </dev/null &
+ pids="$pids $!"
+ i=$((i + 1))
+done
+
+worker_failed=0
+for pid in $pids; do
+ wait "$pid" || worker_failed=1
+done
+pids=
+if [ "$worker_failed" -ne 0 ]; then
+ echo "error: one or more workload processes failed" >&2
+ exit 1
+fi
+
+awk '$1 ~ /^cpu[0-9]+$/ { print $1, $9 }' /proc/stat >"$after"
+end_time=$(awk '{ print $1 }' /proc/uptime)
+elapsed=$(awk -v start="$start_time" -v end="$end_time" \
+ 'BEGIN { printf "%.2f", end - start }')
+clk_tck=$(getconf CLK_TCK 2>/dev/null || echo 100)
+
+echo VIRTME_STEAL_REPORT_BEGIN
+driver_status=none
+[ -n "$driver" ] && driver_status=$driver:loaded
+[ -n "$low_threshold" ] && \
+ driver_status="$driver_status,thresholds=$low_threshold..$high_threshold"
+echo "VM $vm_id kernel=$(uname -r) vCPUs=$workers sample=${elapsed}s driver=$driver_status"
+echo "Workload (unpinned workers)"
+printf '%-10s %12s %16s %16s\n' \
+ WORKER WORK_UNITS ITERATIONS ITERATIONS/s
+printf '%-10s %12s %16s %16s\n' \
+ ---------- ------------ ---------------- ----------------
+total_units=0
+total_iterations=0
+i=0
+while [ "$i" -lt "$workers" ]; do
+ read -r units iterations <"$tmpdir/work-$i"
+ rate=$(awk -v iterations="$iterations" -v elapsed="$elapsed" \
+ 'BEGIN { printf "%.0f", iterations / elapsed }')
+ printf '%-10s %12s %16s %16s\n' \
+ "worker$i" "$units" "$iterations" "$rate"
+ total_units=$((total_units + units))
+ total_iterations=$((total_iterations + iterations))
+ i=$((i + 1))
+done
+total_rate=$(awk -v iterations="$total_iterations" -v elapsed="$elapsed" \
+ 'BEGIN { printf "%.0f", iterations / elapsed }')
+throughput=$(awk -v units="$total_units" -v elapsed="$elapsed" \
+ 'BEGIN { printf "%.0f", units / elapsed }')
+printf '%-10s %12s %16s %16s\n' \
+ TOTAL "$total_units" "$total_iterations" "$total_rate"
+
+echo "Steal time by guest CPU"
+printf '%-8s %12s %12s %10s\n' CPU STEAL_TICKS STEAL_s STEAL_%
+printf '%-8s %12s %12s %10s\n' -------- ------------ ------------ ----------
+average_file=$tmpdir/average-steal
+awk -v hz="$clk_tck" -v elapsed="$elapsed" -v cpus="$workers" \
+ -v average_file="$average_file" '
+ NR == FNR { before[$1] = $2; next }
+ {
+ delta = $2 - before[$1]
+ total += delta
+ printf "%-8s %12d %12.2f %10.2f\n", $1, delta,
+ delta / hz, 100 * delta / hz / elapsed
+ }
+ END {
+ printf "%.2f\n", 100 * total / hz / elapsed / cpus > average_file
+ }
+' "$before" "$after"
+average_steal=$(cat "$average_file")
+echo "VIRTME_STEAL_SUMMARY $vm_id $throughput $average_steal"
+echo VIRTME_STEAL_REPORT_END
diff --git a/drivers/virt/steal_governor_test/virtme-steal-time.sh b/drivers/virt/steal_governor_test/virtme-steal-time.sh
new file mode 100755
index 000000000000..a41d362c7888
--- /dev/null
+++ b/drivers/virt/steal_governor_test/virtme-steal-time.sh
@@ -0,0 +1,321 @@
+#!/usr/bin/env bash
+# Build a minimal KVM guest kernel and measure per-vCPU steal time under load.
+
+set -euo pipefail
+
+script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+kernel_dir=$(CDPATH= cd -- "$script_dir/../../.." && pwd)
+vng=${VNG:-vng}
+build_dir=${BUILD_DIR:-"$kernel_dir/.virtme-steal"}
+payload=${PAYLOAD:-"$script_dir/virtme-steal-payload.sh"}
+vm_count=4
+vcpu_count=$(nproc)
+duration=20
+memory=512M
+driver=
+low_threshold=
+high_threshold=
+verbose=0
+declare -a config_items=()
+
+usage()
+{
+ cat <<EOF
+Usage: ${0##*/} [options] [O=DIR]
+
+Build a virtme-ng minimal kernel with paravirtual steal-time accounting,
+start multiple CPU-bound VMs, and report the steal time of every guest CPU.
+
+Options:
+ -n VMS number of VMs to run (default: $vm_count)
+ -p VCPUS vCPUs per VM (default: $vcpu_count)
+ -d SECONDS workload duration (default: $duration)
+ -m MEMORY memory per VM (default: $memory)
+ --configitem CONFIG[=VALUE]
+ enable or set a kernel config option (repeatable)
+ --driver MODULE
+ load and verify this module before running the payload
+ -lo VALUE low steal threshold in percent * 100 (for example, 150 = 1.5%)
+ -hi VALUE high steal threshold in percent * 100 (for example, 550 = 5.5%)
+ -O DIR kernel build directory (default: $build_dir)
+ -v, --verbose show per-worker and per-CPU result tables
+ -h show this help
+
+Environment equivalents: VNG, BUILD_DIR, and PAYLOAD. A custom payload is
+called inside each VM as: PAYLOAD VM_ID DURATION_SECONDS
+[DRIVER [LOW_THRESHOLD HIGH_THRESHOLD]].
+
+Local virtme-ng checkout example:
+ VNG=../virtme-ng/vng ${0##*/}
+
+The positional O=DIR form is equivalent to -O DIR, for example:
+ ${0##*/} -n 4 O=../build-linux-virtme-steal
+
+Config examples:
+ ${0##*/} --configitem CONFIG_SCHEDSTATS --configitem CONFIG_HZ_1000=y
+
+Threshold example:
+ ${0##*/} --driver steal_governor -lo 150 -hi 550
+EOF
+}
+
+while (($#)); do
+ case $1 in
+ -n|-p|-d|-m|-O|-lo|-hi|--configitem|--driver)
+ if (($# < 2)); then
+ echo "error: $1 requires an argument" >&2
+ exit 2
+ fi
+ case $1 in
+ -n) vm_count=$2 ;;
+ -p) vcpu_count=$2 ;;
+ -d) duration=$2 ;;
+ -m) memory=$2 ;;
+ -O) build_dir=$2 ;;
+ -lo) low_threshold=$2 ;;
+ -hi) high_threshold=$2 ;;
+ --configitem) config_items+=("$2") ;;
+ --driver) driver=$2 ;;
+ esac
+ shift 2
+ ;;
+ --configitem=?*)
+ config_items+=("${1#*=}")
+ shift
+ ;;
+ --driver=?*)
+ driver=${1#*=}
+ shift
+ ;;
+ -v|--verbose)
+ verbose=1
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ O=?*)
+ build_dir=${1#O=}
+ shift
+ ;;
+ O=|--configitem=|--driver=)
+ echo "error: ${1%%=*}= requires a non-empty argument" >&2
+ exit 2
+ ;;
+ *)
+ echo "error: unexpected argument: $1" >&2
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+
+require_positive_integer()
+{
+ local name=$1 value=$2
+
+ if [[ ! $value =~ ^[1-9][0-9]*$ ]]; then
+ echo "error: $name must be a positive integer (got '$value')" >&2
+ exit 2
+ fi
+}
+
+require_positive_integer "VM count" "$vm_count"
+require_positive_integer "vCPU count" "$vcpu_count"
+require_positive_integer "duration" "$duration"
+
+if [[ -n $low_threshold || -n $high_threshold ]]; then
+ if [[ -z $low_threshold || -z $high_threshold ]]; then
+ echo "error: -lo and -hi must be specified together" >&2
+ exit 2
+ fi
+ if [[ ! $low_threshold =~ ^[0-9]+$ || ! $high_threshold =~ ^[0-9]+$ ]]; then
+ echo "error: -lo and -hi must be non-negative integers in percent * 100" >&2
+ exit 2
+ fi
+ low_threshold=$((10#$low_threshold))
+ high_threshold=$((10#$high_threshold))
+ if ((low_threshold >= high_threshold)); then
+ echo "error: -lo must be less than -hi" >&2
+ exit 2
+ fi
+ if ((high_threshold >= 10000)); then
+ echo "error: -hi must be less than 10000 (100%)" >&2
+ exit 2
+ fi
+ if [[ -z $driver ]]; then
+ echo "error: -lo and -hi require --driver MODULE" >&2
+ exit 2
+ fi
+fi
+
+if [[ -n $driver && ! $driver =~ ^[A-Za-z0-9_.-]+$ ]]; then
+ echo "error: invalid driver module name: $driver" >&2
+ exit 2
+fi
+
+for index in "${!config_items[@]}"; do
+ if [[ ! ${config_items[index]} =~ ^CONFIG_[A-Z0-9_]+(=.*)?$ ]]; then
+ echo "error: invalid kernel config item: ${config_items[index]}" >&2
+ exit 2
+ fi
+ if [[ ${config_items[index]} != *=* ]]; then
+ config_items[index]="${config_items[index]}=y"
+ fi
+done
+
+if [[ ! -x $payload ]]; then
+ echo "error: guest payload is not executable: $payload" >&2
+ exit 1
+fi
+if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then
+ echo "error: /dev/kvm is not accessible; KVM is required for steal-time accounting" >&2
+ exit 1
+fi
+
+case $build_dir in
+/*) ;;
+*) build_dir=$PWD/$build_dir ;;
+esac
+
+mkdir -p "$build_dir"
+cd "$kernel_dir"
+
+echo "==> Configuring paravirtual kernel in $build_dir"
+if [[ ! -f $build_dir/.config ]]; then
+ config_args=()
+ for config_item in "${config_items[@]}"; do
+ config_args+=(--configitem "$config_item")
+ done
+ "$vng" --kconfig "${config_args[@]}" \
+ --configitem CONFIG_HYPERVISOR_GUEST=y \
+ --configitem CONFIG_PARAVIRT=y \
+ --configitem CONFIG_KVM_GUEST=y \
+ --configitem CONFIG_PARAVIRT_TIME_ACCOUNTING=y \
+ -- "O=$build_dir"
+else
+ # Preserve an existing local minimal config and add the options needed by
+ # this test. olddefconfig resolves their dependencies for the current tree.
+ config_args=(--file "$build_dir/.config")
+ for config_item in "${config_items[@]}"; do
+ config_name=${config_item%%=*}
+ config_value=${config_item#*=}
+ config_args+=(--set-val "$config_name" "$config_value")
+ done
+ "$kernel_dir/scripts/config" "${config_args[@]}" \
+ -e HYPERVISOR_GUEST \
+ -e PARAVIRT \
+ -e KVM_GUEST \
+ -e PARAVIRT_TIME_ACCOUNTING
+ make -s O="$build_dir" olddefconfig
+fi
+
+echo "==> Building kernel"
+declare -a module_args=()
+if [[ -z $driver ]]; then
+ module_args+=(--skip-modules)
+fi
+"$vng" --build "${module_args[@]}" -- "O=$build_dir"
+
+for option in HYPERVISOR_GUEST PARAVIRT KVM_GUEST PARAVIRT_TIME_ACCOUNTING; do
+ if ! grep -qx "CONFIG_${option}=y" "$build_dir/.config"; then
+ echo "error: CONFIG_${option}=y is required but is absent after configuration" >&2
+ exit 1
+ fi
+done
+
+tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/virtme-steal.XXXXXXXX")
+declare -a vm_pids=()
+
+cleanup()
+{
+ local pid
+
+ for pid in "${vm_pids[@]}"; do
+ kill "$pid" 2>/dev/null || true
+ done
+ wait 2>/dev/null || true
+ rm -rf -- "$tmpdir"
+}
+trap cleanup EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
+
+echo "==> Starting $vm_count VMs ($vcpu_count vCPUs each)"
+for ((vm = 0; vm < vm_count; vm++)); do
+ printf -v guest_script '%q %q %q' "$payload" "$vm" "$duration"
+ if [[ -n $driver ]]; then
+ printf -v driver_arg ' %q' "$driver"
+ guest_script+=$driver_arg
+ if [[ -n $low_threshold ]]; then
+ printf -v threshold_args ' %q %q' \
+ "$low_threshold" "$high_threshold"
+ guest_script+=$threshold_args
+ fi
+ fi
+
+ log=$tmpdir/vm-$vm.log
+ "$vng" \
+ "${module_args[@]}" \
+ --name "steal-vm-$vm" \
+ --cpus "$vcpu_count" \
+ --memory "$memory" \
+ --exec "$guest_script" \
+ -- "O=$build_dir" >"$log" 2>&1 &
+ vm_pids+=("$!")
+done
+
+status=0
+total_throughput=0
+if (( ! verbose )); then
+ printf '%-8s %12s %16s\n' VM THROUGHPUT 'AVG STEAL%'
+ printf '%-8s %12s %16s\n' -------- ------------ ----------------
+fi
+for ((vm = 0; vm < vm_count; vm++)); do
+ if ! wait "${vm_pids[vm]}"; then
+ echo "error: VM $vm failed; its complete log follows" >&2
+ status=1
+ echo "--- VM $vm log ---"
+ cat "$tmpdir/vm-$vm.log"
+ continue
+ fi
+ if ! grep -qx VIRTME_STEAL_REPORT_BEGIN "$tmpdir/vm-$vm.log" ||
+ ! grep -qx VIRTME_STEAL_REPORT_END "$tmpdir/vm-$vm.log"; then
+ echo "error: VM $vm exited without a steal-time report; its complete log follows" >&2
+ cat "$tmpdir/vm-$vm.log"
+ status=1
+ continue
+ fi
+
+ summary=$(awk '/^VIRTME_STEAL_SUMMARY / { print $2, $3, $4 }' \
+ "$tmpdir/vm-$vm.log")
+ if [[ -z $summary ]]; then
+ echo "error: VM $vm exited without a summary; its complete log follows" >&2
+ cat "$tmpdir/vm-$vm.log"
+ status=1
+ continue
+ fi
+
+ if (( verbose )); then
+ echo "--- VM $vm results ---"
+ awk '
+ /^VIRTME_STEAL_REPORT_BEGIN$/ { report = 1; next }
+ /^VIRTME_STEAL_REPORT_END$/ { report = 0 }
+ /^VIRTME_STEAL_SUMMARY / { next }
+ report
+ ' "$tmpdir/vm-$vm.log"
+ else
+ read -r summary_vm summary_throughput summary_steal <<<"$summary"
+ printf '%-8s %12s %16s\n' \
+ "$summary_vm" "$summary_throughput" "$summary_steal"
+ total_throughput=$((total_throughput + summary_throughput))
+ fi
+done
+
+if (( ! verbose )); then
+ printf '%-8s %12s %16s\n' -------- ------------ ----------------
+ printf '%-8s %12s %16s\n' TOTAL "$total_throughput" -
+fi
+
+exit "$status"