From e802c0d8b975fb38145509cd0daae452938d6e0f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Mar 2026 14:17:55 +0100 Subject: [PATCH] performance-metrics: Add qcow2 fsync micro benchmark Add micro_bench_qcow_fsync which writes num_ops clusters into an empty qcow2 image to dirty L2 and refcount metadata then times a single fsync call that flushes all dirty tables to disk. This isolates the metadata flush cost which scales with the number of dirty L2 table entries and refcount blocks. Two TEST_LIST entries: micro_block_qcow_fsync_64_us and micro_block_qcow_fsync_256_us with 64 and 256 cluster workloads. Signed-off-by: Anatol Belski --- performance-metrics/src/main.rs | 26 ++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 30 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 93ab45e31..14ecfb12b 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 68] = [ +const TEST_LIST: [PerformanceTest; 70] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1325,6 +1325,30 @@ const TEST_LIST: [PerformanceTest; 68] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_fsync_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_fsync, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_fsync_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_fsync, + control: PerformanceTestControl { + test_timeout: 10, + 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 index 2ebde205f..30a737780 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -132,3 +132,33 @@ pub fn micro_bench_qcow_punch_hole(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Write num_ops clusters into an empty qcow2 image to dirty L2 and +/// refcount metadata, then time a single fsync that flushes all dirty +/// tables to disk. +/// +/// This isolates the metadata flush cost which scales with the number +/// of dirty L2 table entries and refcount blocks. +/// +/// Returns the fsync wall clock time in seconds. +pub fn micro_bench_qcow_fsync(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::empty_qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + // Write num_ops clusters to dirty L2 and refcount metadata. + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = write_iovec(&buf); + submit_writes(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + // Drain write completions. + drain_completions(async_io.as_mut(), num_ops); + + // Time the flush. + let start = Instant::now(); + async_io.fsync(Some(num_ops as u64)).expect("fsync failed"); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), 1); + + elapsed +}