Skip to main content

rustfs_mimalloc/
api.rs

1//! Stats, options, version, and process information APIs.
2
3use crate::MiMalloc;
4use core::ffi::c_void;
5use core::ptr::NonNull;
6
7/// Mark the current thread as part of a thread pool for mimalloc.
8///
9/// This is a safe wrapper around mimalloc V3's `mi_thread_set_in_threadpool`.
10/// The upstream API takes no pointers, only updates the current thread's
11/// mimalloc thread-local state, and is intended to be called by custom
12/// thread-pool worker threads. Repeated calls keep the same threadpool marker.
13#[inline]
14pub fn set_current_thread_in_threadpool() {
15    unsafe { rustfs_mimalloc_sys::mi_thread_set_in_threadpool() }
16}
17
18/// Process memory information returned by [`MiMalloc::process_info`].
19#[derive(Debug, Clone, Copy, Default)]
20pub struct ProcessInfo {
21    pub elapsed_msecs: usize,
22    pub user_msecs: usize,
23    pub system_msecs: usize,
24    pub current_rss: usize,
25    pub peak_rss: usize,
26    pub current_commit: usize,
27    pub peak_commit: usize,
28    pub page_faults: usize,
29}
30
31impl MiMalloc {
32    /// mimalloc version as `major * 10000 + minor * 100 + patch`.
33    #[inline]
34    pub fn version() -> i32 {
35        unsafe { rustfs_mimalloc_sys::mi_version() }
36    }
37
38    /// Force garbage collection.
39    #[inline]
40    pub fn collect(force: bool) {
41        unsafe { rustfs_mimalloc_sys::mi_collect(force) }
42    }
43
44    /// Usable size of an allocated block (may be larger than requested).
45    ///
46    /// # Safety
47    /// `ptr` must have been allocated by mimalloc.
48    #[inline]
49    pub unsafe fn usable_size(ptr: *const u8) -> usize {
50        unsafe { rustfs_mimalloc_sys::mi_usable_size(ptr as *const c_void) }
51    }
52
53    /// Free a mimalloc block when the allocation size is known.
54    ///
55    /// For small sizes this uses mimalloc's small-free fast path.
56    ///
57    /// # Safety
58    /// `ptr` must be null or a valid mimalloc allocation, and `size` must be
59    /// the allocation size used for the corresponding allocation.
60    #[inline]
61    pub unsafe fn free_csize(ptr: *mut u8, size: usize) {
62        unsafe { rustfs_mimalloc_sys::mi_free_csize(ptr as *mut c_void, size) }
63    }
64
65    /// Free a non-null mimalloc block when the allocation size is known.
66    ///
67    /// For small sizes this uses mimalloc's non-null small-free fast path.
68    ///
69    /// # Safety
70    /// `ptr` must be a valid mimalloc allocation, and `size` must be the
71    /// allocation size used for the corresponding allocation.
72    #[inline]
73    pub unsafe fn free_csize_nonnull(ptr: NonNull<u8>, size: usize) {
74        unsafe { rustfs_mimalloc_sys::mi_free_csize_nonnull(ptr.as_ptr() as *mut c_void, size) }
75    }
76
77    /// Free a small mimalloc block.
78    ///
79    /// # Safety
80    /// `ptr` must be null or a valid mimalloc allocation whose allocation size
81    /// is less than or equal to [`crate::MI_SMALL_SIZE_MAX`].
82    #[inline]
83    pub unsafe fn free_small(ptr: *mut u8) {
84        unsafe { rustfs_mimalloc_sys::mi_free_small(ptr as *mut c_void) }
85    }
86
87    /// Free a non-null small mimalloc block.
88    ///
89    /// # Safety
90    /// `ptr` must be a valid mimalloc allocation whose allocation size is less
91    /// than or equal to [`crate::MI_SMALL_SIZE_MAX`].
92    #[inline]
93    pub unsafe fn free_small_nonnull(ptr: NonNull<u8>) {
94        unsafe { rustfs_mimalloc_sys::mi_free_small_nonnull(ptr.as_ptr() as *mut c_void) }
95    }
96
97    /// Process memory information.
98    pub fn process_info() -> ProcessInfo {
99        let mut info = ProcessInfo::default();
100        unsafe {
101            rustfs_mimalloc_sys::mi_process_info(
102                &mut info.elapsed_msecs,
103                &mut info.user_msecs,
104                &mut info.system_msecs,
105                &mut info.current_rss,
106                &mut info.peak_rss,
107                &mut info.current_commit,
108                &mut info.peak_commit,
109                &mut info.page_faults,
110            );
111        }
112        info
113    }
114
115    // ── Stats ───────────────────────────────────────────────────────────────
116
117    /// Allocation statistics as JSON. Returns empty string on failure.
118    pub fn stats_json() -> String {
119        unsafe {
120            crate::ffi::owned_mimalloc_string(rustfs_mimalloc_sys::mi_stats_get_json(
121                0,
122                core::ptr::null_mut(),
123            ))
124        }
125    }
126
127    /// Allocation statistics in mimalloc's human-readable text format.
128    pub fn stats_print() -> String {
129        crate::ffi::collect_mimalloc_output(|out, arg| unsafe {
130            rustfs_mimalloc_sys::mi_stats_print_out(out, arg);
131        })
132    }
133
134    /// Reset accumulated mimalloc allocation statistics.
135    #[inline]
136    pub fn stats_reset() {
137        unsafe { rustfs_mimalloc_sys::mi_stats_reset() }
138    }
139
140    /// Process memory information in mimalloc's human-readable text format.
141    pub fn process_info_print() -> String {
142        crate::ffi::collect_mimalloc_output(|out, arg| unsafe {
143            rustfs_mimalloc_sys::mi_process_info_print_out(out, arg);
144        })
145    }
146
147    // ── Options ─────────────────────────────────────────────────────────────
148
149    /// Check if an option is enabled.
150    #[inline]
151    pub fn option_is_enabled(option: rustfs_mimalloc_sys::mi_option_t) -> bool {
152        unsafe { rustfs_mimalloc_sys::mi_option_is_enabled(option) }
153    }
154
155    /// Get an option value.
156    #[inline]
157    pub fn option_get(option: rustfs_mimalloc_sys::mi_option_t) -> rustfs_mimalloc_sys::c_long {
158        unsafe { rustfs_mimalloc_sys::mi_option_get(option) }
159    }
160
161    /// Get an option value as size (bytes).
162    #[inline]
163    pub fn option_get_size(option: rustfs_mimalloc_sys::mi_option_t) -> usize {
164        unsafe { rustfs_mimalloc_sys::mi_option_get_size(option) }
165    }
166
167    /// Set an option value.
168    ///
169    /// ```rust
170    /// use rustfs_mimalloc::MiMalloc;
171    /// use rustfs_mimalloc_sys::mi_option_t;
172    ///
173    /// // Return memory to OS immediately
174    /// MiMalloc::option_set(mi_option_t::mi_option_purge_delay, 0);
175    /// ```
176    #[inline]
177    pub fn option_set(
178        option: rustfs_mimalloc_sys::mi_option_t,
179        value: rustfs_mimalloc_sys::c_long,
180    ) {
181        unsafe { rustfs_mimalloc_sys::mi_option_set(option, value) }
182    }
183
184    /// Enable an option.
185    #[inline]
186    pub fn option_enable(option: rustfs_mimalloc_sys::mi_option_t) {
187        unsafe { rustfs_mimalloc_sys::mi_option_enable(option) }
188    }
189
190    /// Disable an option.
191    #[inline]
192    pub fn option_disable(option: rustfs_mimalloc_sys::mi_option_t) {
193        unsafe { rustfs_mimalloc_sys::mi_option_disable(option) }
194    }
195}
196
197// ── Tests ───────────────────────────────────────────────────────────────────
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use rustfs_mimalloc_sys::mi_option_t;
203
204    #[test]
205    fn version_is_v3() {
206        assert!(MiMalloc::version() >= 30501, "expected >= V3.5.1");
207    }
208
209    #[test]
210    fn stats_json_not_empty() {
211        let json = MiMalloc::stats_json();
212        assert!(!json.is_empty());
213    }
214
215    #[test]
216    fn stats_print_not_empty() {
217        let stats = MiMalloc::stats_print();
218        assert!(!stats.is_empty());
219    }
220
221    #[test]
222    fn stats_reset_smoke() {
223        MiMalloc::stats_reset();
224    }
225
226    #[test]
227    fn process_info_print_not_empty() {
228        let info = MiMalloc::process_info_print();
229        assert!(!info.is_empty());
230    }
231
232    #[test]
233    fn option_roundtrip() {
234        // Just verify no panic
235        let _ = MiMalloc::option_get(mi_option_t::mi_option_purge_delay);
236        let _ = MiMalloc::option_is_enabled(mi_option_t::mi_option_show_errors);
237    }
238
239    #[test]
240    fn process_info_smoke() {
241        let info = MiMalloc::process_info();
242        let _ = info;
243    }
244
245    #[test]
246    fn set_current_thread_in_threadpool_smoke() {
247        set_current_thread_in_threadpool();
248        set_current_thread_in_threadpool();
249    }
250
251    #[test]
252    fn usable_size_at_least_requested() {
253        unsafe {
254            let ptr = rustfs_mimalloc_sys::mi_malloc(64);
255            assert!(MiMalloc::usable_size(ptr as *const u8) >= 64);
256            rustfs_mimalloc_sys::mi_free(ptr);
257        }
258    }
259
260    #[test]
261    fn free_small_nonnull_smoke() {
262        unsafe {
263            let ptr = rustfs_mimalloc_sys::mi_malloc_small(64);
264            let ptr = NonNull::new(ptr as *mut u8).expect("mi_malloc_small returned null");
265            MiMalloc::free_small_nonnull(ptr);
266        }
267    }
268
269    #[test]
270    fn free_csize_routes_small_and_large() {
271        unsafe {
272            let small = rustfs_mimalloc_sys::mi_malloc_small(64);
273            MiMalloc::free_csize(small as *mut u8, 64);
274
275            let large_size = rustfs_mimalloc_sys::MI_SMALL_SIZE_MAX + 64;
276            let large = rustfs_mimalloc_sys::mi_malloc(large_size);
277            let large = NonNull::new(large as *mut u8).expect("mi_malloc returned null");
278            MiMalloc::free_csize_nonnull(large, large_size);
279        }
280    }
281}