xref: /spdk/test/vhost/common.sh (revision 1e3d25b901a6b9d2dce4999e2ecbc02f98d79f05)
1#  SPDX-License-Identifier: BSD-3-Clause
2#  Copyright (C) 2017 Intel Corporation
3#  All rights reserved.
4#
5
6: ${SPDK_VHOST_VERBOSE=false}
7: ${VHOST_DIR="$HOME/vhost_test"}
8: ${QEMU_BIN:="qemu-system-x86_64"}
9: ${QEMU_IMG_BIN="qemu-img"}
10
11TEST_DIR=$(readlink -f $rootdir/..)
12VM_DIR=$VHOST_DIR/vms
13TARGET_DIR=$VHOST_DIR/vhost
14VM_PASSWORD="root"
15
16VM_IMAGE=${VM_IMAGE:-"$DEPENDENCY_DIR/vhost/spdk_test_image.qcow2"}
17FIO_BIN=${FIO_BIN:-}
18
19WORKDIR=$(readlink -f "$(dirname "$0")")
20
21if ! hash $QEMU_IMG_BIN $QEMU_BIN; then
22	echo 'ERROR: QEMU is not installed on this system. Unable to run vhost tests.' >&2
23	return 1
24fi
25
26mkdir -p $VHOST_DIR
27mkdir -p $VM_DIR
28mkdir -p $TARGET_DIR
29
30#
31# Source config describing QEMU and VHOST cores and NUMA
32#
33source $rootdir/test/vhost/common/autotest.config
34source "$rootdir/test/scheduler/common.sh"
35
36function vhosttestinit() {
37	if [ "$TEST_MODE" == "iso" ]; then
38		$rootdir/scripts/setup.sh
39	fi
40
41	if [[ -e $VM_IMAGE.gz && ! -e $VM_IMAGE ]]; then
42		gzip -dc "$VM_IMAGE.gz" > "$VM_IMAGE"
43	fi
44
45	# Look for the VM image
46	if [[ ! -f $VM_IMAGE ]]; then
47		[[ $1 != "--no_vm" ]] || return 0
48		echo "$VM_IMAGE is missing" >&2
49		return 1
50	fi
51}
52
53function vhosttestfini() {
54	if [ "$TEST_MODE" == "iso" ]; then
55		$rootdir/scripts/setup.sh reset
56	fi
57}
58
59function message() {
60	local verbose_out
61	if ! $SPDK_VHOST_VERBOSE; then
62		verbose_out=""
63	elif [[ ${FUNCNAME[2]} == "source" ]]; then
64		verbose_out=" (file $(basename ${BASH_SOURCE[1]}):${BASH_LINENO[1]})"
65	else
66		verbose_out=" (function ${FUNCNAME[2]}:${BASH_LINENO[1]})"
67	fi
68
69	local msg_type="$1"
70	shift
71	echo -e "${msg_type}${verbose_out}: $*"
72}
73
74function fail() {
75	echo "===========" >&2
76	message "FAIL" "$@" >&2
77	echo "===========" >&2
78	exit 1
79}
80
81function error() {
82	echo "===========" >&2
83	message "ERROR" "$@" >&2
84	echo "===========" >&2
85	# Don't 'return 1' since the stack trace will be incomplete (why?) missing upper command.
86	false
87}
88
89function warning() {
90	message "WARN" "$@" >&2
91}
92
93function notice() {
94	message "INFO" "$@"
95}
96
97function check_qemu_packedring_support() {
98	qemu_version=$($QEMU_BIN -version | grep -Po "(?<=version )\d+.\d+.\d+")
99	if [[ "$qemu_version" < "4.2.0" ]]; then
100		error "This qemu binary does not support packed ring"
101	fi
102}
103
104function get_vhost_dir() {
105	local vhost_name="$1"
106
107	if [[ -z "$vhost_name" ]]; then
108		error "vhost name must be provided to get_vhost_dir"
109		return 1
110	fi
111
112	echo "$TARGET_DIR/${vhost_name}"
113}
114
115function vhost_run() {
116	local OPTIND
117	local vhost_name
118	local run_gen_nvme=true
119	local vhost_bin="vhost"
120	local vhost_args=()
121	local cmd=()
122
123	while getopts "n:b:g" optchar; do
124		case "$optchar" in
125			n) vhost_name="$OPTARG" ;;
126			b) vhost_bin="$OPTARG" ;;
127			g)
128				run_gen_nvme=false
129				notice "Skipping gen_nvme.sh NVMe bdev configuration"
130				;;
131			*)
132				error "Unknown param $optchar"
133				return 1
134				;;
135		esac
136	done
137	shift $((OPTIND - 1))
138
139	vhost_args=("$@")
140
141	if [[ -z "$vhost_name" ]]; then
142		error "vhost name must be provided to vhost_run"
143		return 1
144	fi
145
146	local vhost_dir
147	vhost_dir="$(get_vhost_dir $vhost_name)"
148	local vhost_app="$SPDK_BIN_DIR/$vhost_bin"
149	local vhost_log_file="$vhost_dir/vhost.log"
150	local vhost_pid_file="$vhost_dir/vhost.pid"
151	local vhost_socket="$vhost_dir/usvhost"
152	notice "starting vhost app in background"
153	[[ -r "$vhost_pid_file" ]] && vhost_kill $vhost_name
154	[[ -d $vhost_dir ]] && rm -f $vhost_dir/*
155	mkdir -p $vhost_dir
156
157	if [[ ! -x $vhost_app ]]; then
158		error "application not found: $vhost_app"
159		return 1
160	fi
161
162	cmd=("$vhost_app" "-r" "$vhost_dir/rpc.sock" "${vhost_args[@]}")
163	if [[ "$vhost_bin" =~ vhost ]]; then
164		cmd+=(-S "$vhost_dir")
165	fi
166
167	notice "Loging to:   $vhost_log_file"
168	notice "Socket:      $vhost_socket"
169	notice "Command:     $cmd"
170
171	timing_enter vhost_start
172
173	iobuf_small_count=${iobuf_small_count:-16383}
174	iobuf_large_count=${iobuf_large_count:-2047}
175
176	"${cmd[@]}" --wait-for-rpc &
177	vhost_pid=$!
178	echo $vhost_pid > $vhost_pid_file
179
180	notice "waiting for app to run..."
181	waitforlisten "$vhost_pid" "$vhost_dir/rpc.sock"
182
183	"$rootdir/scripts/rpc.py" -s "$vhost_dir/rpc.sock" \
184		iobuf_set_options \
185		--small-pool-count="$iobuf_small_count" \
186		--large-pool-count="$iobuf_large_count"
187
188	"$rootdir/scripts/rpc.py" -s "$vhost_dir/rpc.sock" \
189		framework_start_init
190
191	#do not generate nvmes if pci access is disabled
192	if [[ "$cmd" != *"--no-pci"* ]] && [[ "$cmd" != *"-u"* ]] && $run_gen_nvme; then
193		$rootdir/scripts/gen_nvme.sh | $rootdir/scripts/rpc.py -s $vhost_dir/rpc.sock load_subsystem_config
194	fi
195
196	notice "vhost started - pid=$vhost_pid"
197
198	timing_exit vhost_start
199}
200
201function vhost_kill() {
202	local rc=0
203	local vhost_name="$1"
204
205	if [[ -z "$vhost_name" ]]; then
206		error "Must provide vhost name to vhost_kill"
207		return 0
208	fi
209
210	local vhost_dir
211	vhost_dir="$(get_vhost_dir $vhost_name)"
212	local vhost_pid_file="$vhost_dir/vhost.pid"
213
214	if [[ ! -r $vhost_pid_file ]]; then
215		warning "no vhost pid file found"
216		return 0
217	fi
218
219	timing_enter vhost_kill
220	local vhost_pid
221	vhost_pid="$(cat $vhost_pid_file)"
222	notice "killing vhost (PID $vhost_pid) app"
223
224	if kill -INT $vhost_pid > /dev/null; then
225		notice "sent SIGINT to vhost app - waiting 60 seconds to exit"
226		for ((i = 0; i < 60; i++)); do
227			if kill -0 $vhost_pid; then
228				echo "."
229				sleep 1
230			else
231				break
232			fi
233		done
234		if kill -0 $vhost_pid; then
235			error "ERROR: vhost was NOT killed - sending SIGABRT"
236			kill -ABRT $vhost_pid
237			rc=1
238		else
239			while kill -0 $vhost_pid; do
240				echo "."
241			done
242		fi
243	elif kill -0 $vhost_pid; then
244		error "vhost NOT killed - you need to kill it manually"
245		rc=1
246	else
247		notice "vhost was not running"
248	fi
249
250	timing_exit vhost_kill
251
252	rm -rf "$vhost_dir"
253
254	return $rc
255}
256
257function vhost_rpc() {
258	local vhost_name="$1"
259
260	if [[ -z "$vhost_name" ]]; then
261		error "vhost name must be provided to vhost_rpc"
262		return 1
263	fi
264	shift
265
266	$rootdir/scripts/rpc.py -s $(get_vhost_dir $vhost_name)/rpc.sock "$@"
267}
268
269###
270# Mgmt functions
271###
272
273function assert_number() {
274	[[ "$1" =~ [0-9]+ ]] && return 0
275
276	error "Invalid or missing parameter: need number but got '$1'"
277	return 1
278}
279
280# Run command on vm with given password
281# First argument - vm number
282# Second argument - ssh password for vm
283#
284function vm_sshpass() {
285	vm_num_is_valid $1 || return 1
286
287	local ssh_cmd
288	ssh_cmd="sshpass -p $2 ssh \
289		-o UserKnownHostsFile=/dev/null \
290		-o StrictHostKeyChecking=no \
291		-o User=root \
292		-p $(vm_ssh_socket $1) $VM_SSH_OPTIONS 127.0.0.1"
293
294	shift 2
295	$ssh_cmd "$@"
296}
297
298# Helper to validate VM number
299# param $1 VM number
300#
301function vm_num_is_valid() {
302	[[ "$1" =~ ^[0-9]+$ ]] && return 0
303
304	error "Invalid or missing parameter: vm number '$1'"
305	return 1
306}
307
308# Print network socket for given VM number
309# param $1 virtual machine number
310#
311function vm_ssh_socket() {
312	vm_num_is_valid $1 || return 1
313	local vm_dir="$VM_DIR/$1"
314
315	cat $vm_dir/ssh_socket
316}
317
318function vm_fio_socket() {
319	vm_num_is_valid $1 || return 1
320	local vm_dir="$VM_DIR/$1"
321
322	cat $vm_dir/fio_socket
323}
324
325# Execute command on given VM
326# param $1 virtual machine number
327#
328function vm_exec() {
329	vm_num_is_valid $1 || return 1
330
331	local vm_num="$1"
332	shift
333
334	sshpass -p "$VM_PASSWORD" ssh \
335		-o UserKnownHostsFile=/dev/null \
336		-o StrictHostKeyChecking=no \
337		-o User=root \
338		-p $(vm_ssh_socket $vm_num) $VM_SSH_OPTIONS 127.0.0.1 \
339		"$@"
340}
341
342# Execute scp command on given VM
343# param $1 virtual machine number
344#
345function vm_scp() {
346	vm_num_is_valid $1 || return 1
347
348	local vm_num="$1"
349	shift
350
351	sshpass -p "$VM_PASSWORD" scp \
352		-o UserKnownHostsFile=/dev/null \
353		-o StrictHostKeyChecking=no \
354		-o User=root \
355		-P $(vm_ssh_socket $vm_num) $VM_SSH_OPTIONS \
356		"$@"
357}
358
359# check if specified VM is running
360# param $1 VM num
361function vm_is_running() {
362	vm_num_is_valid $1 || return 1
363	local vm_dir="$VM_DIR/$1"
364
365	if [[ ! -r $vm_dir/qemu.pid ]]; then
366		return 1
367	fi
368
369	local vm_pid
370	vm_pid="$(cat $vm_dir/qemu.pid)"
371
372	if /bin/kill -0 $vm_pid; then
373		return 0
374	else
375		if [[ $EUID -ne 0 ]]; then
376			warning "not root - assuming VM running since can't be checked"
377			return 0
378		fi
379
380		# not running - remove pid file
381		rm -f $vm_dir/qemu.pid
382		return 1
383	fi
384}
385
386# check if specified VM is running
387# param $1 VM num
388function vm_os_booted() {
389	vm_num_is_valid $1 || return 1
390	local vm_dir="$VM_DIR/$1"
391
392	if [[ ! -r $vm_dir/qemu.pid ]]; then
393		error "VM $1 is not running"
394		return 1
395	fi
396
397	if ! VM_SSH_OPTIONS="-o ControlMaster=no" vm_exec $1 "true" 2> /dev/null; then
398		# Shutdown existing master. Ignore errors as it might not exist.
399		VM_SSH_OPTIONS="-O exit" vm_exec $1 "true" 2> /dev/null
400		return 1
401	fi
402
403	return 0
404}
405
406# Shutdown given VM
407# param $1 virtual machine number
408# return non-zero in case of error.
409function vm_shutdown() {
410	vm_num_is_valid $1 || return 1
411	local vm_dir="$VM_DIR/$1"
412	if [[ ! -d "$vm_dir" ]]; then
413		error "VM$1 ($vm_dir) not exist - setup it first"
414		return 1
415	fi
416
417	if ! vm_is_running $1; then
418		notice "VM$1 ($vm_dir) is not running"
419		return 0
420	fi
421
422	# Temporarily disabling exit flag for next ssh command, since it will
423	# "fail" due to shutdown
424	notice "Shutting down virtual machine $vm_dir"
425	set +e
426	vm_exec $1 "nohup sh -c 'shutdown -h -P now'" || true
427	notice "VM$1 is shutting down - wait a while to complete"
428	set -e
429}
430
431# Kill given VM
432# param $1 virtual machine number
433#
434function vm_kill() {
435	vm_num_is_valid $1 || return 1
436	local vm_dir="$VM_DIR/$1"
437
438	if [[ ! -r $vm_dir/qemu.pid ]]; then
439		return 0
440	fi
441
442	local vm_pid
443	vm_pid="$(cat $vm_dir/qemu.pid)"
444
445	notice "Killing virtual machine $vm_dir (pid=$vm_pid)"
446	# First kill should fail, second one must fail
447	if /bin/kill $vm_pid; then
448		notice "process $vm_pid killed"
449		rm -rf $vm_dir
450	elif vm_is_running $1; then
451		error "Process $vm_pid NOT killed"
452		return 1
453	fi
454}
455
456# List all VM numbers in VM_DIR
457#
458function vm_list_all() {
459	local vms=()
460	vms=("$VM_DIR"/+([0-9]))
461	if ((${#vms[@]} > 0)); then
462		basename --multiple "${vms[@]}"
463	fi
464}
465
466# Kills all VM in $VM_DIR
467#
468function vm_kill_all() {
469	local vm
470	for vm in $(vm_list_all); do
471		vm_kill $vm
472	done
473
474	rm -rf $VM_DIR
475}
476
477# Shutdown all VM in $VM_DIR
478#
479function vm_shutdown_all() {
480	# XXX: temporarily disable to debug shutdown issue
481	# xtrace_disable
482
483	local vms
484	vms=$(vm_list_all)
485	local vm
486
487	for vm in $vms; do
488		vm_shutdown $vm
489	done
490
491	notice "Waiting for VMs to shutdown..."
492	local timeo=90
493	while [[ $timeo -gt 0 ]]; do
494		local all_vms_down=1
495		for vm in $vms; do
496			if vm_is_running $vm; then
497				all_vms_down=0
498				break
499			fi
500		done
501
502		if [[ $all_vms_down == 1 ]]; then
503			notice "All VMs successfully shut down"
504			xtrace_restore
505			return 0
506		fi
507
508		((timeo -= 1))
509		sleep 1
510	done
511
512	rm -rf $VM_DIR
513
514	xtrace_restore
515}
516
517function vm_setup() {
518	xtrace_disable
519	local OPTIND optchar vm_num
520
521	local os=""
522	local os_mode=""
523	local qemu_args=()
524	local disk_type_g=NOT_DEFINED
525	local read_only="false"
526	# List created of a strings separated with a ":"
527	local disks=()
528	local raw_cache=""
529	local vm_incoming=""
530	local vm_migrate_to=""
531	local force_vm=""
532	local guest_memory=1024
533	local queue_number=""
534	local vhost_dir
535	local packed=false
536	vhost_dir="$(get_vhost_dir 0)"
537	while getopts ':-:' optchar; do
538		case "$optchar" in
539			-)
540				case "$OPTARG" in
541					os=*) os="${OPTARG#*=}" ;;
542					os-mode=*) os_mode="${OPTARG#*=}" ;;
543					qemu-args=*) qemu_args+=("${OPTARG#*=}") ;;
544					disk-type=*) disk_type_g="${OPTARG#*=}" ;;
545					read-only=*) read_only="${OPTARG#*=}" ;;
546					disks=*) IFS=":" read -ra disks <<< "${OPTARG#*=}" ;;
547					raw-cache=*) raw_cache=",cache${OPTARG#*=}" ;;
548					force=*) force_vm=${OPTARG#*=} ;;
549					memory=*) guest_memory=${OPTARG#*=} ;;
550					queue_num=*) queue_number=${OPTARG#*=} ;;
551					incoming=*) vm_incoming="${OPTARG#*=}" ;;
552					migrate-to=*) vm_migrate_to="${OPTARG#*=}" ;;
553					vhost-name=*) vhost_dir="$(get_vhost_dir ${OPTARG#*=})" ;;
554					spdk-boot=*) local boot_from="${OPTARG#*=}" ;;
555					packed) packed=true ;;
556					*)
557						error "unknown argument $OPTARG"
558						return 1
559						;;
560				esac
561				;;
562			*)
563				error "vm_create Unknown param $OPTARG"
564				return 1
565				;;
566		esac
567	done
568
569	# Find next directory we can use
570	if [[ -n $force_vm ]]; then
571		vm_num=$force_vm
572
573		vm_num_is_valid $vm_num || return 1
574		local vm_dir="$VM_DIR/$vm_num"
575		[[ -d $vm_dir ]] && warning "removing existing VM in '$vm_dir'"
576	else
577		local vm_dir=""
578
579		set +x
580		for ((i = 0; i <= 256; i++)); do
581			local vm_dir="$VM_DIR/$i"
582			[[ ! -d $vm_dir ]] && break
583		done
584		xtrace_restore
585
586		vm_num=$i
587	fi
588
589	if [[ $vm_num -eq 256 ]]; then
590		error "no free VM found. do some cleanup (256 VMs created, are you insane?)"
591		return 1
592	fi
593
594	if [[ -n "$vm_migrate_to" && -n "$vm_incoming" ]]; then
595		error "'--incoming' and '--migrate-to' cannot be used together"
596		return 1
597	elif [[ -n "$vm_incoming" ]]; then
598		if [[ -n "$os_mode" || -n "$os" ]]; then
599			error "'--incoming' can't be used together with '--os' nor '--os-mode'"
600			return 1
601		fi
602
603		os_mode="original"
604		os="$VM_DIR/$vm_incoming/os.qcow2"
605	elif [[ -n "$vm_migrate_to" ]]; then
606		[[ "$os_mode" != "backing" ]] && warning "Using 'backing' mode for OS since '--migrate-to' is used"
607		os_mode=backing
608	fi
609
610	notice "Creating new VM in $vm_dir"
611	mkdir -p $vm_dir
612
613	if [[ "$os_mode" == "backing" ]]; then
614		notice "Creating backing file for OS image file: $os"
615		if ! $QEMU_IMG_BIN create -f qcow2 -b $os $vm_dir/os.qcow2 -F qcow2; then
616			error "Failed to create OS backing file in '$vm_dir/os.qcow2' using '$os'"
617			return 1
618		fi
619
620		local os=$vm_dir/os.qcow2
621	elif [[ "$os_mode" == "original" ]]; then
622		warning "Using original OS image file: $os"
623	elif [[ "$os_mode" != "snapshot" ]]; then
624		if [[ -z "$os_mode" ]]; then
625			notice "No '--os-mode' parameter provided - using 'snapshot'"
626			os_mode="snapshot"
627		else
628			error "Invalid '--os-mode=$os_mode'"
629			return 1
630		fi
631	fi
632
633	local qemu_mask_param="VM_${vm_num}_qemu_mask"
634	local qemu_numa_node_param="VM_${vm_num}_qemu_numa_node"
635
636	if [[ -z "${!qemu_mask_param}" ]] || [[ -z "${!qemu_numa_node_param}" ]]; then
637		error "Parameters ${qemu_mask_param} or ${qemu_numa_node_param} not found in autotest.config file"
638		return 1
639	fi
640
641	local task_mask=${!qemu_mask_param}
642
643	notice "TASK MASK: $task_mask"
644	local cmd=(taskset -a -c "$task_mask" "$QEMU_BIN")
645	local vm_socket_offset=$((10000 + 100 * vm_num))
646
647	local ssh_socket=$((vm_socket_offset + 0))
648	local fio_socket=$((vm_socket_offset + 1))
649	local monitor_port=$((vm_socket_offset + 2))
650	local migration_port=$((vm_socket_offset + 3))
651	local gdbserver_socket=$((vm_socket_offset + 4))
652	local vnc_socket=$((100 + vm_num))
653	local qemu_pid_file="$vm_dir/qemu.pid"
654	local cpu_num=0
655
656	set +x
657	# cpu list for taskset can be comma separated or range
658	# or both at the same time, so first split on commas
659	cpu_list=$(echo $task_mask | tr "," "\n")
660	queue_number=0
661	for c in $cpu_list; do
662		# if range is detected - count how many cpus
663		if [[ $c =~ [0-9]+-[0-9]+ ]]; then
664			val=$((c - 1))
665			val=${val#-}
666		else
667			val=1
668		fi
669		cpu_num=$((cpu_num + val))
670		queue_number=$((queue_number + val))
671	done
672
673	if [ -z $queue_number ]; then
674		queue_number=$cpu_num
675	fi
676
677	# Normalize tcp ports to make sure they are available
678	ssh_socket=$(get_free_tcp_port "$ssh_socket")
679	fio_socket=$(get_free_tcp_port "$fio_socket")
680	monitor_port=$(get_free_tcp_port "$monitor_port")
681	migration_port=$(get_free_tcp_port "$migration_port")
682	gdbserver_socket=$(get_free_tcp_port "$gdbserver_socket")
683	vnc_socket=$(get_free_tcp_port "$vnc_socket")
684
685	xtrace_restore
686
687	local node_num=${!qemu_numa_node_param}
688	local boot_disk_present=false
689	notice "NUMA NODE: $node_num"
690	cmd+=(-m "$guest_memory" --enable-kvm -cpu host -smp "$cpu_num" -vga std -vnc ":$vnc_socket" -daemonize)
691	cmd+=(-object "memory-backend-file,id=mem,size=${guest_memory}M,mem-path=/dev/hugepages,share=on,prealloc=yes,host-nodes=$node_num,policy=bind")
692	[[ $os_mode == snapshot ]] && cmd+=(-snapshot)
693	[[ -n "$vm_incoming" ]] && cmd+=(-incoming "tcp:0:$migration_port")
694	cmd+=(-monitor "telnet:127.0.0.1:$monitor_port,server,nowait")
695	cmd+=(-numa "node,memdev=mem")
696	cmd+=(-pidfile "$qemu_pid_file")
697	cmd+=(-serial "file:$vm_dir/serial.log")
698	cmd+=(-D "$vm_dir/qemu.log")
699	cmd+=(-chardev "file,path=$vm_dir/seabios.log,id=seabios" -device "isa-debugcon,iobase=0x402,chardev=seabios")
700	cmd+=(-net "user,hostfwd=tcp::$ssh_socket-:22,hostfwd=tcp::$fio_socket-:8765")
701	cmd+=(-net nic)
702	if [[ -z "$boot_from" ]]; then
703		cmd+=(-drive "file=$os,if=none,id=os_disk")
704		cmd+=(-device "ide-hd,drive=os_disk,bootindex=0")
705	fi
706
707	if ((${#disks[@]} == 0)) && [[ $disk_type_g == virtio* ]]; then
708		disks=("default_virtio.img")
709	elif ((${#disks[@]} == 0)); then
710		error "No disks defined, aborting"
711		return 1
712	fi
713
714	for disk in "${disks[@]}"; do
715		# Each disk can define its type in a form of a disk_name,type. The remaining parts
716		# of the string are dropped.
717		IFS="," read -r disk disk_type _ <<< "$disk"
718		[[ -z $disk_type ]] && disk_type=$disk_type_g
719
720		case $disk_type in
721			virtio)
722				local raw_name="RAWSCSI"
723				local raw_disk=$vm_dir/test.img
724
725				# Create disk file if it not exist or it is smaller than 1G
726				if [[ -f $disk && $(stat --printf="%s" $disk) -ge $((1024 * 1024 * 1024)) ]]; then
727					raw_disk=$disk
728					notice "Using existing image $raw_disk"
729				else
730					notice "Creating Virtio disc $raw_disk"
731					dd if=/dev/zero of=$raw_disk bs=1024k count=1024
732				fi
733
734				cmd+=(-device "virtio-scsi-pci,num_queues=$queue_number")
735				cmd+=(-device "scsi-hd,drive=hd$i,vendor=$raw_name")
736				cmd+=(-drive "if=none,id=hd$i,file=$raw_disk,format=raw$raw_cache")
737				;;
738			spdk_vhost_scsi)
739				notice "using socket $vhost_dir/naa.$disk.$vm_num"
740				cmd+=(-chardev "socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num")
741				cmd+=(-device "vhost-user-scsi-pci,id=scsi_$disk,num_queues=$queue_number,chardev=char_$disk")
742				if [[ "$disk" == "$boot_from" ]]; then
743					cmd[-1]+=,bootindex=0
744					boot_disk_present=true
745				fi
746				;;
747			spdk_vhost_blk)
748				notice "using socket $vhost_dir/naa.$disk.$vm_num"
749				cmd+=(-chardev "socket,id=char_$disk,path=$vhost_dir/naa.$disk.$vm_num")
750				cmd+=(-device "vhost-user-blk-pci,num-queues=$queue_number,chardev=char_$disk")
751				if [[ "$disk" == "$boot_from" ]]; then
752					cmd[-1]+=,bootindex=0
753					boot_disk_present=true
754				fi
755
756				if $packed; then
757					check_qemu_packedring_support
758					notice "Enabling packed ring support for VM $vm_num, controller $vhost_dir/naa.$disk.$vm_num"
759					cmd[-1]+=,packed=on
760				fi
761				;;
762			kernel_vhost)
763				if [[ -z $disk ]]; then
764					error "need WWN for $disk_type"
765					return 1
766				elif [[ ! $disk =~ ^[[:alpha:]]{3}[.][[:xdigit:]]+$ ]]; then
767					error "$disk_type - disk(wnn)=$disk does not look like WNN number"
768					return 1
769				fi
770				notice "Using kernel vhost disk wwn=$disk"
771				cmd+=(-device "vhost-scsi-pci,wwpn=$disk,num_queues=$queue_number")
772				;;
773			vfio_user)
774				notice "using socket $VM_DIR/$vm_num/domain/muser$disk/$disk/cntrl"
775				cmd+=(-device "vfio-user-pci,x-msg-timeout=5000,socket=$VM_DIR/$vm_num/muser/domain/muser$disk/$disk/cntrl")
776				if [[ "$disk" == "$boot_from" ]]; then
777					cmd[-1]+=",bootindex=0"
778					boot_disk_present=true
779				fi
780				;;
781			vfio_user_virtio)
782				notice "using socket $VM_DIR/vfu_tgt/virtio.$disk"
783				cmd+=(-device "vfio-user-pci,x-msg-timeout=5000,socket=$VM_DIR/vfu_tgt/virtio.$disk")
784				if [[ "$disk" == "$boot_from" ]]; then
785					cmd[-1]+=",bootindex=0"
786					boot_disk_present=true
787				fi
788				;;
789			*)
790				error "unknown mode '$disk_type', use: virtio, spdk_vhost_scsi, spdk_vhost_blk, kernel_vhost, vfio_user or vfio_user_virtio"
791				return 1
792				;;
793		esac
794	done
795
796	if [[ -n $boot_from ]] && [[ $boot_disk_present == false ]]; then
797		error "Boot from $boot_from is selected but device is not present"
798		return 1
799	fi
800
801	((${#qemu_args[@]})) && cmd+=("${qemu_args[@]}")
802	notice "Saving to $vm_dir/run.sh"
803	cat <<- RUN > "$vm_dir/run.sh"
804		#!/bin/bash
805		rootdir=$rootdir
806		source "\$rootdir/test/scheduler/common.sh"
807		qemu_log () {
808			echo "=== qemu.log ==="
809			[[ -s $vm_dir/qemu.log ]] && cat $vm_dir/qemu.log
810			echo "=== qemu.log ==="
811		}
812
813		if [[ \$EUID -ne 0 ]]; then
814			echo "Go away user come back as root"
815			exit 1
816		fi
817
818		trap "qemu_log" EXIT
819
820		qemu_cmd=($(printf '%s\n' "${cmd[@]}"))
821		chmod +r $vm_dir/*
822		echo "Running VM in $vm_dir"
823		rm -f $qemu_pid_file
824		cgroup=\$(get_cgroup \$$)
825		set_cgroup_attr_top_bottom \$$ cgroup.subtree_control "+cpuset"
826		create_cgroup \$cgroup/qemu.$vm_num
827		set_cgroup_attr "\$cgroup/qemu.$vm_num" cpuset.mems "$node_num"
828		set_cgroup_attr "\$cgroup/qemu.$vm_num" cpuset.cpus "$task_mask"
829		"\${qemu_cmd[@]}"
830
831		echo "Waiting for QEMU pid file"
832		sleep 1
833		[[ ! -f $qemu_pid_file ]] && sleep 1
834		[[ ! -f $qemu_pid_file ]] && echo "ERROR: no qemu pid file found" && exit 1
835		set_cgroup_attr "\$cgroup/qemu.$vm_num" cgroup.threads \$(< "$qemu_pid_file")
836		exit 0
837		# EOF
838	RUN
839	chmod +x $vm_dir/run.sh
840
841	# Save generated sockets redirection
842	echo $ssh_socket > $vm_dir/ssh_socket
843	echo $fio_socket > $vm_dir/fio_socket
844	echo $monitor_port > $vm_dir/monitor_port
845
846	rm -f $vm_dir/migration_port
847	[[ -z $vm_incoming ]] || echo $migration_port > $vm_dir/migration_port
848
849	echo $gdbserver_socket > $vm_dir/gdbserver_socket
850	echo $vnc_socket >> $vm_dir/vnc_socket
851
852	[[ -z $vm_incoming ]] || ln -fs $VM_DIR/$vm_incoming $vm_dir/vm_incoming
853	[[ -z $vm_migrate_to ]] || ln -fs $VM_DIR/$vm_migrate_to $vm_dir/vm_migrate_to
854}
855
856function vm_run() {
857	local OPTIND optchar vm
858	local run_all=false
859	local vms_to_run=""
860
861	while getopts 'a-:' optchar; do
862		case "$optchar" in
863			a) run_all=true ;;
864			*)
865				error "Unknown param $OPTARG"
866				return 1
867				;;
868		esac
869	done
870
871	if $run_all; then
872		vms_to_run="$(vm_list_all)"
873	else
874		shift $((OPTIND - 1))
875		for vm in "$@"; do
876			vm_num_is_valid $1 || return 1
877			if [[ ! -x $VM_DIR/$vm/run.sh ]]; then
878				error "VM$vm not defined - setup it first"
879				return 1
880			fi
881			vms_to_run+=" $vm"
882		done
883	fi
884
885	for vm in $vms_to_run; do
886		if vm_is_running $vm; then
887			warning "VM$vm ($VM_DIR/$vm) already running"
888			continue
889		fi
890
891		notice "running $VM_DIR/$vm/run.sh"
892		if ! $VM_DIR/$vm/run.sh; then
893			error "FAILED to run vm $vm"
894			return 1
895		fi
896	done
897}
898
899function vm_print_logs() {
900	vm_num=$1
901	warning "================"
902	warning "QEMU LOG:"
903	if [[ -r $VM_DIR/$vm_num/qemu.log ]]; then
904		cat $VM_DIR/$vm_num/qemu.log
905	else
906		warning "LOG qemu.log not found"
907	fi
908
909	warning "VM LOG:"
910	if [[ -r $VM_DIR/$vm_num/serial.log ]]; then
911		cat $VM_DIR/$vm_num/serial.log
912	else
913		warning "LOG serial.log not found"
914	fi
915
916	warning "SEABIOS LOG:"
917	if [[ -r $VM_DIR/$vm_num/seabios.log ]]; then
918		cat $VM_DIR/$vm_num/seabios.log
919	else
920		warning "LOG seabios.log not found"
921	fi
922	warning "================"
923}
924
925# Wait for all created VMs to boot.
926# param $1 max wait time
927function vm_wait_for_boot() {
928	assert_number $1
929
930	xtrace_disable
931
932	local all_booted=false
933	local timeout_time=$1
934	[[ $timeout_time -lt 10 ]] && timeout_time=10
935	local timeout_time
936	timeout_time=$(date -d "+$timeout_time seconds" +%s)
937
938	notice "Waiting for VMs to boot"
939	shift
940	if [[ "$*" == "" ]]; then
941		local vms_to_check="$VM_DIR/[0-9]*"
942	else
943		local vms_to_check=""
944		for vm in "$@"; do
945			vms_to_check+=" $VM_DIR/$vm"
946		done
947	fi
948
949	for vm in $vms_to_check; do
950		local vm_num
951		vm_num=$(basename $vm)
952		local i=0
953		notice "waiting for VM$vm_num ($vm)"
954		while ! vm_os_booted $vm_num; do
955			if ! vm_is_running $vm_num; then
956				warning "VM $vm_num is not running"
957				vm_print_logs $vm_num
958				xtrace_restore
959				return 1
960			fi
961
962			if [[ $(date +%s) -gt $timeout_time ]]; then
963				warning "timeout waiting for machines to boot"
964				vm_print_logs $vm_num
965				xtrace_restore
966				return 1
967			fi
968			if ((i > 30)); then
969				local i=0
970				echo
971			fi
972			echo -n "."
973			sleep 1
974		done
975		echo ""
976		notice "VM$vm_num ready"
977		#Change Timeout for stopping services to prevent lengthy powerdowns
978		#Check that remote system is not Cygwin in case of Windows VMs
979		local vm_os
980		vm_os=$(vm_exec $vm_num "uname -o")
981		if [[ "$vm_os" != "Cygwin" ]]; then
982			vm_exec $vm_num "echo 'DefaultTimeoutStopSec=10' >> /etc/systemd/system.conf; systemctl daemon-reexec"
983		fi
984	done
985
986	notice "all VMs ready"
987	xtrace_restore
988	return 0
989}
990
991function vm_start_fio_server() {
992	local OPTIND optchar
993	local readonly=''
994	local fio_bin=''
995	while getopts ':-:' optchar; do
996		case "$optchar" in
997			-)
998				case "$OPTARG" in
999					fio-bin=*) local fio_bin="${OPTARG#*=}" ;;
1000					readonly) local readonly="--readonly" ;;
1001					*) error "Invalid argument '$OPTARG'" && return 1 ;;
1002				esac
1003				;;
1004			*) error "Invalid argument '$OPTARG'" && return 1 ;;
1005		esac
1006	done
1007
1008	shift $((OPTIND - 1))
1009	for vm_num in "$@"; do
1010		notice "Starting fio server on VM$vm_num"
1011		if [[ $fio_bin != "" ]]; then
1012			vm_exec $vm_num 'cat > /root/fio; chmod +x /root/fio' < $fio_bin
1013			vm_exec $vm_num /root/fio $readonly --eta=never --server --daemonize=/root/fio.pid
1014		else
1015			vm_exec $vm_num fio $readonly --eta=never --server --daemonize=/root/fio.pid
1016		fi
1017	done
1018}
1019
1020function vm_check_scsi_location() {
1021	# Script to find wanted disc
1022	local script='shopt -s nullglob;
1023	for entry in /sys/block/sd*; do
1024		disk_type="$(cat $entry/device/vendor)";
1025		if [[ $disk_type == INTEL* ]] || [[ $disk_type == RAWSCSI* ]] || [[ $disk_type == LIO-ORG* ]]; then
1026			fname=$(basename $entry);
1027			echo -n " $fname";
1028		fi;
1029	done'
1030
1031	SCSI_DISK="$(echo "$script" | vm_exec $1 bash -s)"
1032
1033	if [[ -z "$SCSI_DISK" ]]; then
1034		error "no test disk found!"
1035		return 1
1036	fi
1037}
1038
1039# Script to perform scsi device reset on all disks in VM
1040# param $1 VM num
1041# param $2..$n Disks to perform reset on
1042function vm_reset_scsi_devices() {
1043	for disk in "${@:2}"; do
1044		notice "VM$1 Performing device reset on disk $disk"
1045		vm_exec $1 sg_reset /dev/$disk -vNd
1046	done
1047}
1048
1049function vm_check_blk_location() {
1050	local script='shopt -s nullglob; cd /sys/block; echo vd*'
1051	SCSI_DISK="$(echo "$script" | vm_exec $1 bash -s)"
1052
1053	if [[ -z "$SCSI_DISK" ]]; then
1054		error "no blk test disk found!"
1055		return 1
1056	fi
1057}
1058
1059function vm_check_nvme_location() {
1060	SCSI_DISK="$(vm_exec $1 "grep -l SPDK /sys/class/nvme/*/model" | awk -F/ '{print $5"n1"}')"
1061	if [[ -z "$SCSI_DISK" ]]; then
1062		error "no vfio-user nvme test disk found!"
1063		return 1
1064	fi
1065}
1066
1067function run_fio() {
1068	local arg
1069	local job_file=""
1070	local fio_bin=""
1071	local vms=()
1072	local out=""
1073	local vm
1074	local run_server_mode=true
1075	local run_plugin_mode=false
1076	local fio_start_cmd
1077	local fio_output_format="normal"
1078	local fio_gtod_reduce=false
1079	local wait_for_fio=true
1080
1081	for arg in "$@"; do
1082		case "$arg" in
1083			--job-file=*) local job_file="${arg#*=}" ;;
1084			--fio-bin=*) local fio_bin="${arg#*=}" ;;
1085			--vm=*) vms+=("${arg#*=}") ;;
1086			--out=*)
1087				local out="${arg#*=}"
1088				mkdir -p $out
1089				;;
1090			--local) run_server_mode=false ;;
1091			--plugin)
1092				notice "Using plugin mode. Disabling server mode."
1093				run_plugin_mode=true
1094				run_server_mode=false
1095				;;
1096			--json) fio_output_format="json" ;;
1097			--hide-results) hide_results=true ;;
1098			--no-wait-for-fio) wait_for_fio=false ;;
1099			--gtod-reduce) fio_gtod_reduce=true ;;
1100			*)
1101				error "Invalid argument '$arg'"
1102				return 1
1103				;;
1104		esac
1105	done
1106
1107	if [[ -n "$fio_bin" && ! -r "$fio_bin" ]]; then
1108		error "FIO binary '$fio_bin' does not exist"
1109		return 1
1110	fi
1111
1112	if [[ -z "$fio_bin" ]]; then
1113		fio_bin="fio"
1114	fi
1115
1116	if [[ ! -r "$job_file" ]]; then
1117		error "Fio job '$job_file' does not exist"
1118		return 1
1119	fi
1120
1121	fio_start_cmd="$fio_bin --eta=never "
1122
1123	local job_fname
1124	job_fname=$(basename "$job_file")
1125	log_fname="${job_fname%%.*}.log"
1126	fio_start_cmd+=" --output=$out/$log_fname --output-format=$fio_output_format "
1127
1128	# prepare job file for each VM
1129	for vm in "${vms[@]}"; do
1130		local vm_num=${vm%%:*}
1131		local vmdisks=${vm#*:}
1132
1133		sed "s@filename=@filename=$vmdisks@;s@description=\(.*\)@description=\1 (VM=$vm_num)@" "$job_file" \
1134			| vm_exec $vm_num "cat > /root/$job_fname"
1135
1136		if $fio_gtod_reduce; then
1137			vm_exec $vm_num "echo 'gtod_reduce=1' >> /root/$job_fname"
1138		fi
1139
1140		vm_exec $vm_num cat /root/$job_fname
1141
1142		if $run_server_mode; then
1143			fio_start_cmd+="--client=127.0.0.1,$(vm_fio_socket $vm_num) --remote-config /root/$job_fname "
1144		fi
1145
1146		if ! $run_server_mode; then
1147			if [[ -n "$fio_bin" ]]; then
1148				if ! $run_plugin_mode && [[ -e $fio_bin ]]; then
1149					vm_exec $vm_num 'cat > /root/fio; chmod +x /root/fio' < $fio_bin
1150					vm_fio_bin="/root/fio"
1151				else
1152					vm_fio_bin=$fio_bin
1153				fi
1154			fi
1155
1156			notice "Running local fio on VM $vm_num"
1157			vm_exec $vm_num "$vm_fio_bin --output=/root/$log_fname --output-format=$fio_output_format /root/$job_fname & echo \$! > /root/fio.pid" &
1158			vm_exec_pids+=("$!")
1159		fi
1160	done
1161
1162	if ! $run_server_mode; then
1163		if ! $wait_for_fio; then
1164			return 0
1165		fi
1166		echo "Waiting for guest fio instances to finish.."
1167		wait "${vm_exec_pids[@]}"
1168
1169		for vm in "${vms[@]}"; do
1170			local vm_num=${vm%%:*}
1171			vm_exec $vm_num cat /root/$log_fname > "$out/vm${vm_num}_${log_fname}"
1172		done
1173		return 0
1174	fi
1175
1176	$fio_start_cmd
1177	sleep 1
1178
1179	if [[ "$fio_output_format" == "json" ]]; then
1180		# Fio in client-server mode produces a lot of "trash" output
1181		# preceding JSON structure, making it not possible to parse.
1182		# Remove these lines from file.
1183		# shellcheck disable=SC2005
1184		echo "$(grep -vP '^[<\w]' "$out/$log_fname")" > "$out/$log_fname"
1185	fi
1186
1187	if [[ ! $hide_results ]]; then
1188		cat $out/$log_fname
1189	fi
1190}
1191
1192# Parsing fio results for json output and client-server mode only!
1193function parse_fio_results() {
1194	local fio_log_dir=$1
1195	local fio_log_filename=$2
1196	local fio_csv_filename
1197
1198	# Variables used in parsing loop
1199	local log_file
1200	local rwmode mixread mixwrite
1201	local lat_key lat_divisor
1202	local client_stats iops bw
1203	local read_avg_lat read_min_lat read_max_lat
1204	local write_avg_lat write_min_lat write_min_lat
1205	local clients
1206
1207	declare -A results
1208	results["iops"]=0
1209	results["bw"]=0
1210	results["avg_lat"]=0
1211	results["min_lat"]=0
1212	results["max_lat"]=0
1213
1214	# Loop using the log filename to see if there are any other
1215	# matching files. This is in case we ran fio test multiple times.
1216	log_files=("$fio_log_dir/$fio_log_filename"*)
1217	for log_file in "${log_files[@]}"; do
1218		# Save entire array to avoid opening $log_file multiple times
1219		clients=$(jq -r '.client_stats' "$log_file")
1220		[[ -n $clients ]]
1221		rwmode=$(jq -r '.[0]["job options"]["rw"]' <<< "$clients")
1222		mixread=1
1223		mixwrite=1
1224		if [[ $rwmode = *"rw"* ]]; then
1225			mixread=$(jq -r '.[0]["job options"]["rwmixread"]' <<< "$clients")
1226			mixread=$(bc -l <<< "scale=3; $mixread/100")
1227			mixwrite=$(bc -l <<< "scale=3; 1-$mixread")
1228		fi
1229
1230		client_stats=$(jq -r '.[] | select(.jobname == "All clients")' <<< "$clients")
1231		if [[ -z $client_stats ]]; then
1232			# Potentially single client (single VM)
1233			client_stats=$(jq -r '.[]' <<< "$clients")
1234		fi
1235
1236		# Check latency unit and later normalize to microseconds
1237		lat_key="lat_us"
1238		lat_divisor=1
1239		if jq -er '.read["lat_ns"]' &> /dev/null <<< $client_stats; then
1240			lat_key="lat_ns"
1241			lat_divisor=1000
1242		fi
1243
1244		# Horrific bash float point arithmetic operations below.
1245		# Viewer discretion is advised.
1246		iops=$(jq -r '[.read["iops"],.write["iops"]] | add' <<< $client_stats)
1247		bw=$(jq -r '[.read["bw"],.write["bw"]] | add' <<< $client_stats)
1248		read_avg_lat=$(jq -r --arg lat_key $lat_key '.read[$lat_key]["mean"]' <<< $client_stats)
1249		read_min_lat=$(jq -r --arg lat_key $lat_key '.read[$lat_key]["min"]' <<< $client_stats)
1250		read_max_lat=$(jq -r --arg lat_key $lat_key '.read[$lat_key]["max"]' <<< $client_stats)
1251		write_avg_lat=$(jq -r --arg lat_key $lat_key '.write[$lat_key]["mean"]' <<< $client_stats)
1252		write_min_lat=$(jq -r --arg lat_key $lat_key '.write[$lat_key]["min"]' <<< $client_stats)
1253		write_max_lat=$(jq -r --arg lat_key $lat_key '.write[$lat_key]["max"]' <<< $client_stats)
1254
1255		results["iops"]=$(bc -l <<< "${results[iops]} + $iops")
1256		results["bw"]=$(bc -l <<< "${results[bw]} + $bw")
1257		results["avg_lat"]=$(bc -l <<< "${results[avg_lat]} + ($mixread*$read_avg_lat + $mixwrite*$write_avg_lat)/$lat_divisor")
1258		results["min_lat"]=$(bc -l <<< "${results[min_lat]} + ($mixread*$read_min_lat + $mixwrite*$write_min_lat)/$lat_divisor")
1259		results["max_lat"]=$(bc -l <<< "${results[max_lat]} + ($mixread*$read_max_lat + $mixwrite*$write_max_lat)/$lat_divisor")
1260	done
1261
1262	results["iops"]=$(bc -l <<< "scale=3; ${results[iops]} / ${#log_files[@]}")
1263	results["bw"]=$(bc -l <<< "scale=3; ${results[bw]} / ${#log_files[@]}")
1264	results["avg_lat"]=$(bc -l <<< "scale=3; ${results[avg_lat]} / ${#log_files[@]}")
1265	results["min_lat"]=$(bc -l <<< "scale=3; ${results[min_lat]} / ${#log_files[@]}")
1266	results["max_lat"]=$(bc -l <<< "scale=3; ${results[max_lat]} / ${#log_files[@]}")
1267
1268	fio_csv_filename="${fio_log_filename%%.*}.csv"
1269	cat <<- EOF > "$fio_log_dir/$fio_csv_filename"
1270		iops,bw,avg_lat,min_lat,max_lat
1271		${results["iops"]},${results["bw"]},${results["avg_lat"]},${results["min_lat"]},${results["max_lat"]}
1272	EOF
1273}
1274
1275# Shutdown or kill any running VM and SPDK APP.
1276#
1277function at_app_exit() {
1278	local vhost_name
1279
1280	notice "APP EXITING"
1281	notice "killing all VMs"
1282	vm_kill_all
1283	# Kill vhost application
1284	notice "killing vhost app"
1285
1286	for vhost_name in "$TARGET_DIR"/*; do
1287		vhost_kill "$(basename "$vhost_name")"
1288	done
1289
1290	notice "EXIT DONE"
1291}
1292
1293function error_exit() {
1294	trap - ERR
1295	print_backtrace
1296	set +e
1297	error "Error on $1 $2"
1298
1299	at_app_exit
1300	exit 1
1301}
1302
1303function lookup_dev_irqs() {
1304	local vm=$1 irqs=() cpus=()
1305	local script_get_irqs script_get_cpus
1306
1307	mkdir -p "$VHOST_DIR/irqs"
1308
1309	# All vhost tests depend either on virtio_blk or virtio_scsi drivers on the VM side.
1310	# Considering that, simply iterate over virtio bus and pick pci device corresponding
1311	# to each virtio device.
1312	# For vfio-user setup, look for bare nvme devices.
1313
1314	script_get_irqs=$(
1315		cat <<- 'SCRIPT'
1316			shopt -s nullglob
1317			for virtio in /sys/bus/virtio/devices/virtio*; do
1318			  irqs+=("$(readlink -f "$virtio")/../msi_irqs/"*)
1319			done
1320			irqs+=(/sys/class/nvme/nvme*/device/msi_irqs/*)
1321			printf '%u\n' "${irqs[@]##*/}"
1322		SCRIPT
1323	)
1324
1325	script_get_cpus=$(
1326		cat <<- 'SCRIPT'
1327			cpus=(/sys/devices/system/cpu/cpu[0-9]*)
1328			printf '%u\n' "${cpus[@]##*cpu}"
1329		SCRIPT
1330	)
1331
1332	irqs=($(vm_exec "$vm" "$script_get_irqs"))
1333	cpus=($(vm_exec "$vm" "$script_get_cpus"))
1334	((${#irqs[@]} > 0 && ${#cpus[@]} > 0))
1335
1336	printf '%u\n' "${irqs[@]}" > "$VHOST_DIR/irqs/$vm.irqs"
1337	printf '%u\n' "${cpus[@]}" > "$VHOST_DIR/irqs/$vm.cpus"
1338}
1339
1340function irqs() {
1341	local vm
1342	for vm; do
1343		vm_exec "$vm" "while :; do cat /proc/interrupts; sleep 1s; done" > "$VHOST_DIR/irqs/$vm.interrupts" &
1344		irqs_pids+=($!)
1345	done
1346}
1347
1348function parse_irqs() {
1349	local iter=${1:-1}
1350	"$rootdir/test/vhost/parse_irqs.sh" "$VHOST_DIR/irqs/"*.interrupts
1351	rm "$VHOST_DIR/irqs/"*.interrupts
1352
1353	mkdir -p "$VHOST_DIR/irqs/$iter"
1354	mv "$VHOST_DIR/irqs/"*.parsed "$VHOST_DIR/irqs/$iter/"
1355}
1356
1357function collect_perf() {
1358	local cpus=$1 outf=$2 runtime=$3 delay=$4
1359
1360	mkdir -p "$VHOST_DIR/perf"
1361
1362	perf record -g \
1363		${cpus:+-C "$cpus"} \
1364		${outf:+-o "$outf"} \
1365		${delay:+-D $((delay * 1000))} \
1366		-z \
1367		${runtime:+ -- sleep $((runtime + delay))}
1368}
1369
1370function parse_perf() {
1371	local iter=${1:-1}
1372	local report out
1373
1374	mkdir -p "$VHOST_DIR/perf/$iter"
1375	shift
1376
1377	for report in "$@" "$VHOST_DIR/perf/"*.perf; do
1378		[[ -f $report ]] || continue
1379		perf report \
1380			-n \
1381			-i "$report" \
1382			--header \
1383			--stdio > "$VHOST_DIR/perf/$iter/${report##*/}.parsed"
1384		cp "$report" "$VHOST_DIR/perf/$iter/"
1385	done
1386	rm "$VHOST_DIR/perf/"*.perf
1387}
1388
1389function get_from_fio() {
1390	local opt=$1 conf=$2
1391
1392	[[ -n $opt && -f $conf ]] || return 1
1393
1394	awk -F= "/^$opt/{print \$2}" "$conf"
1395}
1396
1397function get_free_tcp_port() {
1398	local port=$1 to=${2:-1} sockets=()
1399
1400	mapfile -t sockets < /proc/net/tcp
1401
1402	# If there's a TCP socket in a listening state keep incrementing $port until
1403	# we find one that's not used. $to determines how long should we look for:
1404	#  0: don't increment, just check if given $port is in use
1405	# >0: increment $to times
1406	# <0: no increment limit
1407
1408	while [[ ${sockets[*]} == *":$(printf '%04X' "$port") 00000000:0000 0A"* ]]; do
1409		((to-- && ++port <= 65535)) || return 1
1410	done
1411
1412	echo "$port"
1413}
1414
1415function limit_vhost_kernel_threads() {
1416	local cpus=$1 nodes cpu _cpus=() _nodes=()
1417	local _pids=() pid cgroup pid
1418
1419	xtrace_disable_per_cmd map_cpus
1420
1421	_cpus=($(parse_cpu_list <(echo "$cpus")))
1422
1423	for cpu in "${_cpus[@]}"; do
1424		_nodes+=("${cpu_node_map[cpu]}")
1425	done
1426
1427	nodes=$(fold_array_onto_string "${_nodes[@]}")
1428
1429	# vhost kernel threads are named as vhost-PID
1430	_pids=($(pgrep vhost))
1431	((${#_pids[@]} > 0)) || return 1
1432
1433	# All threads should be located under the same initial cgroup. kthreadd does not put them
1434	# under root cgroup, but rather the cgroup of a session from which target/vhost was configured.
1435	# We create dedicated cgroup under the initial one to move all the threads only once instead of
1436	# having an extra step of moving them to the root cgroup first.
1437	set_cgroup_attr_top_bottom "${_pids[0]}" cgroup.subtree_control "+cpuset"
1438
1439	cgroup=$(get_cgroup "${_pids[0]}")
1440	create_cgroup "$cgroup/vhost"
1441
1442	set_cgroup_attr "$cgroup/vhost" cpuset.cpus "$cpus"
1443	set_cgroup_attr "$cgroup/vhost" cpuset.mems "$nodes"
1444
1445	for pid in "${_pids[@]}"; do
1446		move_proc "$pid" "$cgroup/vhost" "$cgroup" cgroup.threads
1447	done
1448}
1449