Skip to content

Commit 0e3be4b

Browse files
committed
Hotplug latency tests
Test latency of hotplugging via udev and manual agent Signed-off-by: James Curtis <jxcurtis@amazon.co.uk>
1 parent e6c2c93 commit 0e3be4b

File tree

10 files changed

+236
-4
lines changed

10 files changed

+236
-4
lines changed

src/vmm/src/devices/pseudo/boot_timer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE: u8 = 123;
1010
/// Pseudo device to record the kernel boot time.
1111
#[derive(Debug)]
1212
pub struct BootTimer {
13-
start_ts: TimestampUs,
13+
pub start_ts: TimestampUs,
1414
}
1515

1616
impl BootTimer {

src/vmm/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,12 @@ impl Vmm {
688688
self.resume_vcpu_threads(start_idx.into())?;
689689

690690
self.acpi_device_manager.notify_cpu_container()?;
691+
if let Some(devices::BusDevice::BootTimer(timer)) =
692+
self.get_bus_device(DeviceType::BootTimer, "BootTimer")
693+
{
694+
let mut locked_timer = timer.lock().expect("Poisoned lock");
695+
locked_timer.start_ts = utils::time::TimestampUs::default();
696+
}
691697

692698
Ok(new_machine_config)
693699
}

tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ def rootfs_fxt(request, record_property):
361361
guest_kernel_linux_5_10 = pytest.fixture(
362362
guest_kernel_fxt, params=kernel_params("vmlinux-5.10*")
363363
)
364+
guest_kernel_linux_acpi_only = pytest.fixture(
365+
guest_kernel_fxt, params=kernel_params("vmlinux-5.10.221")
366+
)
364367
# Use the unfiltered selector, since we don't officially support 6.1 yet.
365368
# TODO: switch to default selector once we add full 6.1 support.
366369
guest_kernel_linux_6_1 = pytest.fixture(
@@ -394,6 +397,15 @@ def uvm_plain_rw(microvm_factory, guest_kernel_linux_5_10, rootfs_rw):
394397
return microvm_factory.build(guest_kernel_linux_5_10, rootfs_rw)
395398

396399

400+
@pytest.fixture
401+
def uvm_hotplug(microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw):
402+
"""Create a VM with ACPI enabled kernels only.
403+
kernel: 5.10
404+
rootfs: Ubuntu 22.04
405+
"""
406+
return microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
407+
408+
397409
@pytest.fixture
398410
def uvm_nano(uvm_plain):
399411
"""A preconfigured uvm with 2vCPUs and 256MiB of memory

tests/host_tools/1-cpu-hotplug.rules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SUBSYSTEM=="cpu", ACTION=="add", ATTR{online}!="1", ATTR{online}="1"

tests/host_tools/hotplug.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
while :; do
6+
[[ -d /sys/devices/system/cpu/cpu$1 ]] && break
7+
done
8+
9+
echo 1 | tee /sys/devices/system/cpu/cpu*/online
10+
11+
/home/hotplug_time.o

tests/host_tools/hotplug_time.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Init wrapper for boot timing. It points at /sbin/init.
5+
6+
#include <fcntl.h>
7+
#include <sys/mman.h>
8+
#include <sys/types.h>
9+
#include <unistd.h>
10+
11+
// Base address values are defined in arch/src/lib.rs as arch::MMIO_MEM_START.
12+
// Values are computed in arch/src/<arch>/mod.rs from the architecture layouts.
13+
// Position on the bus is defined by MMIO_LEN increments, where MMIO_LEN is
14+
// defined as 0x1000 in vmm/src/device_manager/mmio.rs.
15+
#ifdef __x86_64__
16+
#define MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE 0xd0000000
17+
#endif
18+
#ifdef __aarch64__
19+
#define MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE 0x40000000
20+
#endif
21+
22+
#define MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE 123
23+
24+
int main() {
25+
int fd = open("/dev/mem", (O_RDWR | O_SYNC | O_CLOEXEC));
26+
int mapped_size = getpagesize();
27+
28+
char *map_base = mmap(NULL, mapped_size, PROT_WRITE, MAP_SHARED, fd,
29+
MAGIC_MMIO_SIGNAL_GUEST_BOOT_COMPLETE);
30+
31+
*map_base = MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE;
32+
msync(map_base, mapped_size, MS_ASYNC);
33+
}

tests/host_tools/hotplug_time.o

879 KB
Binary file not shown.

tests/host_tools/hotplug_udev.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
while :; do
6+
[[ $(nproc) == $1 ]] && break
7+
done
8+
9+
/home/hotplug_time.o

tests/integration_tests/functional/test_vcpu_hotplug.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33

44
"""Integration tests for hotplugging vCPUs"""
55

6+
import platform
67
import re
78
import time
89

910
import pytest
1011

11-
from framework import microvm
1212
from framework.defs import MAX_SUPPORTED_VCPUS
13-
from framework.microvm import Serial
1413
from framework.utils_cpuid import check_guest_cpuid_output
1514

1615

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

4244

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

7782

83+
@pytest.mark.skipif(
84+
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
85+
)
7886
@pytest.mark.parametrize("vcpu_count", [1, MAX_SUPPORTED_VCPUS - 1])
7987
def test_online_hotplugged_vcpus(uvm_plain, vcpu_count):
8088
"""Test that hotplugged CPUs can be onlined"""
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Testing hotplug performance"""
5+
6+
import platform
7+
import re
8+
import time
9+
from pathlib import Path
10+
11+
import pandas
12+
import pytest
13+
14+
from framework.utils_iperf import IPerf3Test, emit_iperf3_metrics
15+
from host_tools.cargo_build import gcc_compile
16+
from host_tools.fcmetrics import FCMetricsMonitor
17+
18+
19+
@pytest.mark.skipif(
20+
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
21+
)
22+
@pytest.mark.parametrize(
23+
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
24+
)
25+
def test_custom_udev_rule_latency(
26+
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count, results_dir
27+
):
28+
"""Test the latency for hotplugging and booting CPUs in the guest"""
29+
gcc_compile(Path("./host_tools/hotplug_time.c"), Path("host_tools/hotplug_time.o"))
30+
data = []
31+
for i in range(50):
32+
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
33+
uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
34+
uvm_hotplug.help.enable_console()
35+
uvm_hotplug.spawn()
36+
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
37+
uvm_hotplug.add_net_iface()
38+
uvm_hotplug.start()
39+
uvm_hotplug.ssh.scp_put(
40+
Path("./host_tools/hotplug_udev.sh"), Path("/home/hotplug_udev.sh")
41+
)
42+
uvm_hotplug.ssh.scp_put(
43+
Path("./host_tools/hotplug_time.o"), Path("/home/hotplug_time.o")
44+
)
45+
uvm_hotplug.ssh.scp_put(
46+
Path("./host_tools/1-cpu-hotplug.rules"),
47+
Path("/usr/lib/udev/rules.d/1-cpu-hotplug.rules"),
48+
)
49+
uvm_hotplug.ssh.run(
50+
f"udevadm control --reload-rules && tmux new-session -d /bin/bash /home/hotplug_udev.sh {vcpu_count}"
51+
)
52+
53+
uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
54+
time.sleep(2)
55+
56+
# Extract API call duration
57+
api_duration = (
58+
float(
59+
re.findall(
60+
r"Total previous API call duration: (\d+) us\.",
61+
uvm_hotplug.log_data,
62+
)[-1]
63+
)
64+
/ 1000
65+
)
66+
try:
67+
timestamp = (
68+
float(
69+
re.findall(
70+
r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data
71+
)[0]
72+
)
73+
/ 1000
74+
)
75+
except IndexError:
76+
timestamp = None
77+
78+
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": timestamp})
79+
80+
output_file = results_dir / f"hotplug-{vcpu_count}.csv"
81+
82+
csv_data = pandas.DataFrame.from_dict(data).to_csv(
83+
index=False,
84+
float_format="%.3f",
85+
)
86+
87+
output_file.write_text(csv_data)
88+
89+
90+
@pytest.mark.skipif(
91+
platform.machine() != "x86_64", reason="Hotplug only enabled on x86_64."
92+
)
93+
@pytest.mark.parametrize(
94+
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
95+
)
96+
def test_manual_latency(
97+
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count
98+
):
99+
"""Test the latency for hotplugging and booting CPUs in the guest"""
100+
gcc_compile(Path("./host_tools/hotplug_time.c"), Path("host_tools/hotplug_time.o"))
101+
data = []
102+
for _ in range(50):
103+
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
104+
uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
105+
uvm_hotplug.help.enable_console()
106+
uvm_hotplug.spawn()
107+
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
108+
uvm_hotplug.add_net_iface()
109+
uvm_hotplug.start()
110+
uvm_hotplug.ssh.scp_put(
111+
Path("./host_tools/hotplug.sh"), Path("/home/hotplug.sh")
112+
)
113+
uvm_hotplug.ssh.scp_put(
114+
Path("./host_tools/hotplug_time.o"), Path("/home/hotplug_time.o")
115+
)
116+
uvm_hotplug.ssh.run("tmux new-session -d /bin/bash /home/hotplug.sh")
117+
118+
uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
119+
120+
time.sleep(1.5)
121+
# Extract API call duration
122+
api_duration = (
123+
float(
124+
re.findall(
125+
r"Total previous API call duration: (\d+) us\.",
126+
uvm_hotplug.log_data,
127+
)[-1]
128+
)
129+
/ 1000
130+
)
131+
try:
132+
timestamp = (
133+
float(
134+
re.findall(
135+
r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data
136+
)[0]
137+
)
138+
/ 1000
139+
)
140+
except IndexError:
141+
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": None})
142+
continue
143+
144+
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": timestamp})
145+
146+
df = pandas.DataFrame.from_dict(data).to_csv(
147+
f"../test_results/manual-hotplug_{vcpu_count}.csv",
148+
index=False,
149+
float_format="%.3f",
150+
)
151+
152+
output_file.write_text(csv_data)

0 commit comments

Comments
 (0)