Skip to content

Commit

Permalink
Hotplug latency tests
Browse files Browse the repository at this point in the history
Test latency of hotplugging via udev and manual agent

Signed-off-by: James Curtis <[email protected]>
  • Loading branch information
JamesC1305 committed Aug 9, 2024
1 parent e6c2c93 commit 0e3be4b
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/vmm/src/devices/pseudo/boot_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE: u8 = 123;
/// Pseudo device to record the kernel boot time.
#[derive(Debug)]
pub struct BootTimer {
start_ts: TimestampUs,
pub start_ts: TimestampUs,
}

impl BootTimer {
Expand Down
6 changes: 6 additions & 0 deletions src/vmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,12 @@ impl Vmm {
self.resume_vcpu_threads(start_idx.into())?;

self.acpi_device_manager.notify_cpu_container()?;
if let Some(devices::BusDevice::BootTimer(timer)) =
self.get_bus_device(DeviceType::BootTimer, "BootTimer")
{
let mut locked_timer = timer.lock().expect("Poisoned lock");
locked_timer.start_ts = utils::time::TimestampUs::default();
}

Ok(new_machine_config)
}
Expand Down
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ def rootfs_fxt(request, record_property):
guest_kernel_linux_5_10 = pytest.fixture(
guest_kernel_fxt, params=kernel_params("vmlinux-5.10*")
)
guest_kernel_linux_acpi_only = pytest.fixture(
guest_kernel_fxt, params=kernel_params("vmlinux-5.10.221")
)
# Use the unfiltered selector, since we don't officially support 6.1 yet.
# TODO: switch to default selector once we add full 6.1 support.
guest_kernel_linux_6_1 = pytest.fixture(
Expand Down Expand Up @@ -394,6 +397,15 @@ def uvm_plain_rw(microvm_factory, guest_kernel_linux_5_10, rootfs_rw):
return microvm_factory.build(guest_kernel_linux_5_10, rootfs_rw)


@pytest.fixture
def uvm_hotplug(microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw):
"""Create a VM with ACPI enabled kernels only.
kernel: 5.10
rootfs: Ubuntu 22.04
"""
return microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)


@pytest.fixture
def uvm_nano(uvm_plain):
"""A preconfigured uvm with 2vCPUs and 256MiB of memory
Expand Down
1 change: 1 addition & 0 deletions tests/host_tools/1-cpu-hotplug.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SUBSYSTEM=="cpu", ACTION=="add", ATTR{online}!="1", ATTR{online}="1"
11 changes: 11 additions & 0 deletions tests/host_tools/hotplug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

while :; do
[[ -d /sys/devices/system/cpu/cpu$1 ]] && break
done

echo 1 | tee /sys/devices/system/cpu/cpu*/online

/home/hotplug_time.o
33 changes: 33 additions & 0 deletions tests/host_tools/hotplug_time.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Init wrapper for boot timing. It points at /sbin/init.

#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

// Base address values are defined in arch/src/lib.rs as arch::MMIO_MEM_START.
// Values are computed in arch/src/<arch>/mod.rs from the architecture layouts.
// Position on the bus is defined by MMIO_LEN increments, where MMIO_LEN is
// defined as 0x1000 in vmm/src/device_manager/mmio.rs.
#ifdef __x86_64__
#define MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE 0xd0000000
#endif
#ifdef __aarch64__
#define MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE 0x40000000
#endif

#define MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE 123

int main() {
int fd = open("/dev/mem", (O_RDWR | O_SYNC | O_CLOEXEC));
int mapped_size = getpagesize();

char *map_base = mmap(NULL, mapped_size, PROT_WRITE, MAP_SHARED, fd,
MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE);

*map_base = MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE;
msync(map_base, mapped_size, MS_ASYNC);
}
Binary file added tests/host_tools/hotplug_time.o
Binary file not shown.
9 changes: 9 additions & 0 deletions tests/host_tools/hotplug_udev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

while :; do
[[ $(nproc) == $1 ]] && break
done

/home/hotplug_time.o
14 changes: 11 additions & 3 deletions tests/integration_tests/functional/test_vcpu_hotplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@

"""Integration tests for hotplugging vCPUs"""

import platform
import re
import time

import pytest

from framework import microvm
from framework.defs import MAX_SUPPORTED_VCPUS
from framework.microvm import Serial
from framework.utils_cpuid import check_guest_cpuid_output


@pytest.mark.skipif(
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
)
@pytest.mark.parametrize("vcpu_count", [1, MAX_SUPPORTED_VCPUS - 1])
def test_hotplug_vcpus(uvm_plain, vcpu_count):
"""Test hotplugging works as intended"""
Expand All @@ -40,6 +42,9 @@ def test_hotplug_vcpus(uvm_plain, vcpu_count):
)


@pytest.mark.skipif(
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
)
@pytest.mark.parametrize(
"vcpu_count", [-1, 0, MAX_SUPPORTED_VCPUS, MAX_SUPPORTED_VCPUS + 1]
)
Expand All @@ -63,7 +68,7 @@ def test_negative_hotplug_vcpus(uvm_plain, vcpu_count):
with pytest.raises(
RuntimeError,
match=re.compile(
f"An error occurred when deserializing the json body of a request: invalid value: integer `-\\d+`, expected u8+"
"An error occurred when deserializing the json body of a request: invalid value: integer `-\\d+`, expected u8+"
),
):
uvm_plain.api.hotplug.put(Vcpu={"add": vcpu_count})
Expand All @@ -75,6 +80,9 @@ def test_negative_hotplug_vcpus(uvm_plain, vcpu_count):
uvm_plain.api.hotplug.put(Vcpu={"add": vcpu_count})


@pytest.mark.skipif(
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
)
@pytest.mark.parametrize("vcpu_count", [1, MAX_SUPPORTED_VCPUS - 1])
def test_online_hotplugged_vcpus(uvm_plain, vcpu_count):
"""Test that hotplugged CPUs can be onlined"""
Expand Down
152 changes: 152 additions & 0 deletions tests/integration_tests/performance/test_vcpu_hotplug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""Testing hotplug performance"""

import platform
import re
import time
from pathlib import Path

import pandas
import pytest

from framework.utils_iperf import IPerf3Test, emit_iperf3_metrics
from host_tools.cargo_build import gcc_compile
from host_tools.fcmetrics import FCMetricsMonitor


@pytest.mark.skipif(
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
)
@pytest.mark.parametrize(
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
)
def test_custom_udev_rule_latency(
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count, results_dir
):
"""Test the latency for hotplugging and booting CPUs in the guest"""
gcc_compile(Path("./host_tools/hotplug_time.c"), Path("host_tools/hotplug_time.o"))
data = []
for i in range(50):
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
uvm_hotplug.help.enable_console()
uvm_hotplug.spawn()
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
uvm_hotplug.add_net_iface()
uvm_hotplug.start()
uvm_hotplug.ssh.scp_put(
Path("./host_tools/hotplug_udev.sh"), Path("/home/hotplug_udev.sh")
)
uvm_hotplug.ssh.scp_put(
Path("./host_tools/hotplug_time.o"), Path("/home/hotplug_time.o")
)
uvm_hotplug.ssh.scp_put(
Path("./host_tools/1-cpu-hotplug.rules"),
Path("/usr/lib/udev/rules.d/1-cpu-hotplug.rules"),
)
uvm_hotplug.ssh.run(
f"udevadm control --reload-rules && tmux new-session -d /bin/bash /home/hotplug_udev.sh {vcpu_count}"
)

uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
time.sleep(2)

# Extract API call duration
api_duration = (
float(
re.findall(
r"Total previous API call duration: (\d+) us\.",
uvm_hotplug.log_data,
)[-1]
)
/ 1000
)
try:
timestamp = (
float(
re.findall(
r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data
)[0]
)
/ 1000
)
except IndexError:
timestamp = None

data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": timestamp})

output_file = results_dir / f"hotplug-{vcpu_count}.csv"

csv_data = pandas.DataFrame.from_dict(data).to_csv(
index=False,
float_format="%.3f",
)

output_file.write_text(csv_data)


@pytest.mark.skipif(
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
)
@pytest.mark.parametrize(
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
)
def test_manual_latency(
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count
):
"""Test the latency for hotplugging and booting CPUs in the guest"""
gcc_compile(Path("./host_tools/hotplug_time.c"), Path("host_tools/hotplug_time.o"))
data = []
for _ in range(50):
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
uvm_hotplug.help.enable_console()
uvm_hotplug.spawn()
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
uvm_hotplug.add_net_iface()
uvm_hotplug.start()
uvm_hotplug.ssh.scp_put(
Path("./host_tools/hotplug.sh"), Path("/home/hotplug.sh")
)
uvm_hotplug.ssh.scp_put(
Path("./host_tools/hotplug_time.o"), Path("/home/hotplug_time.o")
)
uvm_hotplug.ssh.run("tmux new-session -d /bin/bash /home/hotplug.sh")

uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})

time.sleep(1.5)
# Extract API call duration
api_duration = (
float(
re.findall(
r"Total previous API call duration: (\d+) us\.",
uvm_hotplug.log_data,
)[-1]
)
/ 1000
)
try:
timestamp = (
float(
re.findall(
r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data
)[0]
)
/ 1000
)
except IndexError:
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": None})
continue

data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": timestamp})

df = pandas.DataFrame.from_dict(data).to_csv(
f"../test_results/manual-hotplug_{vcpu_count}.csv",
index=False,
float_format="%.3f",
)

output_file.write_text(csv_data)

0 comments on commit 0e3be4b

Please sign in to comment.