Skip to content

Commit 3e5187c

Browse files
committed
Initial (hacky) performance tests
Create initial performance tests measuring the time between hotplug API request and vCPUs being available to the guest. Note: These tests are NOT designed to be merged or used in CI, they are merely investigative tests for the latency of vCPU hotplugging Signed-off-by: James Curtis <[email protected]>
1 parent 52dabdf commit 3e5187c

File tree

10 files changed

+333
-2
lines changed

10 files changed

+333
-2
lines changed

src/vmm/src/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ fn create_vmm_and_vcpus(
201201
// This has to instantiated here, before the CpuContainer, to ensure that it gets the
202202
// correct address, the first page of MMIO memory.
203203
if boot_timer_enabled {
204-
let boot_timer = crate::devices::pseudo::BootTimer::new(TimestampUs::default());
204+
let mut boot_timer = crate::devices::pseudo::BootTimer::new(TimestampUs::default());
205205

206206
mmio_device_manager
207207
.register_mmio_boot_timer(&mut resource_allocator, boot_timer)

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

+1-1
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

+8
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ impl Vmm {
620620
&mut self,
621621
config: HotplugVcpuConfig,
622622
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
623+
use utils::time::TimestampUs;
624+
623625
use crate::logger::IncMetric;
624626
if config.add < 1 {
625627
return Err(HotplugVcpuError::VcpuCountTooLow);
@@ -688,6 +690,12 @@ impl Vmm {
688690
self.resume_vcpu_threads(start_idx.into())?;
689691

690692
self.acpi_device_manager.notify_cpu_container()?;
693+
if let Some(devices::BusDevice::BootTimer(dev)) = self
694+
.mmio_device_manager
695+
.get_device(DeviceType::BootTimer, "BootTimer")
696+
{
697+
dev.lock().unwrap().start_ts = TimestampUs::default()
698+
}
691699

692700
Ok(new_machine_config)
693701
}

tests/conftest.py

+8
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,11 @@ 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+
return microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
403+
404+
397405
@pytest.fixture
398406
def uvm_nano(uvm_plain):
399407
"""A preconfigured uvm with 2vCPUs and 256MiB of memory

tests/host_tools/1-cpu-hotplug.rules

+1
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.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# import pandas
2+
# import re
3+
# from framework.microvm import MicroVMFactory
4+
#
5+
# KERNEL = "vmlinux-5.10.221"
6+
# ROOTFS = "ubuntu-22.04.ext4"
7+
#
8+
#
9+
# def run_tests():
10+
# factory = MicrovmFactory(fc_binary_path, jailer_binary_path)
11+
# manual_data = test_manual_latency(factory)
12+
# manual_data.to_csv("~/dev/results/manual_hotplug_data.csv")
13+
#
14+
# def test_manual_latency(microvm_factory):
15+
# """Test the latency for hotplugging and booting CPUs in the guest"""
16+
# fc_binary_path, jailer_binary_path = build_tools.get_firecracker_binaries()
17+
# df = pandas.DataFrame(columns=["vcpus", "api", "onlining"])
18+
# gcc_compile(Path("./hotplug_time.c"), Path("./hotplug_time.o"))
19+
# data = []
20+
# for vcpu_count in range(2, 30, 2):
21+
# for i in range(50):
22+
# uvm_hotplug = microvm_factory.build(KERNEL, ROOTFS)
23+
# uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
24+
# uvm_hotplug.help.enable_console()
25+
# uvm_hotplug.spawn()
26+
# uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
27+
# uvm_hotplug.add_net_iface()
28+
# uvm_hotplug.start()
29+
# uvm_hotplug.ssh.scp_put(Path("./host_tools/hotplug.sh"), Path("/home/hotplug.sh"))
30+
# uvm_hotplug.ssh.scp_put(Path("./host_tools//hotplug_time.o"), Path("/home/hotplug_time.o"))
31+
# uvm_hotplug.ssh.run("tmux new-session -d /bin/bash /home/hotplug.sh > /home/test 2>&1")
32+
#
33+
#
34+
# uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
35+
#
36+
# time.sleep(0.25)
37+
# # Extract API call duration
38+
# api_duration = float(re.findall(r"Total previous API call duration: (\d+) us\.", uvm_hotplug.log_data)[-1]) / 1000
39+
# timestamp = float(re.findall(r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data)[0]) / 1000
40+
# data.append({"vcpus" : vcpu_count, "api": api_duration, "onlining": timestamp})
41+
# return pandas.DataFrame.from_dict(data)
42+
43+
# def test_custom_udev_latency():
44+
# """Test the latency for hotplugging and booting CPUs in the guest"""
45+
# fc_binary_path, jailer_binary_path = build_tools.get_firecracker_binaries()
46+
# df = pandas.DataFrame(columns=["vcpus", "api", "onlining"])
47+
# gcc_compile(Path("./hotplug_time.c"), Path("./hotplug_time.o"))
48+
# data = []
49+
# for vcpu_count in range(2, 30, 2):
50+
# for i in range(50):
51+
# uvm_hotplug = microvm_factory.build(KERNEL, ROOTFS)
52+
# uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
53+
# uvm_hotplug.help.enable_console()
54+
# uvm_hotplug.spawn()
55+
# uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
56+
# uvm_hotplug.add_net_iface()
57+
# uvm_hotplug.start()
58+
# uvm_hotplug.ssh.scp_put(Path("./host_tools/hotplug.sh"), Path("/home/hotplug.sh"))
59+
# uvm_hotplug.ssh.scp_put(Path("./host_tools//hotplug_time.o"), Path("/home/hotplug_time.o"))
60+
# uvm_hotplug.ssh.run("tmux new-session -d /bin/bash /home/hotplug.sh > /home/test 2>&1")
61+
#
62+
#
63+
# uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
64+
#
65+
# time.sleep(0.25)
66+
# # Extract API call duration
67+
# api_duration = float(re.findall(r"Total previous API call duration: (\d+) us\.", uvm_hotplug.log_data)[-1]) / 1000
68+
# timestamp = float(re.findall(r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data)[0]) / 1000
69+
# data.append({"vcpus" : vcpu_count, "api": api_duration, "onlining": timestamp})
70+
#

tests/host_tools/hotplug.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
while :; do
4+
[[ -d /sys/devices/system/cpu/cpu1 ]] && break
5+
done
6+
7+
readarray -t offline_cpus < <(lscpu -p=cpu --offline | sed '/^#/d')
8+
9+
for cpu_idx in ${offline_cpus[@]}; do
10+
echo 1 >/sys/devices/system/cpu/cpu$cpu_idx/online
11+
done
12+
13+
/home/hotplug_time.o

tests/host_tools/hotplug_time.c

+33
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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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 os
7+
import re
8+
import time
9+
from pathlib import Path
10+
11+
import pandas
12+
import pytest
13+
14+
from framework.utils_cpuid import check_guest_cpuid_output
15+
from host_tools.cargo_build import gcc_compile
16+
17+
18+
@pytest.mark.parametrize(
19+
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
20+
)
21+
def test_custom_udev_rule_latency(
22+
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count
23+
):
24+
"""Test the latency for hotplugging and booting CPUs in the guest"""
25+
api_durations = []
26+
onlining_durations = []
27+
print(f"Vcpu count: {vcpu_count}")
28+
for i in range(5):
29+
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
30+
uvm_hotplug.jailer.extra_args.update({"no-seccomp": None})
31+
uvm_hotplug.help.enable_console()
32+
uvm_hotplug.spawn()
33+
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
34+
uvm_hotplug.add_net_iface()
35+
uvm_hotplug.start()
36+
uvm_hotplug.ssh.run("rm /usr/lib/udev/rules.d/40-vm-hotadd.rules")
37+
uvm_hotplug.ssh.scp_put(
38+
Path("./host_tools/1-cpu-hotplug.rules"),
39+
Path("/usr/lib/udev/rules.d/1-cpu-hotplug.rules"),
40+
)
41+
42+
time.sleep(0.25)
43+
44+
uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
45+
time.sleep(0.25)
46+
_, stdout, _ = uvm_hotplug.ssh.run("dmesg")
47+
48+
# Extract API call duration
49+
api_duration = (
50+
float(
51+
re.findall(
52+
r"Total previous API call duration: (\d+) us\.",
53+
uvm_hotplug.log_data,
54+
)[-1]
55+
)
56+
/ 1000
57+
)
58+
59+
# Extract onlining timings
60+
start = float(
61+
re.findall(r"\[\s+(\d+\.\d+)\] CPU1 has been hot-added\n", stdout)[0]
62+
)
63+
end = float(re.findall(r"\[\s+(\d+\.\d+)\] \w+", stdout)[-1])
64+
elapsed_time = (end - start) * 1000
65+
print(f"Api call duration: {api_duration} ms")
66+
print(f"Onlining duration: {elapsed_time} ms")
67+
api_durations.append(api_duration)
68+
onlining_durations.append(elapsed_time)
69+
uvm_hotplug.kill()
70+
time.sleep(1)
71+
72+
avg_api_duration = sum(api_durations) / 5
73+
avg_onlining_duration = sum(onlining_durations) / 5
74+
print(f"Averages for {vcpu_count} hotplugged vcpus:")
75+
print(f"\tAverage API call duration: {avg_api_duration} ms")
76+
print(f"\tAverage onliing duration: {avg_onlining_duration} ms")
77+
78+
79+
@pytest.mark.parametrize(
80+
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
81+
)
82+
def test_default_udev_rule_latency(
83+
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count
84+
):
85+
"""Test the latency for hotplugging and booting CPUs in the guest"""
86+
api_durations = []
87+
onlining_durations = []
88+
print(f"Vcpu count: {vcpu_count}")
89+
for i in range(5):
90+
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
91+
uvm_hotplug.jailer.extra_args.update({"no-seccomp": None})
92+
uvm_hotplug.help.enable_console()
93+
uvm_hotplug.spawn()
94+
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
95+
uvm_hotplug.add_net_iface()
96+
uvm_hotplug.start()
97+
98+
time.sleep(0.25)
99+
100+
_, stdout, _ = uvm_hotplug.ssh.run("ls /usr/lib/udev/rules.d")
101+
default_rule = re.search(r"40-vm-hotadd\.rules", stdout)
102+
assert default_rule is not None
103+
104+
uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
105+
time.sleep(0.25)
106+
_, stdout, _ = uvm_hotplug.ssh.run("dmesg")
107+
108+
# Extract API call duration
109+
api_duration = (
110+
float(
111+
re.findall(
112+
r"Total previous API call duration: (\d+) us\.",
113+
uvm_hotplug.log_data,
114+
)[-1]
115+
)
116+
/ 1000
117+
)
118+
119+
# Extract onlining timings
120+
start = float(
121+
re.findall(r"\[\s+(\d+\.\d+)\] CPU1 has been hot-added\n", stdout)[0]
122+
)
123+
end = float(re.findall(r"\[\s+(\d+\.\d+)\] \w+", stdout)[-1])
124+
elapsed_time = (end - start) * 1000
125+
print(f"Api call duration: {api_duration} ms")
126+
print(f"Onlining duration: {elapsed_time} ms")
127+
api_durations.append(api_duration)
128+
onlining_durations.append(elapsed_time)
129+
uvm_hotplug.kill()
130+
time.sleep(1)
131+
132+
avg_api_duration = sum(api_durations) / 5
133+
avg_onlining_duration = sum(onlining_durations) / 5
134+
print(f"Averages for {vcpu_count} hotplugged vcpus:")
135+
print(f"\tAverage API call duration: {avg_api_duration} ms")
136+
print(f"\tAverage onliing duration: {avg_onlining_duration} ms")
137+
138+
139+
@pytest.mark.parametrize(
140+
"vcpu_count", [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
141+
)
142+
def test_manual_latency(
143+
microvm_factory, guest_kernel_linux_acpi_only, rootfs_rw, vcpu_count
144+
):
145+
"""Test the latency for hotplugging and booting CPUs in the guest"""
146+
gcc_compile(Path("./host_tools/hotplug_time.c"), Path("host_tools/hotplug_time.o"))
147+
data = []
148+
for i in range(50):
149+
uvm_hotplug = microvm_factory.build(guest_kernel_linux_acpi_only, rootfs_rw)
150+
uvm_hotplug.jailer.extra_args.update({"boot-timer": None, "no-seccomp": None})
151+
uvm_hotplug.help.enable_console()
152+
uvm_hotplug.spawn()
153+
uvm_hotplug.basic_config(vcpu_count=1, mem_size_mib=128)
154+
uvm_hotplug.add_net_iface()
155+
uvm_hotplug.start()
156+
uvm_hotplug.ssh.scp_put(
157+
Path("./host_tools/hotplug.sh"), Path("/home/hotplug.sh")
158+
)
159+
uvm_hotplug.ssh.scp_put(
160+
Path("./host_tools//hotplug_time.o"), Path("/home/hotplug_time.o")
161+
)
162+
uvm_hotplug.ssh.run(
163+
"tmux new-session -d /bin/bash /home/hotplug.sh > /home/test 2>&1"
164+
)
165+
166+
uvm_hotplug.api.hotplug.put(Vcpu={"add": vcpu_count})
167+
168+
time.sleep(1.5)
169+
# Extract API call duration
170+
api_duration = (
171+
float(
172+
re.findall(
173+
r"Total previous API call duration: (\d+) us\.",
174+
uvm_hotplug.log_data,
175+
)[-1]
176+
)
177+
/ 1000
178+
)
179+
try:
180+
timestamp = (
181+
float(
182+
re.findall(
183+
r"Guest-boot-time\s+\=\s+(\d+)\s+us", uvm_hotplug.log_data
184+
)[0]
185+
)
186+
/ 1000
187+
)
188+
except:
189+
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": None})
190+
continue
191+
# Extract onlining timings
192+
data.append({"vcpus": vcpu_count, "api": api_duration, "onlining": timestamp})
193+
194+
df = pandas.DataFrame.from_dict(data).to_csv(
195+
f"../test_results/manual-hotplug_{vcpu_count}.csv",
196+
index=False,
197+
float_format="%.3f",
198+
)

0 commit comments

Comments
 (0)