Skip to content

Performance test for vhost-user-blk #4190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 8, 2023
Merged
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
11 changes: 8 additions & 3 deletions .buildkite/pipeline_ab.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
)

perf_test = {
"block": {
"label": "🖴 Block Performance",
"test_path": "integration_tests/performance/test_block_ab.py",
"virtio-block": {
"label": "🖴 Virtio Block Performance",
"test_path": "integration_tests/performance/test_block_ab.py::test_block_performance",
"devtool_opts": "-c 1-10 -m 0",
},
"vhost-user-block": {
"label": "🖴 vhost-user Block Performance",
"test_path": "integration_tests/performance/test_block_ab.py::test_block_vhost_user_performance",
"devtool_opts": "-c 1-10 -m 0",
},
"network-latency": {
Expand Down
37 changes: 37 additions & 0 deletions tests/framework/utils_vhost_user_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""Utilities for vhost-user-blk backend."""

import os
import subprocess
import time

from framework import utils

VHOST_USER_SOCKET = "/vub.socket"


def spawn_vhost_user_backend(vm, host_mem_path, readonly=False):
"""Spawn vhost-user-blk backend."""

uid = vm.jailer.uid
gid = vm.jailer.gid

socket_path = f"{vm.chroot()}{VHOST_USER_SOCKET}"
args = ["vhost-user-blk", "-s", socket_path, "-b", host_mem_path]
if readonly:
args.append("-r")
proc = subprocess.Popen(args)

# Give the backend time to initialise.
time.sleep(1)

assert proc is not None and proc.poll() is None, "backend is not up"

with utils.chroot(vm.chroot()):
# The backend will create the socket path with root rights.
# Change rights to the jailer's.
os.chown(VHOST_USER_SOCKET, uid, gid)

return proc
11 changes: 10 additions & 1 deletion tests/host_tools/drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""Utilities for creating filesystems on the host."""

import os
import tempfile

from framework import utils

Expand All @@ -13,12 +14,20 @@ class FilesystemFile:
KNOWN_FILEFS_FORMATS = {"ext4"}
path = None

def __init__(self, path: str, size: int = 256, fs_format: str = "ext4"):
def __init__(self, path: str = None, size: int = 256, fs_format: str = "ext4"):
"""Create a new file system in a file.

Raises if the file system format is not supported, if the file already
exists, or if it ends in '/'.
"""

# If no path is supplied, use a temporary file.
# This is useful to force placing the file on disk, not in memory,
# because qemu vhost-user-blk backend always uses O_DIRECT,
# but O_DIRECT is not supported by tmpfs.
if path is None:
_, path = tempfile.mkstemp(suffix=f".{fs_format}", dir="/tmp")

if fs_format not in self.KNOWN_FILEFS_FORMATS:
raise ValueError("Format not in: + " + str(self.KNOWN_FILEFS_FORMATS))
# Here we append the format as a
Expand Down
33 changes: 5 additions & 28 deletions tests/integration_tests/functional/test_drive_vhost_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,11 @@
# SPDX-License-Identifier: Apache-2.0
"""Tests for vhost-user-block device."""

import os
import subprocess
import time

from framework import utils

VHOST_USER_SOCKET = "/vub.socket"


def spawn_vhost_user_backend(vm, host_mem_path):
"""Spawn vhost-user-block backend."""

uid = vm.jailer.uid
gid = vm.jailer.gid

sp = f"{vm.chroot()}{VHOST_USER_SOCKET}"
args = ["vhost-user-blk", "-s", sp, "-b", host_mem_path, "-r"]
proc = subprocess.Popen(args)

time.sleep(1)
if proc is None or proc.poll() is not None:
print("vub is not running")

with utils.chroot(vm.chroot()):
# The backend will create the socket path with root rights.
# Change rights to the jailer's.
os.chown(VHOST_USER_SOCKET, uid, gid)
return proc
from framework.utils_vhost_user_backend import (
VHOST_USER_SOCKET,
spawn_vhost_user_backend,
)


def test_vhost_user_block(microvm_factory, guest_kernel, rootfs_ubuntu_22):
Expand All @@ -48,7 +25,7 @@ def test_vhost_user_block(microvm_factory, guest_kernel, rootfs_ubuntu_22):
# Converting path from tmpfs ("./srv/..") to local
# path on the host ("../build/..")
rootfs_path = utils.to_local_dir_path(str(rootfs_ubuntu_22))
_backend = spawn_vhost_user_backend(vm, rootfs_path)
_backend = spawn_vhost_user_backend(vm, rootfs_path, readonly=True)

vm.basic_config()
vm.add_vhost_user_block("1", VHOST_USER_SOCKET, is_root_device=True)
Expand Down
62 changes: 61 additions & 1 deletion tests/integration_tests/performance/test_block_ab.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
import pytest

import host_tools.drive as drive_tools
from framework.utils import CmdBuilder, get_cpu_percent, run_cmd
from framework.utils import CmdBuilder, ProcessManager, get_cpu_percent, run_cmd
from framework.utils_vhost_user_backend import (
VHOST_USER_SOCKET,
spawn_vhost_user_backend,
)

# size of the block device used in the test, in MB
BLOCK_DEVICE_SIZE_MB = 2048
Expand Down Expand Up @@ -188,3 +192,59 @@ def test_block_performance(
**vm.dimensions,
}
)


def pin_backend(backend, cpu_id: int):
"""Pin the vhost-user backend to a cpu list."""
return ProcessManager.set_cpu_affinity(backend.pid, [cpu_id])


@pytest.mark.nonci
@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"])
@pytest.mark.parametrize("fio_mode", ["randread", "randwrite"])
@pytest.mark.parametrize("fio_block_size", [4096], ids=["bs4096"])
def test_block_vhost_user_performance(
microvm_factory,
guest_kernel,
rootfs,
vcpus,
fio_mode,
fio_block_size,
metrics,
):
"""
Execute block device emulation benchmarking scenarios.
"""
vm = microvm_factory.build(guest_kernel, rootfs, monitor_memory=False)
vm.spawn(log_level="Info")
vm.basic_config(vcpu_count=vcpus, mem_size_mib=GUEST_MEM_MIB)
vm.add_net_iface()

# Add a secondary block device for benchmark tests.
fs = drive_tools.FilesystemFile(size=BLOCK_DEVICE_SIZE_MB)
backend = spawn_vhost_user_backend(vm, fs.path, readonly=False)
vm.add_vhost_user_block("scratch", VHOST_USER_SOCKET)
vm.start()

# Pin uVM threads to physical cores.
assert vm.pin_vmm(0), "Failed to pin firecracker thread."
assert vm.pin_api(1), "Failed to pin fc_api thread."
pin_backend(backend, 2)
for i in range(vm.vcpus_count):
assert vm.pin_vcpu(i, i + 3), f"Failed to pin fc_vcpu {i} thread."

logs_dir, cpu_load = run_fio(vm, fio_mode, fio_block_size)

process_fio_logs(vm, fio_mode, logs_dir, metrics)

for cpu_util_data_point in list(cpu_load["firecracker"].values())[0]:
metrics.put_metric("cpu_utilization_vmm", cpu_util_data_point, "Percent")

metrics.set_dimensions(
{
"performance_test": "test_block_vhost_user_performance",
"fio_mode": fio_mode,
"fio_block_size": str(fio_block_size),
**vm.dimensions,
}
)