From 00957fa9dbe7004eafefecb805e1e775e5aad36f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 15:28:29 +0100 Subject: [PATCH] performance-metrics: Add AIO completion drain micro benchmark Add micro_block_raw_aio_drain_128_us and micro_block_raw_aio_drain_256_us tests that submit N AIO writes to a temporary file, wait for the eventfd signal, then time how long it takes to drain all completions via next_completed_request(). This measures per completion syscall overhead and provides a baseline before any batching optimizations. Signed-off-by: Anatol Belski --- Cargo.lock | 2 + performance-metrics/Cargo.toml | 2 + performance-metrics/src/main.rs | 31 +++++++++++- performance-metrics/src/micro_bench_block.rs | 53 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 performance-metrics/src/micro_bench_block.rs diff --git a/Cargo.lock b/Cargo.lock index af88d4db9..7fa87d651 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1596,8 +1596,10 @@ dependencies = [ name = "performance-metrics" version = "0.1.0" dependencies = [ + "block", "clap", "dirs", + "libc", "serde", "serde_json", "test_infra", diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 60be8f45a..516ea9e0a 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -5,8 +5,10 @@ name = "performance-metrics" version = "0.1.0" [dependencies] +block = { path = "../block" } clap = { workspace = true, features = ["wrap_help"] } dirs = { workspace = true } +libc = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index bcfee6d97..c648d189c 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -4,6 +4,7 @@ // // Custom harness to run performance tests +mod micro_bench_block; mod performance_tests; mod util; @@ -336,6 +337,10 @@ mod adjuster { v * 1000.0 } + pub fn s_to_us(v: f64) -> f64 { + v * 1_000_000.0 + } + pub fn bps_to_gbps(v: f64) -> f64 { v / (1_000_000_000_f64) } @@ -346,7 +351,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 60] = [ +const TEST_LIST: [PerformanceTest; 62] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1197,6 +1202,30 @@ const TEST_LIST: [PerformanceTest; 60] = [ }, unit_adjuster: adjuster::Bps_to_MiBps, }, + PerformanceTest { + name: "micro_block_raw_aio_drain_128_us", + func_ptr: micro_bench_block::micro_bench_aio_drain, + control: PerformanceTestControl { + test_timeout: 5, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_raw_aio_drain_256_us", + func_ptr: micro_bench_block::micro_bench_aio_drain, + control: PerformanceTestControl { + test_timeout: 5, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs new file mode 100644 index 000000000..6dc51af65 --- /dev/null +++ b/performance-metrics/src/micro_bench_block.rs @@ -0,0 +1,53 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//! In process micro benchmarks for block layer internals. +//! +//! These run without booting a VM and measure hot path operations +//! (e.g. AIO completion draining) at the syscall level. + +use std::os::unix::io::AsRawFd; +use std::time::Instant; + +use block::async_io::AsyncIo; +use block::raw_async_aio::RawFileAsyncAio; + +use crate::PerformanceTestControl; +use crate::util::{self, BLOCK_SIZE}; + +/// Submit num_ops AIO writes, wait for them all to land, then time +/// how long it takes to drain every completion via next_completed_request(). +/// +/// Returns the drain wall clock time in seconds. +pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let tmp = util::sized_tempfile(num_ops); + let fd = tmp.as_file().as_raw_fd(); + let mut aio = RawFileAsyncAio::new(fd, num_ops as u32).expect("failed to create AIO context"); + + let buf = vec![0xA5u8; BLOCK_SIZE as usize]; + + // Submit all writes. + for i in 0..num_ops { + let iovec = libc::iovec { + iov_base: buf.as_ptr() as *mut _, + iov_len: buf.len(), + }; + aio.write_vectored((i as u64 * BLOCK_SIZE) as libc::off_t, &[iovec], i as u64) + .expect("write_vectored failed"); + } + + // Wait until the eventfd signals that completions are available. + util::wait_for_eventfd(aio.notifier()); + + // Drain all completions and measure. + let start = Instant::now(); + let mut drained = 0usize; + while drained < num_ops { + if aio.next_completed_request().is_some() { + drained += 1; + } + } + start.elapsed().as_secs_f64() +}