feat(cli): move trace callbacks into Rust

The CLI trace translation unit still contained the complete CSV writer and
strong callback implementations even though the Rust CLI archive already had
the equivalent trace module. Reduce the C file to its public-header shim and
make the Rust implementation authoritative, preserving the exact CSV header,
version assertion, timing fields, and no-trace build behavior. Focused tests
cover header creation and the version-mismatch assertion.

Test Plan:
- CLI tests: 161 default and 127 reduced-feature -- passed
- Focused trace tests: 3/3 in both configurations -- passed
- `make -B -C programs -j2` for all CLI variants -- passed
- Trace compression/decompression smoke test -- passed
- C shim compile with and without `ZSTD_NOTRACE` -- passed
- Clippy, nightly rustfmt, and `git diff --check` -- passed
This commit is contained in:
2026-07-18 18:00:42 +02:00
parent 4aafcde301
commit 829f781855
2 changed files with 42 additions and 172 deletions
+39 -11
View File
@@ -4,11 +4,12 @@
//! Command-line trace callbacks.
//!
//! `programs/zstdcli_trace.c` is a small strong-symbol override for the
//! library's weak tracing callbacks. Keep the wire-visible structures and
//! CSV format here so the CLI archive does not need a C implementation merely
//! to enable tracing. The decompression-only CLI still gets the no-op
//! callbacks below; that variant does not link the compression parameter API.
//! `programs/zstdcli_trace.c` is a declaration-only ABI shim. These functions
//! provide the strong-symbol override for the library's weak tracing
//! callbacks. Keep the wire-visible structures and CSV format here so the CLI
//! archive does not need a C implementation merely to enable tracing. The
//! decompression-only CLI still gets the no-op callbacks below; that variant
//! does not link the compression parameter API.
use std::ffi::{c_char, c_int, c_uint, c_void, CStr};
use std::fs::{File, OpenOptions};
@@ -17,6 +18,10 @@ use std::path::Path;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
/* Mirrors ZSTD_VERSION_NUMBER in lib/zstd.h for the CLI/library ABI check. */
const ZSTD_VERSION_NUMBER: c_uint = 10_507;
const TRACE_HEADER: &str = "Algorithm, Version, Method, Mode, Level, Workers, Dictionary Size, Uncompressed Size, Compressed Size, Duration Nanos, Compression Ratio, Speed MB/s";
#[cfg(feature = "compression")]
const ZSTD_C_COMPRESSION_LEVEL: c_int = 100;
#[cfg(feature = "compression")]
@@ -81,10 +86,7 @@ pub unsafe extern "C" fn TRACE_enable(filename: *const c_char) {
.unwrap_or_else(|poisoned| poisoned.into_inner());
if let Some(mut file) = file {
if !was_regular_file {
let _ = writeln!(
file,
"Algorithm, Version, Method, Mode, Level, Workers, Dictionary Size, Uncompressed Size, Compressed Size, Duration Nanos, Compression Ratio, Speed MB/s"
);
let _ = writeln!(file, "{TRACE_HEADER}");
}
state.file = Some(file);
} else {
@@ -163,6 +165,13 @@ fn trace_log(method: &str, begin: u64, trace: &ZSTD_Trace, state: &mut TraceStat
);
}
fn assert_trace_version(trace: &ZSTD_Trace) {
debug_assert_eq!(
trace.version, ZSTD_VERSION_NUMBER,
"CLI version must match trace version"
);
}
#[no_mangle]
pub extern "C" fn ZSTD_trace_compress_begin(_cctx: *const c_void) -> u64 {
trace_begin()
@@ -176,6 +185,7 @@ pub unsafe extern "C" fn ZSTD_trace_compress_end(ctx: u64, trace: *const ZSTD_Tr
let mut state = trace_state()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
assert_trace_version(unsafe { &*trace });
trace_log("compress", ctx, unsafe { &*trace }, &mut state);
}
@@ -192,6 +202,7 @@ pub unsafe extern "C" fn ZSTD_trace_decompress_end(ctx: u64, trace: *const ZSTD_
let mut state = trace_state()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
assert_trace_version(unsafe { &*trace });
trace_log("decompress", ctx, unsafe { &*trace }, &mut state);
}
@@ -222,8 +233,25 @@ mod tests {
assert_ne!(ZSTD_trace_compress_begin(std::ptr::null()), 0);
TRACE_finish();
let contents = fs::read_to_string(&path).unwrap();
assert_eq!(contents.lines().count(), 1);
assert!(contents.starts_with("Algorithm, Version,"));
assert_eq!(contents, format!("{TRACE_HEADER}\n"));
let _ = fs::remove_file(path);
}
#[test]
#[should_panic(expected = "CLI version must match trace version")]
fn trace_version_mismatch_panics_like_c_assertion() {
let trace = ZSTD_Trace {
version: ZSTD_VERSION_NUMBER - 1,
streaming: 0,
dictionaryID: 0,
dictionaryIsCold: 0,
dictionarySize: 0,
uncompressedSize: 0,
compressedSize: 1,
params: std::ptr::null(),
cctx: std::ptr::null(),
dctx: std::ptr::null(),
};
assert_trace_version(&trace);
}
}