feat(cli): move multi-file summary policy to Rust

The multi-file compression and decompression entry points already delegate
file iteration and resource-sensitive work to Rust, but each still kept the
final summary decision and progress-to-summary ordering in C. Move that
scalar policy into a Rust ABI function. The C side now supplies only exact
legacy display callbacks and unchanged format strings, while Rust receives
plain counters and a summary kind without exposing FIO, cRess, dRess, or
codec layouts. Focused unit tests cover both summary modes, callback order,
argument forwarding, and sessions that must not display a summary.

Test Plan:
- `git diff --check -- programs/fileio.c rust/src/fileio_asyncio.rs` -- passed
- `git diff --cached --check` -- passed
- `rustfmt --check --edition 2021 rust/src/fileio_asyncio.rs` -- parsed the file;
  reports pre-existing formatting differences elsewhere in the file, so no
  unrelated formatting was applied
- Cargo, make, and runtime tests were intentionally not run per worker scope
This commit is contained in:
2026-07-21 20:37:11 +02:00
parent f154f511dd
commit cc43ebac4e
2 changed files with 274 additions and 23 deletions
+180
View File
@@ -1598,6 +1598,73 @@ struct FIO_rust_compression_context_t {
totalBytesOutput: usize,
}
pub const FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION: c_int = 0;
pub const FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION: c_int = 1;
type FIO_rust_multiple_summary_progress_fn = unsafe extern "C" fn(*mut c_void);
type FIO_rust_compress_multiple_summary_fn =
unsafe extern "C" fn(*mut c_void, c_int, u64, u64);
type FIO_rust_decompress_multiple_summary_fn = unsafe extern "C" fn(*mut c_void, c_int, u64);
/// C supplies only display sinks. The multi-file counters are passed as
/// scalars so the summary policy never depends on private FIO/cRess/dRess
/// layouts or external codec state.
#[repr(C)]
pub struct FIO_rust_multiple_summary_callbacks_t {
pub opaque: *mut c_void,
pub display_progress: Option<FIO_rust_multiple_summary_progress_fn>,
pub display_compression: Option<FIO_rust_compress_multiple_summary_fn>,
pub display_decompression: Option<FIO_rust_decompress_multiple_summary_fn>,
}
/// Runs the final multi-file summary in the same position and order as the
/// original C callers: clear progress first, then select the compression or
/// decompression summary sink. C retains only the exact display formatting
/// callbacks, preserving diagnostics and the CLI ABI.
#[no_mangle]
pub unsafe extern "C" fn FIO_rust_displayMultipleFileSummary(
nb_files_total: c_int,
nb_files_processed: c_int,
total_bytes_input: u64,
total_bytes_output: u64,
summary_kind: c_int,
callbacks: *const FIO_rust_multiple_summary_callbacks_t,
) {
if nb_files_total <= 1 || nb_files_processed < 1 {
return;
}
assert!(!callbacks.is_null());
let callbacks = unsafe { &*callbacks };
let display_progress = callbacks
.display_progress
.expect("multi-file summary progress callback is required");
unsafe { display_progress(callbacks.opaque) };
match summary_kind {
FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION => {
let display = callbacks
.display_compression
.expect("compression summary callback is required");
unsafe {
display(
callbacks.opaque,
nb_files_processed,
total_bytes_input,
total_bytes_output,
)
};
}
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION => {
let display = callbacks
.display_decompression
.expect("decompression summary callback is required");
unsafe { display(callbacks.opaque, nb_files_processed, total_bytes_output) };
}
_ => unreachable!("invalid multi-file summary kind"),
}
}
type PoolFunction = unsafe extern "C" fn(*mut c_void);
#[cfg(not(test))]
@@ -7328,6 +7395,119 @@ mod tests {
statuses: Vec<c_int>,
}
#[derive(Default)]
struct MultipleSummaryState {
events: Vec<&'static str>,
compression: Option<(c_int, u64, u64)>,
decompression: Option<(c_int, u64)>,
}
unsafe extern "C" fn record_multiple_summary_progress(opaque: *mut c_void) {
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
state.events.push("progress");
}
unsafe extern "C" fn record_multiple_compression_summary(
opaque: *mut c_void,
nb_files_processed: c_int,
total_bytes_input: u64,
total_bytes_output: u64,
) {
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
state.events.push("compression");
state.compression = Some((
nb_files_processed,
total_bytes_input,
total_bytes_output,
));
}
unsafe extern "C" fn record_multiple_decompression_summary(
opaque: *mut c_void,
nb_files_processed: c_int,
total_bytes_output: u64,
) {
let state = unsafe { &mut *opaque.cast::<MultipleSummaryState>() };
state.events.push("decompression");
state.decompression = Some((nb_files_processed, total_bytes_output));
}
fn multiple_summary_callbacks(
state: &mut MultipleSummaryState,
) -> FIO_rust_multiple_summary_callbacks_t {
FIO_rust_multiple_summary_callbacks_t {
opaque: (state as *mut MultipleSummaryState).cast(),
display_progress: Some(record_multiple_summary_progress),
display_compression: Some(record_multiple_compression_summary),
display_decompression: Some(record_multiple_decompression_summary),
}
}
#[test]
fn multiple_file_summary_dispatches_compression_after_progress() {
let mut state = MultipleSummaryState::default();
let callbacks = multiple_summary_callbacks(&mut state);
unsafe {
FIO_rust_displayMultipleFileSummary(
3,
2,
4096,
1024,
FIO_RUST_MULTIPLE_SUMMARY_COMPRESSION,
&callbacks,
)
};
assert_eq!(state.events, ["progress", "compression"]);
assert_eq!(state.compression, Some((2, 4096, 1024)));
assert_eq!(state.decompression, None);
}
#[test]
fn multiple_file_summary_dispatches_decompression_and_skips_empty_sessions() {
let mut state = MultipleSummaryState::default();
let callbacks = multiple_summary_callbacks(&mut state);
unsafe {
FIO_rust_displayMultipleFileSummary(
4,
3,
0,
777,
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
&callbacks,
)
};
assert_eq!(state.events, ["progress", "decompression"]);
assert_eq!(state.decompression, Some((3, 777)));
state.events.clear();
state.decompression = None;
unsafe {
FIO_rust_displayMultipleFileSummary(
1,
3,
10,
20,
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
&callbacks,
)
};
unsafe {
FIO_rust_displayMultipleFileSummary(
4,
0,
10,
20,
FIO_RUST_MULTIPLE_SUMMARY_DECOMPRESSION,
&callbacks,
)
};
assert!(state.events.is_empty());
assert_eq!(state.decompression, None);
}
unsafe extern "C" fn record_multiple_compression_file(
opaque: *mut c_void,
destination: *const c_char,