diff --git a/docs/benchmark.md b/docs/benchmark.md new file mode 100644 index 0000000..f62e94c --- /dev/null +++ b/docs/benchmark.md @@ -0,0 +1,105 @@ +# Benchmark Tool + +The benchmark script measures VM boot time by capturing a boot completion message via Unix socket. + +## Usage + +```bash +./scripts/benchmark.sh [OPTIONS] [KERNEL_PATH] [DRIVE_PATH] +``` + +### Options + +- `-q, --quiet`: Quiet mode - outputs only the boot time in seconds (useful for scripting) +- `-c, --cores NUM`: Number of CPU cores to allocate to the VM (default: 1) +- `-m, --mem SIZE`: Memory size in MB to allocate to the VM (default: 256) + +### Arguments + +- `KERNEL_PATH`: Path to the kernel file (default: `kernels/netbsd-SMOL`) +- `DRIVE_PATH`: Path to the disk image (default: `images/benchmark-amd64.img`) + +## Examples + +### Basic Usage (Verbose Mode) + +```bash +$ ./scripts/benchmark.sh +Kernel: kernels/netbsd-SMOL +Drive: images/benchmark-amd64.img +Starting VM at Thu Oct 30 09:45:13 PM CET 2025 +Socket ready: measure_boot.sock +VM PID: 35983 +Waiting for boot message... + +========================================= +Boot time: 0.143655123 seconds +========================================= + +Message received: +BOOT_COMPLETE +``` + +### Quiet Mode + +```bash +$ ./scripts/benchmark.sh -q +0.140307116 +``` + +### Custom Kernel and Drive + +```bash +# Custom kernel only (uses default drive) +$ ./scripts/benchmark.sh kernels/custom-kernel + +# Custom kernel and drive +$ ./scripts/benchmark.sh kernels/custom-kernel images/custom.img + +# Quiet mode with custom paths +$ ./scripts/benchmark.sh -q kernels/custom-kernel images/custom.img + +# With custom CPU and RAM +$ ./scripts/benchmark.sh -c 2 -m 512 + +# Combining options +$ ./scripts/benchmark.sh -q -c 4 -m 1024 kernels/custom-kernel +``` + +### Scripting Examples + +```bash +# Run multiple benchmarks and calculate average +total=0 +runs=10 +for i in $(seq 1 $runs); do + time=$(./scripts/benchmark.sh -q) + total=$(echo "$total + $time" | bc) + echo "Run $i: $time seconds" +done +avg=$(echo "scale=9; $total / $runs" | bc) +echo "Average: $avg seconds" + +# Compare two different configurations +echo "Configuration A:" +time_a=$(./scripts/benchmark.sh -q kernels/netbsd-SMOL images/config-a.img) +echo "Configuration B:" +time_b=$(./scripts/benchmark.sh -q kernels/netbsd-SMOL images/config-b.img) +diff=$(echo "$time_b - $time_a" | bc) +echo "Difference: $diff seconds" +``` + +## Requirements + +- `qemu` with KVM support +- `socat` for Unix socket communication +- `bc` for floating-point arithmetic + +## How It Works + +1. Creates a Unix socket for communication with the VM +2. Starts a `socat` server listening on the socket +3. Launches QEMU with the specified kernel and drive +4. The VM sends "BOOT_COMPLETE" message via virtio console to the socket +5. Measures the time between VM launch and message receipt +6. Cleans up processes and temporary files \ No newline at end of file diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh new file mode 100755 index 0000000..8861093 --- /dev/null +++ b/scripts/benchmark.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Define log function based on quiet flag +log() { + [ "$QUIET" != "true" ] && echo "$@" +} + +setup_benchmark() { + SOCKET="measure_boot.sock" + FIFO="/tmp/boot_fifo_$$" + + rm -f "./$SOCKET" "$FIFO" + mkfifo "$FIFO" + + # Start socat server FIRST - listening and ready before QEMU starts + timeout 10 socat UNIX-LISTEN:"./$SOCKET" - 2>/dev/null > "$FIFO" & + SOCAT_PID=$! + + log "Socket ready: $SOCKET" + + START_TIME=$(date +%s.%N) + log "Starting VM at $(date)" + + # Export variables for parent script + export BENCHMARK_SOCKET="$SOCKET" + export BENCHMARK_FIFO="$FIFO" + export BENCHMARK_START_TIME="$START_TIME" + export BENCHMARK_SOCAT_PID="$SOCAT_PID" +} + +finish_benchmark() { + # Read from FIFO (blocks until data arrives) + head -1 < "$BENCHMARK_FIFO" > /dev/null + + END_TIME=$(date +%s.%N) + BOOT_TIME=$(printf "%.9f" $(echo "$END_TIME - $BENCHMARK_START_TIME" | bc)) + + if [ "$QUIET" = "true" ]; then + echo "$BOOT_TIME" + else + log "" + log "=========================================" + log "Boot time: ${BOOT_TIME} seconds" + log "=========================================" + log "" + fi + + # Force kill QEMU and cleanup the socket + kill $BENCHMARK_SOCAT_PID $BENCHMARK_VM_PID 2>/dev/null || true + rm -f "./$BENCHMARK_SOCKET" "$BENCHMARK_FIFO" +} + +benchmark_extra_chardev() { + echo "-chardev socket,path=${BENCHMARK_SOCKET},server=off,id=measure0 \ +-device virtconsole,chardev=measure0,name=measure0" +} diff --git a/service/benchmark/etc/rc b/service/benchmark/etc/rc new file mode 100644 index 0000000..668b8cf --- /dev/null +++ b/service/benchmark/etc/rc @@ -0,0 +1,12 @@ +#!/bin/sh + +. /etc/include/basicrc + +cd /dev && /bin/sh /etc/MAKEDEV ttyVI01 + +# Send boot complete immediately +echo "BOOT_COMPLETE" > /dev/ttyVI01 + +ksh + +. /etc/include/shutdown diff --git a/service/benchmark/etc/sysctl.conf b/service/benchmark/etc/sysctl.conf new file mode 100755 index 0000000..9b03cdd --- /dev/null +++ b/service/benchmark/etc/sysctl.conf @@ -0,0 +1,2 @@ +# this takes ages +net.inet6.ip6.dad_count=0 diff --git a/startnb.sh b/startnb.sh index dce5911..46b2308 100755 --- a/startnb.sh +++ b/startnb.sh @@ -6,7 +6,7 @@ usage() Usage: ${0##*/} -f conffile | -k kernel -i image [-c CPUs] [-m memory] [-a kernel parameters] [-r root disk] [-h drive2] [-p port] [-t tcp serial port] [-w path] [-x qemu extra args] - [-b] [-n] [-s] [-d] [-v] [-u] + [-b] [-n] [-s] [-d] [-v] [-u] [-B] [-q] Boot a microvm -f conffile vm config file @@ -27,6 +27,8 @@ Usage: ${0##*/} -f conffile | -k kernel -i image [-c CPUs] [-m memory] -d daemonize -v verbose -u non-colorful output + -B benchmark mode (measure boot time) + -q quiet mode (with -B, output only boot time) -h this help _USAGE_ # as per https://www.qemu.org/docs/master/system/invocation.html @@ -40,7 +42,7 @@ if pgrep VirtualBoxVM >/dev/null 2>&1; then exit 1 fi -options="f:k:a:p:i:m:n:c:r:l:p:uw:x:t:hbdsv" +options="f:k:a:p:i:m:n:c:r:l:p:uw:x:t:hbdsvBq" export CHOUPI=y @@ -52,6 +54,7 @@ do case $opt in a) append="$OPTARG";; b) bridgenet=yes;; + B) BENCHMARK=yes;; c) cores="$OPTARG";; d) DAEMON=yes;; # first load vm config file @@ -69,6 +72,7 @@ do m) mem="$OPTARG";; n) max_ports=$(($OPTARG + 1));; p) hostfwd=$OPTARG;; + q) QUIET=true;; r) root="$OPTARG";; s) sharerw=yes;; t) serial_port=$OPTARG;; @@ -82,6 +86,13 @@ done . service/common/choupi +# Benchmark mode setup +if [ -n "$BENCHMARK" ]; then + . scripts/benchmark.sh + setup_benchmark + max_ports=2 +fi + # envvars override kernel=${kernel:-$KERNEL} img=${img:-$NBIMG} @@ -223,6 +234,8 @@ if [ -n "$max_ports" ]; then echo "host socket ${v}: ${sockpath}" done fi +# Benchmark mode: add measurement socket +[ -n "$BENCHMARK" ] && viosock="$viosock $(benchmark_extra_chardev)" # QMP is available [ -n "${qmp_port}" ] && extra="$extra -qmp tcp:localhost:${qmp_port},server,wait=off" @@ -238,4 +251,11 @@ cmd="${QEMU} -smp $cores \ [ -n "$VERBOSE" ] && echo "$cmd" && exit -eval $cmd +if [ -n "$BENCHMARK" ]; then + eval $cmd > /dev/null 2>&1 & + BENCHMARK_VM_PID=$! + export BENCHMARK_VM_PID + finish_benchmark +else + eval $cmd +fi