From 34e8e3dbf9c0ac0baa626d98d1093827a7c6102a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 5 Jun 2026 16:39:22 +0100 Subject: [PATCH] performance-metrics: Switch to QcowDisk Switch from QcowFile to QcowDisk taking advantage of QcowTempDisk where appropriate. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Rob Bradford --- block/Cargo.toml | 1 + block/src/formats/qcow/mod.rs | 19 ++++++---- performance-metrics/Cargo.toml | 2 +- performance-metrics/src/util.rs | 64 ++++++++++++++------------------- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/block/Cargo.toml b/block/Cargo.toml index 22ea206de..87bd1c355 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" [features] default = [] io_uring = ["dep:io-uring"] +test-utils = [] [dependencies] bitflags = { workspace = true } diff --git a/block/src/formats/qcow/mod.rs b/block/src/formats/qcow/mod.rs index f9a6942da..dad7d0238 100644 --- a/block/src/formats/qcow/mod.rs +++ b/block/src/formats/qcow/mod.rs @@ -12,27 +12,31 @@ pub mod internal; pub mod worker; use std::fs::File; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use std::io::Seek; use std::os::unix::io::AsRawFd; use std::sync::Arc; use std::{fmt, io}; +#[cfg(any(test, feature = "test-utils"))] use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use vmm_sys_util::tempfile::TempFile; use self::internal::backing::shared_backing_from; use self::internal::metadata::{BackingRead, QcowMetadata}; use self::internal::qcow_raw_file::QcowRawFile; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use self::internal::{BackingFileConfig, Error as QcowError, QcowHeader}; use self::internal::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; #[cfg(feature = "io_uring")] use self::worker::async_uring::QcowAsync; use self::worker::sync::QcowSync; -use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError, GuestMemoryTarget}; +#[cfg(any(test, feature = "test-utils"))] +use crate::async_io::GuestMemoryTarget; +use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError}; use crate::disk_file; +#[cfg(any(test, feature = "test-utils"))] use crate::disk_file::AsyncDiskFile; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; @@ -106,6 +110,7 @@ impl QcowDisk { } /// Synchronous write convenience for tests and benchmarks. + #[cfg(any(test, feature = "test-utils"))] pub fn write_all_at(&self, offset: u64, data: &[u8]) { let mut async_io = self.create_async_io(1).unwrap(); let mem = @@ -121,7 +126,7 @@ impl QcowDisk { } /// Writes a fresh qcow2 layout into `file` -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub(crate) fn create_image( file: &File, virtual_size: u64, @@ -152,13 +157,13 @@ pub(crate) fn create_image( } /// Helper struct to create a new qcow2 image in a temporary file. -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub struct QcowTempDisk { tmp: TempFile, disk: QcowDisk, } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl QcowTempDisk { /// Creates a new qcow2 image in a temporary file with optional /// backing file. Flags are passed to QcowDisk::new. diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index bb8f575fb..6a178eb9f 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -5,7 +5,7 @@ name = "performance-metrics" version = "0.1.0" [dependencies] -block = { path = "../block", features = ["io_uring"] } +block = { path = "../block", features = ["io_uring", "test-utils"] } clap = { workspace = true, features = ["wrap_help"] } libc = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index b4cafa300..66262d690 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -7,7 +7,7 @@ //! Shared benchmark helpers. use std::fs::File; -use std::io::{ErrorKind, Seek, SeekFrom, Write}; +use std::io::ErrorKind; use std::os::unix::fs::FileExt; use std::process::Command; use std::sync::Arc; @@ -15,8 +15,8 @@ use std::thread; use std::time::Duration; use block::async_io::{AsyncIo, GuestMemoryTarget}; -use block::formats::qcow::QcowDisk; -use block::formats::qcow::internal::{BackingFileConfig, ImageType, QcowFile, RawFile}; +use block::formats::qcow::internal::{BackingFileConfig, ImageType}; +use block::formats::qcow::{QcowDisk, QcowTempDisk}; use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::tempfile::TempFile; @@ -37,21 +37,18 @@ pub fn sized_tempfile(num_blocks: usize) -> TempFile { /// the tempfile handle. /// /// Each cluster is default QCOW2 cluster size of 64 KiB. The image is -/// created via `QcowFile::new` then populated with writes so that the -/// clusters are actually allocated in the L2 / refcount tables. +/// created via `QcowTempDisk::new` then populated with writes via the +/// synchronous AsyncIo backend so that the clusters are actually +/// allocated in the L2 / refcount tables. fn create_qcow_tempfile(num_clusters: usize) -> TempFile { - let tmp = TempFile::new().expect("failed to create tempfile"); let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; - let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); - let mut qcow = QcowFile::new(raw, 3, virtual_size, true).expect("failed to create QCOW2 file"); + let tmp_disk = QcowTempDisk::new(virtual_size, None, false, true, false) + .expect("failed to create QCOW2 file"); let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; - for i in 0..num_clusters { - qcow.seek(SeekFrom::Start(i as u64 * QCOW_CLUSTER_SIZE)) - .expect("seek failed"); - qcow.write_all(&buf).expect("write failed"); + for i in 0..num_clusters as u64 { + tmp_disk.disk().write_all_at(i * QCOW_CLUSTER_SIZE, &buf); } - qcow.flush().expect("flush failed"); - tmp + tmp_disk.into_tempfile() } /// Create a QCOW2 image with `num_clusters` allocated clusters opened @@ -196,11 +193,10 @@ pub fn drain_async_completions(async_io: &mut dyn AsyncIo, count: usize) { /// Create an empty QCOW2 image sized for `num_clusters` clusters. /// No data clusters are allocated. fn create_empty_qcow_tempfile(num_clusters: usize) -> TempFile { - let tmp = TempFile::new().expect("failed to create tempfile"); let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; - let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); - QcowFile::new(raw, 3, virtual_size, true).expect("failed to create qcow2 file"); - tmp + QcowTempDisk::new(virtual_size, None, false, true, false) + .expect("failed to create qcow2 file") + .into_tempfile() } /// Empty QCOW2 opened via QcowDisk with synchronous backend. @@ -241,16 +237,13 @@ fn create_overlay_tempfiles(num_clusters: usize) -> (TempFile, TempFile) { } } - let overlay = TempFile::new().expect("failed to create overlay tempfile"); - { - let raw = RawFile::new(overlay.as_file().try_clone().unwrap(), false); - let backing_config = BackingFileConfig { - path: backing.as_path().to_str().unwrap().to_string(), - format: Some(ImageType::Raw), - }; - QcowFile::new_from_backing(raw, 3, virtual_size, &backing_config, true) - .expect("failed to create overlay qcow2"); - } + let backing_config = BackingFileConfig { + path: backing.as_path().to_str().unwrap().to_string(), + format: Some(ImageType::Raw), + }; + let overlay = QcowTempDisk::new(virtual_size, Some(&backing_config), false, true, false) + .expect("failed to create overlay qcow2") + .into_tempfile(); (backing, overlay) } @@ -360,17 +353,14 @@ pub const L2_ENTRIES_PER_TABLE: usize = QCOW_CLUSTER_SIZE as usize / 8; /// spanning `num_l2_tables` L2 tables. fn create_sparse_qcow_tempfile(num_l2_tables: usize) -> TempFile { let virtual_size = QCOW_CLUSTER_SIZE * (num_l2_tables as u64 * L2_ENTRIES_PER_TABLE as u64); - let tmp = TempFile::new().expect("failed to create tempfile"); - let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); - let mut qcow = QcowFile::new(raw, 3, virtual_size, true).expect("failed to create qcow2 file"); + let tmp_disk = QcowTempDisk::new(virtual_size, None, false, true, false) + .expect("failed to create qcow2 file"); let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; - for i in 0..num_l2_tables { - let offset = i as u64 * L2_ENTRIES_PER_TABLE as u64 * QCOW_CLUSTER_SIZE; - qcow.seek(SeekFrom::Start(offset)).expect("seek failed"); - qcow.write_all(&buf).expect("write failed"); + for i in 0..num_l2_tables as u64 { + let offset = i * L2_ENTRIES_PER_TABLE as u64 * QCOW_CLUSTER_SIZE; + tmp_disk.disk().write_all_at(offset, &buf); } - qcow.flush().expect("flush failed"); - tmp + tmp_disk.into_tempfile() } /// Sparse QCOW2 opened via QcowDisk with synchronous backend.