Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions docs/benchmark.md
Original file line number Diff line number Diff line change
@@ -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
56 changes: 56 additions & 0 deletions scripts/benchmark.sh
Original file line number Diff line number Diff line change
@@ -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"
}
12 changes: 12 additions & 0 deletions service/benchmark/etc/rc
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions service/benchmark/etc/sysctl.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# this takes ages
net.inet6.ip6.dad_count=0
26 changes: 23 additions & 3 deletions startnb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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;;
Expand All @@ -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}
Expand Down Expand Up @@ -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"

Expand All @@ -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