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    /// Convert a byte size to a mimalloc machine-word count.
54    ///
55    /// This mirrors mimalloc's `mi_wsize_from_size` helper for the word-size
56    /// small allocation fast paths.
57    #[inline]
58    pub const fn wsize_from_size(size: usize) -> usize {
59        rustfs_mimalloc_sys::mi_wsize_from_size(size)
60    }
61
62    /// Allocate a mimalloc block when the allocation size is known.
63    ///
64    /// For small sizes this uses mimalloc's word-size small-allocation fast path.
65    ///
66    /// # Safety
67    /// The returned raw pointer must be checked for null and eventually freed
68    /// with a compatible mimalloc free API.
69    #[inline]
70    pub unsafe fn malloc_csize(size: usize) -> *mut u8 {
71        unsafe { rustfs_mimalloc_sys::mi_malloc_csize(size) as *mut u8 }
72    }
73
74    /// Allocate a zeroed mimalloc block when the allocation size is known.
75    ///
76    /// For small sizes this uses mimalloc's word-size small-allocation fast path.
77    ///
78    /// # Safety
79    /// The returned raw pointer must be checked for null and eventually freed
80    /// with a compatible mimalloc free API.
81    #[inline]
82    pub unsafe fn zalloc_csize(size: usize) -> *mut u8 {
83        unsafe { rustfs_mimalloc_sys::mi_zalloc_csize(size) as *mut u8 }
84    }
85
86    /// Allocate a small block by machine-word count.
87    ///
88    /// # Safety
89    /// `wsize` is measured in `usize` machine words, not bytes. The returned raw
90    /// pointer must be checked for null and eventually freed with a compatible
91    /// mimalloc free API.
92    #[inline]
93    pub unsafe fn wmalloc_small(wsize: usize) -> *mut u8 {
94        unsafe { rustfs_mimalloc_sys::mi_wmalloc_small(wsize) as *mut u8 }
95    }
96
97    /// Allocate a zeroed small block by machine-word count.
98    ///
99    /// # Safety
100    /// `wsize` is measured in `usize` machine words, not bytes. The returned raw
101    /// pointer must be checked for null and eventually freed with a compatible
102    /// mimalloc free API.
103    #[inline]
104    pub unsafe fn wzalloc_small(wsize: usize) -> *mut u8 {
105        unsafe { rustfs_mimalloc_sys::mi_wzalloc_small(wsize) as *mut u8 }
106    }
107
108    /// Free a mimalloc block when the allocation size is known.
109    ///
110    /// For small sizes this uses mimalloc's small-free fast path.
111    ///
112    /// # Safety
113    /// `ptr` must be null or a valid mimalloc allocation, and `size` must be
114    /// the allocation size used for the corresponding allocation.
115    #[inline]
116    pub unsafe fn free_csize(ptr: *mut u8, size: usize) {
117        unsafe { rustfs_mimalloc_sys::mi_free_csize(ptr as *mut c_void, size) }
118    }
119
120    /// Free a non-null mimalloc block when the allocation size is known.
121    ///
122    /// For small sizes this uses mimalloc's non-null small-free fast path.
123    ///
124    /// # Safety
125    /// `ptr` must be a valid mimalloc allocation, and `size` must be the
126    /// allocation size used for the corresponding allocation.
127    #[inline]
128    pub unsafe fn free_csize_nonnull(ptr: NonNull<u8>, size: usize) {
129        unsafe { rustfs_mimalloc_sys::mi_free_csize_nonnull(ptr.as_ptr() as *mut c_void, size) }
130    }
131
132    /// Free a small mimalloc block.
133    ///
134    /// # Safety
135    /// `ptr` must be null or a valid mimalloc allocation whose allocation size
136    /// is less than or equal to [`crate::MI_SMALL_SIZE_MAX`].
137    #[inline]
138    pub unsafe fn free_small(ptr: *mut u8) {
139        unsafe { rustfs_mimalloc_sys::mi_free_small(ptr as *mut c_void) }
140    }
141
142    /// Free a non-null small mimalloc block.
143    ///
144    /// # Safety
145    /// `ptr` must be a valid mimalloc allocation whose allocation size is less
146    /// than or equal to [`crate::MI_SMALL_SIZE_MAX`].
147    #[inline]
148    pub unsafe fn free_small_nonnull(ptr: NonNull<u8>) {
149        unsafe { rustfs_mimalloc_sys::mi_free_small_nonnull(ptr.as_ptr() as *mut c_void) }
150    }
151
152    /// Process memory information.
153    pub fn process_info() -> ProcessInfo {
154        let mut info = ProcessInfo::default();
155        unsafe {
156            rustfs_mimalloc_sys::mi_process_info(
157                &mut info.elapsed_msecs,
158                &mut info.user_msecs,
159                &mut info.system_msecs,
160                &mut info.current_rss,
161                &mut info.peak_rss,
162                &mut info.current_commit,
163                &mut info.peak_commit,
164                &mut info.page_faults,
165            );
166        }
167        info
168    }
169
170    // ── Stats ───────────────────────────────────────────────────────────────
171
172    /// Allocation statistics as JSON. Returns empty string on failure.
173    pub fn stats_json() -> String {
174        unsafe {
175            crate::ffi::owned_mimalloc_string(rustfs_mimalloc_sys::mi_stats_get_json(
176                0,
177                core::ptr::null_mut(),
178            ))
179        }
180    }
181
182    /// Allocation statistics in mimalloc's human-readable text format.
183    pub fn stats_print() -> String {
184        crate::ffi::collect_mimalloc_output(|out, arg| unsafe {
185            rustfs_mimalloc_sys::mi_stats_print_out(out, arg);
186        })
187    }
188
189    /// Reset accumulated mimalloc allocation statistics.
190    #[inline]
191    pub fn stats_reset() {
192        unsafe { rustfs_mimalloc_sys::mi_stats_reset() }
193    }
194
195    /// Process memory information in mimalloc's human-readable text format.
196    pub fn process_info_print() -> String {
197        crate::ffi::collect_mimalloc_output(|out, arg| unsafe {
198            rustfs_mimalloc_sys::mi_process_info_print_out(out, arg);
199        })
200    }
201
202    // ── Options ─────────────────────────────────────────────────────────────
203
204    /// Check if an option is enabled.
205    #[inline]
206    pub fn option_is_enabled(option: rustfs_mimalloc_sys::mi_option_t) -> bool {
207        unsafe { rustfs_mimalloc_sys::mi_option_is_enabled(option) }
208    }
209
210    /// Get an option value.
211    #[inline]
212    pub fn option_get(option: rustfs_mimalloc_sys::mi_option_t) -> rustfs_mimalloc_sys::c_long {
213        unsafe { rustfs_mimalloc_sys::mi_option_get(option) }
214    }
215
216    /// Get an option value as size (bytes).
217    #[inline]
218    pub fn option_get_size(option: rustfs_mimalloc_sys::mi_option_t) -> usize {
219        unsafe { rustfs_mimalloc_sys::mi_option_get_size(option) }
220    }
221
222    /// Set an option value.
223    ///
224    /// ```rust
225    /// use rustfs_mimalloc::MiMalloc;
226    /// use rustfs_mimalloc_sys::mi_option_t;
227    ///
228    /// // Return memory to OS immediately
229    /// MiMalloc::option_set(mi_option_t::mi_option_purge_delay, 0);
230    /// ```
231    #[inline]
232    pub fn option_set(
233        option: rustfs_mimalloc_sys::mi_option_t,
234        value: rustfs_mimalloc_sys::c_long,
235    ) {
236        unsafe { rustfs_mimalloc_sys::mi_option_set(option, value) }
237    }
238
239    /// Enable an option.
240    #[inline]
241    pub fn option_enable(option: rustfs_mimalloc_sys::mi_option_t) {
242        unsafe { rustfs_mimalloc_sys::mi_option_enable(option) }
243    }
244
245    /// Disable an option.
246    #[inline]
247    pub fn option_disable(option: rustfs_mimalloc_sys::mi_option_t) {
248        unsafe { rustfs_mimalloc_sys::mi_option_disable(option) }
249    }
250}
251
252// ── Tests ───────────────────────────────────────────────────────────────────
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use rustfs_mimalloc_sys::mi_option_t;
258
259    #[test]
260    fn version_is_v3() {
261        assert!(MiMalloc::version() >= 30502, "expected >= V3.5.2");
262    }
263
264    #[test]
265    fn stats_json_not_empty() {
266        let json = MiMalloc::stats_json();
267        assert!(!json.is_empty());
268    }
269
270    #[test]
271    fn stats_print_not_empty() {
272        let stats = MiMalloc::stats_print();
273        assert!(!stats.is_empty());
274    }
275
276    #[test]
277    fn stats_reset_smoke() {
278        MiMalloc::stats_reset();
279    }
280
281    #[test]
282    fn process_info_print_not_empty() {
283        let info = MiMalloc::process_info_print();
284        assert!(!info.is_empty());
285    }
286
287    #[test]
288    fn option_roundtrip() {
289        // Just verify no panic
290        let _ = MiMalloc::option_get(mi_option_t::mi_option_purge_delay);
291        let _ = MiMalloc::option_is_enabled(mi_option_t::mi_option_show_errors);
292    }
293
294    #[test]
295    fn process_info_smoke() {
296        let info = MiMalloc::process_info();
297        let _ = info;
298    }
299
300    #[test]
301    fn set_current_thread_in_threadpool_smoke() {
302        set_current_thread_in_threadpool();
303        set_current_thread_in_threadpool();
304    }
305
306    #[test]
307    fn usable_size_at_least_requested() {
308        unsafe {
309            let ptr = rustfs_mimalloc_sys::mi_malloc(64);
310            assert!(MiMalloc::usable_size(ptr as *const u8) >= 64);
311            rustfs_mimalloc_sys::mi_free(ptr);
312        }
313    }
314
315    #[test]
316    fn free_small_nonnull_smoke() {
317        unsafe {
318            let ptr = rustfs_mimalloc_sys::mi_malloc_small(64);
319            let ptr = NonNull::new(ptr as *mut u8).expect("mi_malloc_small returned null");
320            MiMalloc::free_small_nonnull(ptr);
321        }
322    }
323
324    #[test]
325    fn word_size_small_alloc_smoke() {
326        unsafe {
327            let wsize = MiMalloc::wsize_from_size(64);
328
329            let ptr =
330                NonNull::new(MiMalloc::wmalloc_small(wsize)).expect("wmalloc_small returned null");
331            MiMalloc::free_small_nonnull(ptr);
332
333            let zeroed =
334                NonNull::new(MiMalloc::wzalloc_small(wsize)).expect("wzalloc_small returned null");
335            assert!((0..64).all(|i| *zeroed.as_ptr().add(i) == 0));
336            MiMalloc::free_small_nonnull(zeroed);
337        }
338    }
339
340    #[test]
341    fn csize_alloc_smoke() {
342        unsafe {
343            let ptr = NonNull::new(MiMalloc::malloc_csize(64)).expect("malloc_csize returned null");
344            MiMalloc::free_csize_nonnull(ptr, 64);
345
346            let zeroed =
347                NonNull::new(MiMalloc::zalloc_csize(64)).expect("zalloc_csize returned null");
348            assert!((0..64).all(|i| *zeroed.as_ptr().add(i) == 0));
349            MiMalloc::free_csize_nonnull(zeroed, 64);
350        }
351    }
352
353    #[test]
354    fn sys_theap_csize_alloc_smoke() {
355        unsafe {
356            let heap = rustfs_mimalloc_sys::mi_heap_new();
357            assert!(!heap.is_null(), "mi_heap_new returned null");
358
359            let theap = rustfs_mimalloc_sys::mi_heap_theap(heap);
360            assert!(!theap.is_null(), "mi_heap_theap returned null");
361
362            let ptr =
363                NonNull::new(rustfs_mimalloc_sys::mi_theap_malloc_csize(theap, 64) as *mut u8)
364                    .expect("mi_theap_malloc_csize returned null");
365            MiMalloc::free_csize_nonnull(ptr, 64);
366
367            let zeroed =
368                NonNull::new(rustfs_mimalloc_sys::mi_theap_zalloc_csize(theap, 64) as *mut u8)
369                    .expect("mi_theap_zalloc_csize returned null");
370            assert!((0..64).all(|i| *zeroed.as_ptr().add(i) == 0));
371            MiMalloc::free_csize_nonnull(zeroed, 64);
372
373            rustfs_mimalloc_sys::mi_heap_delete(heap);
374        }
375    }
376
377    #[test]
378    fn free_csize_routes_small_and_large() {
379        unsafe {
380            let small = rustfs_mimalloc_sys::mi_malloc_small(64);
381            MiMalloc::free_csize(small as *mut u8, 64);
382
383            let large_size = rustfs_mimalloc_sys::MI_SMALL_SIZE_MAX + 64;
384            let large = rustfs_mimalloc_sys::mi_malloc(large_size);
385            let large = NonNull::new(large as *mut u8).expect("mi_malloc returned null");
386            MiMalloc::free_csize_nonnull(large, large_size);
387        }
388    }
389}