From da7a09c358a041d8a922d41c2c926569edc18879 Mon Sep 17 00:00:00 2001 From: James Brown Date: Wed, 29 Apr 2026 12:33:16 -0700 Subject: [PATCH 1/3] feat: expose mi_stats_get_json and a safe wrapper around it --- libmimalloc-sys/src/extended.rs | 8 +++++++- src/extended.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/libmimalloc-sys/src/extended.rs b/libmimalloc-sys/src/extended.rs index d37adba..2d60c48 100644 --- a/libmimalloc-sys/src/extended.rs +++ b/libmimalloc-sys/src/extended.rs @@ -447,6 +447,12 @@ extern "C" { /// /// Note: This function is thread safe. pub fn mi_register_error(out: mi_error_fun, arg: *mut c_void); + + /// Get the statistics for the current subprocess aggregated over all its heaps as JSON. + /// + /// Returns pointer to the buffer or NULL on failure. Use mi_free() to free the buffer if the buf parameter was NULL. + #[cfg(not(feature = "v2"))] + pub fn mi_stats_get_json(buf_size: usize, buf: *mut c_char) -> *mut c_char; } /// An output callback. Must be thread-safe. @@ -1092,7 +1098,7 @@ mod tests { #[test] fn it_calculates_usable_size() { - let ptr = unsafe { mi_malloc(32) } as *mut u8; + let ptr = unsafe { crate::mi_malloc(32) } as *mut u8; let usable_size = unsafe { mi_usable_size(ptr as *mut c_void) }; assert!( usable_size >= 32, diff --git a/src/extended.rs b/src/extended.rs index be9614c..6861dde 100644 --- a/src/extended.rs +++ b/src/extended.rs @@ -1,5 +1,7 @@ use crate::MiMalloc; use core::ffi::c_void; +#[cfg(not(feature = "v2"))] +use core::ffi::{c_char, CStr}; impl MiMalloc { /// Get the mimalloc version. @@ -17,6 +19,29 @@ impl MiMalloc { pub unsafe fn usable_size(&self, ptr: *const u8) -> usize { ffi::mi_usable_size(ptr as *const c_void) } + + /// Call the given function with a string version of the JSON stats for the whole process + /// + /// Allocates (using mimalloc itself) to store the JSON structure. + #[cfg(not(feature = "v2"))] + pub fn with_stats_json(f: F) -> Result + where + F: FnOnce(&str) -> O, + { + unsafe { + let buf = ffi::mi_stats_get_json(0, core::ptr::null::() as *mut _); + if buf.is_null() { + return Err("failed to call mi_stats_get_json"); + } + let cstr = CStr::from_ptr(buf); + let slice = cstr + .to_str() + .map_err(|_| "mi_stats_get_json contained invalid UTF-8")?; + let o = f(slice); + ffi::mi_free(buf as _); + Ok(o) + } + } } #[cfg(test)] @@ -43,4 +68,12 @@ mod test { assert!(usable_size >= 8); } } + + #[test] + #[cfg(not(feature = "v2"))] + fn test_with_stats_json() { + let (first_char, len) = MiMalloc::with_stats_json(|f| (f.chars().next(), f.len())).unwrap(); + assert_eq!(first_char, Some('{')); + assert!(len > 1); + } } From e1fd9ebf4a6031e2d1746d9f66bd7b6c5bb8a982 Mon Sep 17 00:00:00 2001 From: James Brown Date: Wed, 20 May 2026 14:37:36 -0700 Subject: [PATCH 2/3] fix up some tests --- libmimalloc-sys/src/extended.rs | 3 ++- libmimalloc-sys/sys-test/build.rs | 6 ++++++ src/lib.rs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libmimalloc-sys/src/extended.rs b/libmimalloc-sys/src/extended.rs index 2d60c48..52e4770 100644 --- a/libmimalloc-sys/src/extended.rs +++ b/libmimalloc-sys/src/extended.rs @@ -1094,11 +1094,12 @@ extern "C" { #[cfg(test)] mod tests { + use super::super::mi_malloc; use super::*; #[test] fn it_calculates_usable_size() { - let ptr = unsafe { crate::mi_malloc(32) } as *mut u8; + let ptr = unsafe { mi_malloc(32) } as *mut u8; let usable_size = unsafe { mi_usable_size(ptr as *mut c_void) }; assert!( usable_size >= 32, diff --git a/libmimalloc-sys/sys-test/build.rs b/libmimalloc-sys/sys-test/build.rs index 27ab233..5a49144 100644 --- a/libmimalloc-sys/sys-test/build.rs +++ b/libmimalloc-sys/sys-test/build.rs @@ -55,5 +55,11 @@ fn main() { } }); + if version == "v3" { + cfg.header("mimalloc-stats.h").include(format!( + "{cargo_manifest_dir}/../c_src/mimalloc/{version}/include" + )); + } + cfg.generate("../src/lib.rs", "all.rs"); } diff --git a/src/lib.rs b/src/lib.rs index 4288b2f..f54f1da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! own benchmarks. //! //! To enable secure mode, put in `Cargo.toml`: -//! ```rust,ignore +//! ```toml //! [dependencies] //! mimalloc = { version = "*", features = ["secure"] } //! ``` From eb4a16de5af89d3fec9e7b6ef0c4b4e89c1b794e Mon Sep 17 00:00:00 2001 From: James Brown Date: Wed, 20 May 2026 14:57:24 -0700 Subject: [PATCH 3/3] simplify API --- src/extended.rs | 56 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/extended.rs b/src/extended.rs index 6861dde..acf0c14 100644 --- a/src/extended.rs +++ b/src/extended.rs @@ -20,30 +20,49 @@ impl MiMalloc { ffi::mi_usable_size(ptr as *const c_void) } - /// Call the given function with a string version of the JSON stats for the whole process + /// Extract a string containing the JSON statistics for the whole process /// /// Allocates (using mimalloc itself) to store the JSON structure. #[cfg(not(feature = "v2"))] - pub fn with_stats_json(f: F) -> Result - where - F: FnOnce(&str) -> O, - { + pub fn stats_json() -> Result { unsafe { let buf = ffi::mi_stats_get_json(0, core::ptr::null::() as *mut _); - if buf.is_null() { - return Err("failed to call mi_stats_get_json"); + if let Some(inner) = core::ptr::NonNull::new(buf) { + Ok(StatsJson { inner }) + } else { + Err("failed to call mi_stats_get_json") } - let cstr = CStr::from_ptr(buf); - let slice = cstr - .to_str() - .map_err(|_| "mi_stats_get_json contained invalid UTF-8")?; - let o = f(slice); - ffi::mi_free(buf as _); - Ok(o) } } } +#[cfg(not(feature = "v2"))] +/// Wrapper around the output of `MiMalloc::stats_json()` +/// +/// Derefs to a CStr +pub struct StatsJson { + inner: core::ptr::NonNull, +} + +#[cfg(not(feature = "v2"))] +impl core::ops::Deref for StatsJson { + type Target = CStr; + + fn deref(&self) -> &Self::Target { + unsafe { + let cstr = CStr::from_ptr(self.inner.as_ptr()); + &cstr + } + } +} + +#[cfg(not(feature = "v2"))] +impl Drop for StatsJson { + fn drop(&mut self) { + unsafe { ffi::mi_free(self.inner.as_ptr() as _) } + } +} + #[cfg(test)] mod test { use super::*; @@ -71,9 +90,10 @@ mod test { #[test] #[cfg(not(feature = "v2"))] - fn test_with_stats_json() { - let (first_char, len) = MiMalloc::with_stats_json(|f| (f.chars().next(), f.len())).unwrap(); - assert_eq!(first_char, Some('{')); - assert!(len > 1); + fn test_stats_json() { + let stats = MiMalloc::stats_json().expect("should get stats"); + let slice = stats.to_str().expect("should be valid UTF-8"); + assert_eq!(slice.chars().next(), Some('{')); + assert!(stats.count_bytes() > 1); } }