From 743de82bd9e7ad6dc4736d700cd24f9c634cefb6 Mon Sep 17 00:00:00 2001 From: Marco Marangoni Date: Mon, 8 Jun 2026 14:26:32 +0000 Subject: [PATCH] feat: enable THP for guest memory This commit adds THP for the guest memory, with a new value for the huge_pages option. Signed-off-by: Marco Marangoni --- CHANGELOG.md | 6 +++ docs/hugepages.md | 51 ++++++++++++------- .../request/machine_configuration.rs | 1 + src/firecracker/swagger/firecracker.yaml | 7 ++- src/vmm/src/devices/virtio/vhost_user.rs | 1 + src/vmm/src/persist.rs | 13 +++-- src/vmm/src/resources.rs | 21 ++++++++ src/vmm/src/vmm_config/machine_config.rs | 23 +++++++-- src/vmm/src/vstate/memory.rs | 51 +++++++++++++++---- tests/framework/microvm.py | 5 +- .../functional/test_huge_pages.py | 47 +++++++++++++++++ .../integration_tests/functional/test_pmem.py | 25 +++++---- .../functional/test_shut_down.py | 9 +++- .../functional/test_snapshot_basic.py | 9 +++- .../performance/test_balloon.py | 4 +- .../performance/test_boottime.py | 10 ++++ .../performance/test_hotplug_memory.py | 14 ++++- 17 files changed, 244 insertions(+), 53 deletions(-) create mode 100644 tests/integration_tests/functional/test_huge_pages.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2a76e523d..cb86c1c9588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ and this project adheres to ### Added +- [#6003](https://github.com/firecracker-microvm/firecracker/pull/6003): Added a + new option `Transparent` for the `huge_pages` setting. If set, Firecracker + will use transparent huge pages for the guest memory via + `madvise(MADV_HUGEPAGE)`. Guest memory must be a multiple of 2MB when using + this option. + ### Changed ### Deprecated diff --git a/docs/hugepages.md b/docs/hugepages.md index a1c1a761953..4479e3cbb35 100644 --- a/docs/hugepages.md +++ b/docs/hugepages.md @@ -1,14 +1,37 @@ # Backing Guest Memory by Huge Pages -Firecracker supports backing the guest memory of a VM by 2MB hugetlbfs pages. -This can be enabled by setting the `huge_pages` field of `PUT` or `PATCH` -requests to the `/machine-config` endpoint to `2M`. - -Backing guest memory by huge pages can bring performance improvements for -specific workloads, due to less TLB contention and less overhead during -virtual->physical address resolution. It can also help reduce the number of -KVM_EXITS required to rebuild extended page tables post snapshot restore, as -well as improve boot times (by up to 50% as measured by Firecracker's +Firecracker supports three modes for the `huge_pages` field of `PUT` or `PATCH` +requests to the `/machine-config` endpoint: + +- `None` (default): Uses regular 4K pages with no huge page behavior. +- `Transparent`: Uses `madvise(MADV_HUGEPAGE)` to request transparent huge pages + for guest memory. Guest memory size must be a multiple of 2MB. +- `2M`: Backs guest memory by 2MB hugetlbfs pages. + +## Transparent Huge Pages (THP) + +Setting `huge_pages` to `Transparent` enables transparent huge pages for guest +memory via `madvise(MADV_HUGEPAGE)`. This allows the kernel to opportunistically +back guest memory with 2MB pages without requiring a pre-allocated hugetlbfs +pool. + +Limitations: + +- THP is only effective for anonymous memory (non-memfd). When vhost-user-blk + devices are in use, guest memory is memfd-backed and THP will not be applied. +- THP does not integrate with UFFD; no transparent huge pages will be allocated + during userfault-handling while resuming from a snapshot. + +Please refer to the [Linux Documentation][thp_docs] for more information. + +## Hugetlbfs (2M) + +Setting `huge_pages` to `2M` backs guest memory by 2MB hugetlbfs pages. This can +bring performance improvements for specific workloads, due to less TLB +contention and less overhead during virtual->physical address resolution. It can +also help reduce the number of KVM_EXITS required to rebuild extended page +tables post snapshot restore, as well as improve boot times (by up to 50% as +measured by Firecracker's [boot time performance tests](../tests/integration_tests/performance/test_boottime.py)) Using hugetlbfs requires the host running Firecracker to have a pre-allocated @@ -43,15 +66,5 @@ the device is unable to reclaim the hugepage backing of the guest and drop RSS. However, the balloon can still be inflated and used to restrict memory usage in the guest. -## FAQ - -### Why does Firecracker not offer a transparent huge pages (THP) setting? - -Firecracker's guest memory can be memfd based. Linux (as of 6.1) does not offer -a way to dynamically enable THP for such memory regions. Additionally, UFFD does -not integrate with THP (no transparent huge pages will be allocated during -userfaulting). Please refer to the [Linux Documentation][thp_docs] for more -information. - [hugetlbfs_docs]: https://docs.kernel.org/admin-guide/mm/hugetlbpage.html [thp_docs]: https://www.kernel.org/doc/html/next/admin-guide/mm/transhuge.html#hugepages-in-tmpfs-shmem diff --git a/src/firecracker/src/api_server/request/machine_configuration.rs b/src/firecracker/src/api_server/request/machine_configuration.rs index 2e8addffb74..a12326d3a8d 100644 --- a/src/firecracker/src/api_server/request/machine_configuration.rs +++ b/src/firecracker/src/api_server/request/machine_configuration.rs @@ -104,6 +104,7 @@ mod tests { let huge_pages_cases = [ ("None", HugePageConfig::None), + ("Transparent", HugePageConfig::Transparent), ("2M", HugePageConfig::Hugetlbfs2M), ]; diff --git a/src/firecracker/swagger/firecracker.yaml b/src/firecracker/swagger/firecracker.yaml index d1ac91bdb6a..edbff22f97a 100644 --- a/src/firecracker/swagger/firecracker.yaml +++ b/src/firecracker/swagger/firecracker.yaml @@ -1442,8 +1442,13 @@ definitions: type: string enum: - None + - Transparent - 2M - description: Which huge pages configuration (if any) should be used to back guest memory. + default: None + description: >- + Which huge pages configuration should be used to back guest memory. + "None" uses regular 4K pages. "Transparent" enables THP via + madvise(MADV_HUGEPAGE). "2M" uses explicit hugetlbfs 2MB pages. MemoryBackend: type: object diff --git a/src/vmm/src/devices/virtio/vhost_user.rs b/src/vmm/src/devices/virtio/vhost_user.rs index 831595f0940..b0a95a1bdf3 100644 --- a/src/vmm/src/devices/virtio/vhost_user.rs +++ b/src/vmm/src/devices/virtio/vhost_user.rs @@ -487,6 +487,7 @@ pub(crate) mod tests { libc::MAP_PRIVATE, Some(file), false, + libc::MADV_HUGEPAGE, ) .unwrap() .into_iter() diff --git a/src/vmm/src/persist.rs b/src/vmm/src/persist.rs index 9112eef66cb..f522b276534 100644 --- a/src/vmm/src/persist.rs +++ b/src/vmm/src/persist.rs @@ -449,8 +449,13 @@ pub fn restore_from_snapshot( .into()); } ( - guest_memory_from_file(mem_backend_path, mem_state, track_dirty_pages) - .map_err(RestoreFromSnapshotGuestMemoryError::File)?, + guest_memory_from_file( + mem_backend_path, + mem_state, + track_dirty_pages, + vm_resources.machine_config.huge_pages, + ) + .map_err(RestoreFromSnapshotGuestMemoryError::File)?, None, ) } @@ -512,9 +517,11 @@ fn guest_memory_from_file( mem_file_path: &Path, mem_state: &GuestMemoryState, track_dirty_pages: bool, + huge_pages: HugePageConfig, ) -> Result, GuestMemoryFromFileError> { let mem_file = File::open(mem_file_path)?; - let guest_mem = memory::snapshot_file(mem_file, mem_state.regions(), track_dirty_pages)?; + let guest_mem = + memory::snapshot_file(mem_file, mem_state.regions(), track_dirty_pages, huge_pages)?; Ok(guest_mem) } diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index 632db13f259..dc6c43f57a4 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -580,6 +580,7 @@ mod tests { use crate::vmm_config::RateLimiterConfig; use crate::vmm_config::boot_source::{BootConfig, BootSource, BootSourceConfig}; use crate::vmm_config::drive::{BlockBuilder, BlockDeviceConfig}; + use crate::vmm_config::machine_config::HugePageConfig::{Hugetlbfs2M, Transparent}; use crate::vmm_config::machine_config::{HugePageConfig, MachineConfig, MachineConfigError}; use crate::vmm_config::net::{NetBuilder, NetworkInterfaceConfig}; use crate::vmm_config::vsock::tests::default_config; @@ -1476,6 +1477,26 @@ mod tests { Err(MachineConfigError::InvalidMemorySize) ); + // Odd memory size - not supported by THP/Hugetlbfs + aux_vm_config.mem_size_mib = Some(1025); + aux_vm_config.huge_pages = Some(Transparent); + assert_eq!( + vm_resources.update_machine_config(&aux_vm_config), + Err(MachineConfigError::InvalidMemorySize) + ); + aux_vm_config.huge_pages = Some(Hugetlbfs2M); + assert_eq!( + vm_resources.update_machine_config(&aux_vm_config), + Err(MachineConfigError::InvalidMemorySize) + ); + // Odd size supported by HugePageConfig::None + aux_vm_config.huge_pages = Some(HugePageConfig::None); + vm_resources.update_machine_config(&aux_vm_config).unwrap(); + assert_eq!( + MachineConfigUpdate::from(vm_resources.machine_config.clone()), + aux_vm_config + ); + // Incompatible mem_size_mib with balloon size. vm_resources.machine_config.mem_size_mib = 128; vm_resources diff --git a/src/vmm/src/vmm_config/machine_config.rs b/src/vmm/src/vmm_config/machine_config.rs index a975f5217ad..02ac1d9981f 100644 --- a/src/vmm/src/vmm_config/machine_config.rs +++ b/src/vmm/src/vmm_config/machine_config.rs @@ -34,9 +34,11 @@ pub enum MachineConfigError { /// Describes the possible (huge)page configurations for a microVM's memory. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum HugePageConfig { - /// Do not use hugepages, e.g. back guest memory by 4K + /// Back guest memory by 4K pages, no hugepage behavior #[default] None, + /// Use madvise(MADV_HUGEPAGE) for transparent huge pages + Transparent, /// Back guest memory by 2MB hugetlbfs pages #[serde(rename = "2M")] Hugetlbfs2M, @@ -49,6 +51,10 @@ impl HugePageConfig { let divisor = match self { // Any integer memory size expressed in MiB will be a multiple of 4096KiB. HugePageConfig::None => 1, + // Note: THP technically supports memory not 2MB aligned, however that would mean + // some pages at the tail would be forced to be 4k size. To avoid performance/fragmentation surprises, + // having a memory multiple of 2MB is wiser. + HugePageConfig::Transparent => 2, HugePageConfig::Hugetlbfs2M => 2, }; @@ -59,11 +65,20 @@ impl HugePageConfig { /// create a mapping backed by huge pages as described by this [`HugePageConfig`]. pub fn mmap_flags(&self) -> libc::c_int { match self { - HugePageConfig::None => 0, + HugePageConfig::None | HugePageConfig::Transparent => 0, HugePageConfig::Hugetlbfs2M => libc::MAP_HUGETLB | libc::MAP_HUGE_2MB, } } + /// Returns the flags required to pass to [libc::madvise], after allocating anonymous guest memory. + /// Note: returning [libc::MADV_NORMAL] might skip the call to `madvise` entirely. + pub fn madvise_flags(&self) -> libc::c_int { + match self { + HugePageConfig::Transparent => libc::MADV_HUGEPAGE, + HugePageConfig::None | HugePageConfig::Hugetlbfs2M => libc::MADV_NORMAL, + } + } + /// Returns `true` iff this [`HugePageConfig`] describes a hugetlbfs-based configuration. pub fn is_hugetlbfs(&self) -> bool { matches!(self, HugePageConfig::Hugetlbfs2M) @@ -72,7 +87,7 @@ impl HugePageConfig { /// Gets the page size in bytes of this [`HugePageConfig`]. pub fn page_size(&self) -> usize { match self { - HugePageConfig::None => 4096, + HugePageConfig::None | HugePageConfig::Transparent => 4096, HugePageConfig::Hugetlbfs2M => 2 * 1024 * 1024, } } @@ -81,7 +96,7 @@ impl HugePageConfig { impl From for Option { fn from(value: HugePageConfig) -> Self { match value { - HugePageConfig::None => None, + HugePageConfig::None | HugePageConfig::Transparent => None, HugePageConfig::Hugetlbfs2M => Some(memfd::HugetlbSize::Huge2MB), } } diff --git a/src/vmm/src/vstate/memory.rs b/src/vmm/src/vstate/memory.rs index 13057c171d6..8c69e7ca9e9 100644 --- a/src/vmm/src/vstate/memory.rs +++ b/src/vmm/src/vstate/memory.rs @@ -6,6 +6,7 @@ // found in the THIRD-PARTY file. use std::fs::File; +use std::io; use std::io::SeekFrom; use std::ops::Deref; use std::sync::{Arc, Mutex}; @@ -22,12 +23,12 @@ pub use vm_memory::{ }; use vm_memory::{GuestMemoryError, GuestMemoryRegionBytes, VolatileSlice, WriteVolatile}; -use crate::DirtyBitmap; use crate::arch::host_page_size; use crate::logger::error; use crate::utils::u64_to_usize; use crate::vmm_config::machine_config::HugePageConfig; use crate::vstate::vm::{KvmVm, VmError}; +use crate::{DirtyBitmap, warn_unrestricted}; /// Type of GuestRegionMmap. pub type GuestRegionMmap = vm_memory::GuestRegionMmap>; @@ -528,6 +529,7 @@ pub fn create( mmap_flags: libc::c_int, file: Option, track_dirty_pages: bool, + madvise_flags: libc::c_int, ) -> Result, MemoryError> { let mut offset = 0; let file = file.map(Arc::new); @@ -559,6 +561,18 @@ pub fn create( start, ) .ok_or(MemoryError::VmMemoryError) + .inspect(|region| { + if madvise_flags != libc::MADV_NORMAL { + // SAFETY: The referenced memory was just mapped. + let ret = unsafe { + libc::madvise(region.as_ptr().cast(), region.size(), madvise_flags) + }; + if ret != 0 { + let e = io::Error::last_os_error(); + warn_unrestricted!("Madvise call failed for guest memory: {e}"); + } + } + }) }) .collect::, _>>() } @@ -577,6 +591,7 @@ pub fn memfd_backed( libc::MAP_SHARED | huge_pages.mmap_flags(), Some(memfd_file), track_dirty_pages, + huge_pages.madvise_flags(), ) } @@ -591,6 +606,7 @@ pub fn anonymous( libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | huge_pages.mmap_flags(), None, track_dirty_pages, + huge_pages.madvise_flags(), ) } @@ -600,6 +616,7 @@ pub fn snapshot_file( file: File, regions: impl Iterator, track_dirty_pages: bool, + huge_pages: HugePageConfig, ) -> Result, MemoryError> { let regions: Vec<_> = regions.collect(); let memory_size = regions @@ -619,6 +636,7 @@ pub fn snapshot_file( libc::MAP_PRIVATE, Some(file), track_dirty_pages, + huge_pages.madvise_flags(), ) } @@ -951,8 +969,13 @@ mod tests { file.write_all(&vec![0x42u8; page_size]).unwrap(); let regions = vec![(GuestAddress(0), page_size)]; - let guest_regions = - snapshot_file(file, regions.into_iter(), dirty_page_tracking).unwrap(); + let guest_regions = snapshot_file( + file, + regions.into_iter(), + dirty_page_tracking, + HugePageConfig::None, + ) + .unwrap(); assert_eq!(guest_regions.len(), 1); guest_regions.iter().for_each(|region| { assert_eq!(region.bitmap().is_some(), dirty_page_tracking); @@ -973,7 +996,8 @@ mod tests { (GuestAddress(0x10000), page_size), (GuestAddress(0x20000), page_size), ]; - let guest_regions = snapshot_file(file, regions.into_iter(), false).unwrap(); + let guest_regions = + snapshot_file(file, regions.into_iter(), false, HugePageConfig::None).unwrap(); assert_eq!(guest_regions.len(), 3); } @@ -985,7 +1009,7 @@ mod tests { file.write_all(&vec![0x42u8; page_size]).unwrap(); let regions = vec![(GuestAddress(0), 2 * page_size)]; - let result = snapshot_file(file, regions.into_iter(), false); + let result = snapshot_file(file, regions.into_iter(), false, HugePageConfig::None); assert!(matches!(result.unwrap_err(), MemoryError::OffsetTooLarge)); } @@ -1175,8 +1199,15 @@ mod tests { let mut memory_file = TempFile::new().unwrap().into_file(); guest_memory.dump(&mut memory_file).unwrap(); - let restored_guest_memory = - into_region_ext(snapshot_file(memory_file, memory_state.regions(), false).unwrap()); + let restored_guest_memory = into_region_ext( + snapshot_file( + memory_file, + memory_state.regions(), + false, + HugePageConfig::None, + ) + .unwrap(), + ); // Check that the region contents are the same. let mut restored_region = vec![0u8; page_size * 2]; @@ -1240,8 +1271,9 @@ mod tests { .unwrap(); // We can restore from this because this is the first dirty dump. - let restored_guest_memory = - into_region_ext(snapshot_file(file, memory_state.regions(), false).unwrap()); + let restored_guest_memory = into_region_ext( + snapshot_file(file, memory_state.regions(), false, HugePageConfig::None).unwrap(), + ); // Check that the region contents are the same. let mut restored_region = vec![0u8; region_size]; @@ -1465,6 +1497,7 @@ mod tests { memory_file, std::iter::once((GuestAddress(0), 2 * page_size)), false, + HugePageConfig::None, ) .unwrap(), ); diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index 8339ae1695e..1c6de9f2384 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -187,6 +187,7 @@ class HugePagesConfig(str, Enum): """Enum describing the huge pages configurations supported Firecracker""" NONE = "None" + TRANSPARENT = "Transparent" HUGETLBFS_2MB = "2M" @@ -274,6 +275,7 @@ def __init__( self.iface = {} self.disks = {} self.disks_vhost_user = {} + self.huge_pages = HugePagesConfig.NONE self.vcpus_count = None self.mem_size_bytes = None self.cpu_template_name = "None" @@ -816,7 +818,7 @@ def basic_config( boot_args: str = None, use_initrd: bool = False, track_dirty_pages: bool = False, - huge_pages: HugePagesConfig = None, + huge_pages: HugePagesConfig = HugePagesConfig.NONE, rootfs_io_engine=None, cpu_template: Optional[str] = None, enable_entropy_device=False, @@ -844,6 +846,7 @@ def basic_config( track_dirty_pages=track_dirty_pages, huge_pages=huge_pages, ) + self.huge_pages = huge_pages self.vcpus_count = vcpu_count self.mem_size_bytes = mem_size_mib * 2**20 diff --git a/tests/integration_tests/functional/test_huge_pages.py b/tests/integration_tests/functional/test_huge_pages.py new file mode 100644 index 00000000000..0803bf6cfc5 --- /dev/null +++ b/tests/integration_tests/functional/test_huge_pages.py @@ -0,0 +1,47 @@ +# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Tests that transparent huge pages are used (or not) based on configuration.""" + +import pytest + +from framework import utils +from framework.microvm import HugePagesConfig + + +def get_anon_huge_pages_kb(pid: int) -> int: + """Get total AnonHugePages in kB for a process from /proc//smaps.""" + cmd = f"awk '/AnonHugePages/{{sum += $2}} END{{print sum}}' /proc/{pid}/smaps" + _, stdout, _ = utils.check_output(cmd) + return int(stdout.strip()) + + +@pytest.mark.parametrize( + "huge_pages", [HugePagesConfig.NONE, HugePagesConfig.TRANSPARENT] +) +def test_transparent_huge_pages_allocation(uvm, huge_pages): + """ + Test that allocating memory in the guest causes transparent huge pages + to appear on the host when configured, and not when disabled. + """ + vm = uvm + vm.spawn() + vm.basic_config(vcpu_count=2, mem_size_mib=256, huge_pages=huge_pages) + vm.add_net_iface() + vm.start() + + # Allocate and touch anonymous memory inside the guest to trigger host-side + # page faults on the guest memory region (which is what THP backs). + vm.ssh.check_output("python3 -c 'x = bytearray(128 * 1024 * 1024)'") + + anon_huge_kb = get_anon_huge_pages_kb(vm.firecracker_pid) + + if huge_pages == HugePagesConfig.TRANSPARENT: + # With THP enabled, the kernel should have promoted some pages (let's say 100 MB out of 128MB) + assert ( + anon_huge_kb > 64 * 1024 + ), f"Expected AnonHugePages > 0 with Transparent huge pages, got {anon_huge_kb} kB" + else: + # With huge pages disabled, no anonymous huge pages should be present + assert ( + anon_huge_kb == 0 + ), f"Expected AnonHugePages == 0 with huge pages None, got {anon_huge_kb} kB" diff --git a/tests/integration_tests/functional/test_pmem.py b/tests/integration_tests/functional/test_pmem.py index 51cd9548c5f..db537d8029f 100644 --- a/tests/integration_tests/functional/test_pmem.py +++ b/tests/integration_tests/functional/test_pmem.py @@ -11,6 +11,11 @@ import host_tools.drive as drive_tools from framework import utils from framework.artifacts import ACPI_GUEST_KERNELS, pin_guest_kernel, pin_rootfs_mode +from framework.microvm import HugePagesConfig + +pytestmark = pytest.mark.parametrize( + "huge_pages", [HugePagesConfig.NONE, HugePagesConfig.TRANSPARENT] +) ALIGNMENT = 2 << 20 @@ -50,13 +55,13 @@ def check_pmem_exist(vm, index, root, read_only, size, extension): assert False -def test_pmem_zero_size_backing_file(uvm): +def test_pmem_zero_size_backing_file(uvm, huge_pages): """ Test that a pmem device with a zero-sized backing file fails at boot time. """ vm = uvm vm.spawn() - vm.basic_config(add_root_device=True) + vm.basic_config(add_root_device=True, huge_pages=huge_pages) zero_size_path = os.path.join(vm.fsfiles, "zero_scratch") utils.check_output(f"touch {zero_size_path}") @@ -69,7 +74,7 @@ def test_pmem_zero_size_backing_file(uvm): vm.start() -def test_pmem_add(uvm, microvm_factory): +def test_pmem_add(uvm, huge_pages, microvm_factory): """ Test addition of pmem devices to the VM and writes persistance @@ -77,7 +82,7 @@ def test_pmem_add(uvm, microvm_factory): vm = uvm vm.spawn() - vm.basic_config(add_root_device=True) + vm.basic_config(add_root_device=True, huge_pages=huge_pages) vm.add_net_iface() # Pmem should work with non 2MB aligned files as well @@ -121,7 +126,7 @@ def test_pmem_add(uvm, microvm_factory): @pin_rootfs_mode("rw") -def test_pmem_add_as_root_rw(uvm, rootfs, microvm_factory): +def test_pmem_add_as_root_rw(uvm, huge_pages, rootfs, microvm_factory): """ Test addition of a single root pmem device in read-write mode """ @@ -130,7 +135,7 @@ def test_pmem_add_as_root_rw(uvm, rootfs, microvm_factory): vm.memory_monitor = None vm.monitors = [] vm.spawn() - vm.basic_config(add_root_device=False) + vm.basic_config(add_root_device=False, huge_pages=huge_pages) vm.add_net_iface() rootfs_size = os.path.getsize(rootfs) @@ -144,7 +149,7 @@ def test_pmem_add_as_root_rw(uvm, rootfs, microvm_factory): check_pmem_exist(restored_vm, 0, True, False, align(rootfs_size), "ext4") -def test_pmem_add_as_root_ro(uvm, rootfs, microvm_factory): +def test_pmem_add_as_root_ro(uvm, huge_pages, rootfs, microvm_factory): """ Test addition of a single root pmem device in read-only mode """ @@ -153,7 +158,7 @@ def test_pmem_add_as_root_ro(uvm, rootfs, microvm_factory): vm.memory_monitor = None vm.monitors = [] vm.spawn() - vm.basic_config(add_root_device=False) + vm.basic_config(add_root_device=False, huge_pages=huge_pages) vm.add_net_iface() rootfs_size = os.path.getsize(rootfs) @@ -188,6 +193,7 @@ def test_pmem_dax_memory_saving( microvm_factory, guest_kernel, rootfs, + huge_pages, ): """ Test that booting from pmem with DAX enabled indeed saves memory in the @@ -197,7 +203,7 @@ def test_pmem_dax_memory_saving( # Boot from a block device vm = microvm_factory.build(guest_kernel, rootfs, pci=True, monitor_memory=False) vm.spawn() - vm.basic_config() + vm.basic_config(huge_pages=huge_pages) vm.add_net_iface() vm.start() block_cache_usage = inside_buff_cache(vm) @@ -211,6 +217,7 @@ def test_pmem_dax_memory_saving( vm_pmem.basic_config( add_root_device=False, boot_args="reboot=k panic=1 nomodule swiotlb=noforce console=ttyS0 rootflags=dax", + huge_pages=huge_pages, ) vm_pmem.add_net_iface() vm_pmem.add_pmem("pmem", rootfs, True, False) diff --git a/tests/integration_tests/functional/test_shut_down.py b/tests/integration_tests/functional/test_shut_down.py index 1c36716fb59..a5620c0c123 100644 --- a/tests/integration_tests/functional/test_shut_down.py +++ b/tests/integration_tests/functional/test_shut_down.py @@ -4,12 +4,17 @@ import platform +import pytest from packaging import version from framework import utils +from framework.microvm import HugePagesConfig -def test_reboot(uvm): +@pytest.mark.parametrize( + "huge_pages", [HugePagesConfig.NONE, HugePagesConfig.TRANSPARENT] +) +def test_reboot(uvm, huge_pages): """ Test reboot from guest. """ @@ -23,7 +28,7 @@ def test_reboot(uvm): # Set up the microVM with 4 vCPUs, 256 MiB of RAM, 0 network ifaces, and # a root file system with the rw permission. The network interfaces is # added after we get a unique MAC and IP. - vm.basic_config(vcpu_count=4) + vm.basic_config(vcpu_count=4, huge_pages=huge_pages) vm.add_net_iface() vm.start() diff --git a/tests/integration_tests/functional/test_snapshot_basic.py b/tests/integration_tests/functional/test_snapshot_basic.py index 7cecc80509b..dd5efe61d68 100644 --- a/tests/integration_tests/functional/test_snapshot_basic.py +++ b/tests/integration_tests/functional/test_snapshot_basic.py @@ -20,6 +20,7 @@ import host_tools.network as net_tools from framework import utils from framework.artifacts import GUEST_KERNEL_DEFAULT, pin_guest_kernel, pin_rootfs_mode +from framework.microvm import HugePagesConfig from framework.properties import global_props from framework.utils import check_filesystem, check_output from framework.utils_cpu_templates import ALL_CPU_TEMPLATES, pin_cpu_template @@ -60,7 +61,12 @@ def _get_guest_drive_size(ssh_connection, guest_dev_name="/dev/vdb"): @pin_guest_kernel(GUEST_KERNEL_DEFAULT) @pytest.mark.parametrize("resume_at_restore", [True, False]) -def test_resume(uvm_configured, microvm_factory, resume_at_restore): +@pytest.mark.parametrize( + "huge_pages", + [HugePagesConfig.NONE, HugePagesConfig.TRANSPARENT], + indirect=True, +) +def test_resume(uvm_configured, microvm_factory, resume_at_restore, huge_pages): """Tests snapshot is resumable at or after restoration. Check that a restored microVM is resumable by either @@ -68,6 +74,7 @@ def test_resume(uvm_configured, microvm_factory, resume_at_restore): b. PUT /snapshot/load with `resume_vm=True` """ vm = uvm_configured + assert vm.huge_pages == huge_pages vm.add_net_iface() vm.start() snapshot = vm.snapshot_full() diff --git a/tests/integration_tests/performance/test_balloon.py b/tests/integration_tests/performance/test_balloon.py index b9157468a88..2aa22da4229 100644 --- a/tests/integration_tests/performance/test_balloon.py +++ b/tests/integration_tests/performance/test_balloon.py @@ -16,10 +16,10 @@ track_cpu_utilization, ) -# Every test in this module exercises both huge_pages variants. +# Every test in this module exercises all huge_pages variants. pytestmark = pytest.mark.parametrize( "huge_pages", - [HugePagesConfig.NONE, HugePagesConfig.HUGETLBFS_2MB], + HugePagesConfig, indirect=True, ) diff --git a/tests/integration_tests/performance/test_boottime.py b/tests/integration_tests/performance/test_boottime.py index ce02baf873c..c8c855f1e7a 100644 --- a/tests/integration_tests/performance/test_boottime.py +++ b/tests/integration_tests/performance/test_boottime.py @@ -9,6 +9,7 @@ import pytest from framework.artifacts import ACPI_GUEST_KERNELS, pin_guest_kernel, pin_rootfs_mode +from framework.microvm import HugePagesConfig # Regex for obtaining boot time from some string. @@ -106,6 +107,7 @@ def launch_vm_with_boot_timer( mem_size_mib, pci_enabled, boot_from_pmem, + huge_pages=HugePagesConfig.NONE, ): """Launches a microVM with guest-timer and returns the reported metrics for it""" vm = microvm_factory.build( @@ -119,6 +121,7 @@ def launch_vm_with_boot_timer( mem_size_mib=mem_size_mib, boot_args=DEFAULT_BOOT_ARGS + " init=/usr/local/bin/init", enable_entropy_device=True, + huge_pages=huge_pages, ) else: vm.basic_config( @@ -127,6 +130,7 @@ def launch_vm_with_boot_timer( mem_size_mib=mem_size_mib, boot_args=DEFAULT_BOOT_ARGS + " init=/usr/local/bin/init rootflags=dax", enable_entropy_device=True, + huge_pages=huge_pages, ) vm.add_pmem("pmem", rootfs, True, True) @@ -152,6 +156,9 @@ def test_boot_timer(microvm_factory, guest_kernel, rootfs, pci_enabled): ) @pin_rootfs_mode("rw") @pytest.mark.parametrize("boot_from_pmem", [True, False], ids=["PmemBoot", "BlockBoot"]) +@pytest.mark.parametrize( + "huge_pages", [HugePagesConfig.NONE, HugePagesConfig.TRANSPARENT], indirect=True +) @pytest.mark.nonci def test_boottime( microvm_factory, @@ -161,6 +168,7 @@ def test_boottime( mem_size_mib, boot_from_pmem, pci_enabled, + huge_pages, metrics, ): """Test boot time with different guest configurations""" @@ -174,6 +182,7 @@ def test_boottime( mem_size_mib, pci_enabled, boot_from_pmem, + huge_pages, ) if i == 0: @@ -181,6 +190,7 @@ def test_boottime( { "performance_test": "test_boottime", "boot_from_pmem": str(boot_from_pmem), + "huge_pages": str(huge_pages), **vm.dimensions, } ) diff --git a/tests/integration_tests/performance/test_hotplug_memory.py b/tests/integration_tests/performance/test_hotplug_memory.py index d754e11dc69..f22effeb89b 100644 --- a/tests/integration_tests/performance/test_hotplug_memory.py +++ b/tests/integration_tests/performance/test_hotplug_memory.py @@ -80,7 +80,7 @@ def uvm_resumed_memhp( """Restores a VM with the given memory hotplugging config after booting and snapshotting""" if vhost_user: pytest.skip("vhost-user doesn't support snapshot/restore") - if huge_pages and huge_pages != HugePagesConfig.NONE and not uffd_handler: + if huge_pages == HugePagesConfig.HUGETLBFS_2MB and not uffd_handler: pytest.skip("Hugepages requires a UFFD handler") uvm = uvm_booted_memhp( uvm, @@ -102,6 +102,7 @@ def uvm_resumed_memhp( @pytest.fixture( params=[ (uvm_booted_memhp, False, HugePagesConfig.NONE, None, None), + (uvm_booted_memhp, False, HugePagesConfig.TRANSPARENT, None, None), (uvm_booted_memhp, False, HugePagesConfig.HUGETLBFS_2MB, None, None), (uvm_booted_memhp, True, HugePagesConfig.NONE, None, None), (uvm_resumed_memhp, False, HugePagesConfig.NONE, None, SnapshotType.FULL), @@ -113,6 +114,13 @@ def uvm_resumed_memhp( None, SnapshotType.DIFF_MINCORE, ), + ( + uvm_resumed_memhp, + False, + HugePagesConfig.TRANSPARENT, + None, + SnapshotType.FULL, + ), ( uvm_resumed_memhp, False, @@ -130,11 +138,13 @@ def uvm_resumed_memhp( ], ids=[ "booted", + "booted-thp", "booted-huge-pages", "booted-vhost-user", "resumed", "resumed-diff", "resumed-mincore", + "resumed-thp", "resumed-uffd", "resumed-uffd-huge-pages", ], @@ -469,7 +479,7 @@ def timed_memory_hotplug(uvm, size, metrics, metric_prefix, fc_metric_name): ) @pytest.mark.parametrize( "huge_pages", - [HugePagesConfig.NONE, HugePagesConfig.HUGETLBFS_2MB], + HugePagesConfig, ) def test_memory_hotplug_latency( microvm_factory, guest_kernel, rootfs, hotplug_size, huge_pages, metrics