diff --git a/programs/zstdcli_trace.c b/programs/zstdcli_trace.c index 35075a52c..859738790 100644 --- a/programs/zstdcli_trace.c +++ b/programs/zstdcli_trace.c @@ -8,165 +8,7 @@ * You may select, at your option, one of the above-listed licenses. */ +/* The trace implementation lives in rust/src/zstdcli_trace.rs. Keep this + * translation unit so C build systems can continue to enumerate the public + * trace header without defining a second set of ABI symbols. */ #include "zstdcli_trace.h" - -#include -#include - -#include "timefn.h" -#include "util.h" - -#define ZSTD_STATIC_LINKING_ONLY -#include "../lib/zstd.h" -/* We depend on the trace header to avoid duplicating the ZSTD_trace struct. - * But, we check the version so it is compatible with dynamic linking. - */ -#include "../lib/common/zstd_trace.h" -/* We only use macros from threading.h so it is compatible with dynamic linking */ -#include "../lib/common/threading.h" - -#if ZSTD_TRACE - -static FILE* g_traceFile = NULL; -static int g_mutexInit = 0; -static ZSTD_pthread_mutex_t g_mutex; -static UTIL_time_t g_enableTime = UTIL_TIME_INITIALIZER; - -void TRACE_enable(char const* filename) -{ - int const writeHeader = !UTIL_isRegularFile(filename); - if (g_traceFile) - fclose(g_traceFile); - g_traceFile = fopen(filename, "a"); - if (g_traceFile && writeHeader) { - /* Fields: - * algorithm - * version - * method - * streaming - * level - * workers - * dictionary size - * uncompressed size - * compressed size - * duration nanos - * compression ratio - * speed MB/s - */ - fprintf(g_traceFile, "Algorithm, Version, Method, Mode, Level, Workers, Dictionary Size, Uncompressed Size, Compressed Size, Duration Nanos, Compression Ratio, Speed MB/s\n"); - } - g_enableTime = UTIL_getTime(); - if (!g_mutexInit) { - if (!ZSTD_pthread_mutex_init(&g_mutex, NULL)) { - g_mutexInit = 1; - } else { - TRACE_finish(); - } - } -} - -void TRACE_finish(void) -{ - if (g_traceFile) { - fclose(g_traceFile); - } - g_traceFile = NULL; - if (g_mutexInit) { - ZSTD_pthread_mutex_destroy(&g_mutex); - g_mutexInit = 0; - } -} - -static void TRACE_log(char const* method, PTime duration, ZSTD_Trace const* trace) -{ - int level = 0; - int workers = 0; - double const ratio = (double)trace->uncompressedSize / (double)trace->compressedSize; - double const speed = ((double)trace->uncompressedSize * 1000) / (double)duration; - if (trace->params) { - ZSTD_CCtxParams_getParameter(trace->params, ZSTD_c_compressionLevel, &level); - ZSTD_CCtxParams_getParameter(trace->params, ZSTD_c_nbWorkers, &workers); - } - assert(g_traceFile != NULL); - - ZSTD_pthread_mutex_lock(&g_mutex); - /* Fields: - * algorithm - * version - * method - * streaming - * level - * workers - * dictionary size - * uncompressed size - * compressed size - * duration nanos - * compression ratio - * speed MB/s - */ - fprintf(g_traceFile, - "zstd, %u, %s, %s, %d, %d, %llu, %llu, %llu, %llu, %.2f, %.2f\n", - trace->version, - method, - trace->streaming ? "streaming" : "single-pass", - level, - workers, - (unsigned long long)trace->dictionarySize, - (unsigned long long)trace->uncompressedSize, - (unsigned long long)trace->compressedSize, - (unsigned long long)duration, - ratio, - speed); - ZSTD_pthread_mutex_unlock(&g_mutex); -} - -/** - * These symbols override the weak symbols provided by the library. - */ - -ZSTD_TraceCtx ZSTD_trace_compress_begin(ZSTD_CCtx const* cctx) -{ - (void)cctx; - if (g_traceFile == NULL) - return 0; - return (ZSTD_TraceCtx)UTIL_clockSpanNano(g_enableTime); -} - -void ZSTD_trace_compress_end(ZSTD_TraceCtx ctx, ZSTD_Trace const* trace) -{ - PTime const beginNanos = (PTime)ctx; - PTime const endNanos = UTIL_clockSpanNano(g_enableTime); - PTime const durationNanos = endNanos > beginNanos ? endNanos - beginNanos : 0; - assert(g_traceFile != NULL); - assert(trace->version == ZSTD_VERSION_NUMBER); /* CLI version must match. */ - TRACE_log("compress", durationNanos, trace); -} - -ZSTD_TraceCtx ZSTD_trace_decompress_begin(ZSTD_DCtx const* dctx) -{ - (void)dctx; - if (g_traceFile == NULL) - return 0; - return (ZSTD_TraceCtx)UTIL_clockSpanNano(g_enableTime); -} - -void ZSTD_trace_decompress_end(ZSTD_TraceCtx ctx, ZSTD_Trace const* trace) -{ - PTime const beginNanos = (PTime)ctx; - PTime const endNanos = UTIL_clockSpanNano(g_enableTime); - PTime const durationNanos = endNanos > beginNanos ? endNanos - beginNanos : 0; - assert(g_traceFile != NULL); - assert(trace->version == ZSTD_VERSION_NUMBER); /* CLI version must match. */ - TRACE_log("decompress", durationNanos, trace); -} - -#else /* ZSTD_TRACE */ - -void TRACE_enable(char const* filename) -{ - (void)filename; -} - -void TRACE_finish(void) {} - -#endif /* ZSTD_TRACE */ diff --git a/rust/src/zstdcli_trace.rs b/rust/src/zstdcli_trace.rs index 3558d1768..3a0ee7a9f 100644 --- a/rust/src/zstdcli_trace.rs +++ b/rust/src/zstdcli_trace.rs @@ -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); + } }