1#![cfg_attr(
9 not(test),
10 deny(
11 clippy::unwrap_used,
12 clippy::unreachable,
13 clippy::expect_used,
14 clippy::panic,
15 clippy::unimplemented,
16 clippy::todo
17 )
18)]
19
20#[cfg(not(feature = "jemalloc"))]
21use std::alloc::{GlobalAlloc, Layout, System};
22#[cfg(not(feature = "jemalloc"))]
23use std::sync::atomic::{AtomicUsize, Ordering};
24
25#[cfg(not(feature = "jemalloc"))]
27pub static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
28#[cfg(not(feature = "jemalloc"))]
30pub static PEAK: AtomicUsize = AtomicUsize::new(0);
31
32#[cfg(not(feature = "jemalloc"))]
34pub struct TrackingAlloc;
35
36#[cfg(not(feature = "jemalloc"))]
37#[inline]
38fn record_alloc(size: usize) {
39 let current = ALLOCATED.fetch_add(size, Ordering::Relaxed) + size;
40 PEAK.fetch_max(current, Ordering::Relaxed);
41}
42
43#[cfg(not(feature = "jemalloc"))]
44#[inline]
45fn record_dealloc(size: usize) {
46 ALLOCATED.fetch_sub(size, Ordering::Relaxed);
47}
48
49#[cfg(not(feature = "jemalloc"))]
50unsafe impl GlobalAlloc for TrackingAlloc {
54 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
55 let ptr = unsafe { System.alloc(layout) };
58 if !ptr.is_null() {
59 record_alloc(layout.size());
60 }
61 ptr
62 }
63
64 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
65 let ptr = unsafe { System.alloc_zeroed(layout) };
68 if !ptr.is_null() {
69 record_alloc(layout.size());
70 }
71 ptr
72 }
73
74 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
75 record_dealloc(layout.size());
76 unsafe { System.dealloc(ptr, layout) };
79 }
80
81 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
82 let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
85 if !new_ptr.is_null() {
86 match new_size.cmp(&layout.size()) {
87 std::cmp::Ordering::Greater => record_alloc(new_size - layout.size()),
88 std::cmp::Ordering::Less => record_dealloc(layout.size() - new_size),
89 std::cmp::Ordering::Equal => {}
90 }
91 }
92 new_ptr
93 }
94}
95
96#[cfg(not(feature = "jemalloc"))]
98#[expect(
99 clippy::cast_precision_loss,
100 reason = "Allocator metrics are approximate diagnostics reported as f64 MiB values."
101)]
102#[must_use]
103pub fn stats() -> (f64, f64) {
104 let allocated = ALLOCATED.load(Ordering::Relaxed) as f64 / 1_048_576.0;
105 let peak = PEAK.load(Ordering::Relaxed) as f64 / 1_048_576.0;
106 (allocated, peak)
107}
108
109#[cfg(feature = "jemalloc-stats")]
111#[expect(
112 clippy::cast_precision_loss,
113 reason = "Allocator metrics are approximate diagnostics reported as f64 MiB values."
114)]
115#[must_use]
116pub fn stats() -> (f64, f64) {
117 if let Err(error) = tikv_jemalloc_ctl::epoch::advance() {
118 tracing::warn!(error = %error, "failed to refresh jemalloc stats epoch");
119 }
120
121 let allocated = tikv_jemalloc_ctl::stats::allocated::read().unwrap_or(0) as f64 / 1_048_576.0;
122 let resident = tikv_jemalloc_ctl::stats::resident::read().unwrap_or(0) as f64 / 1_048_576.0;
123 (allocated, resident)
124}
125
126#[cfg(all(feature = "jemalloc", not(feature = "jemalloc-stats")))]
128pub fn stats() -> (f64, f64) {
129 (0.0, 0.0)
130}
131
132#[cfg(test)]
133mod tests {
134 #[cfg(not(feature = "jemalloc"))]
135 use std::{
136 alloc::{GlobalAlloc, Layout},
137 ptr,
138 sync::{Mutex, MutexGuard},
139 };
140
141 #[cfg(not(feature = "jemalloc"))]
142 static TEST_ALLOC_LOCK: Mutex<()> = Mutex::new(());
143
144 #[cfg(not(feature = "jemalloc"))]
145 fn reset_tracking_counters() -> MutexGuard<'static, ()> {
146 let guard = TEST_ALLOC_LOCK.lock().unwrap();
147 super::ALLOCATED.store(0, std::sync::atomic::Ordering::Relaxed);
148 super::PEAK.store(0, std::sync::atomic::Ordering::Relaxed);
149 guard
150 }
151
152 #[cfg(not(feature = "jemalloc"))]
153 fn assert_stats_bytes(allocated: usize, peak: usize) {
154 let (allocated_mib, peak_mib) = super::stats();
155
156 assert_eq!(allocated_mib, allocated as f64 / 1_048_576.0);
157 assert_eq!(peak_mib, peak as f64 / 1_048_576.0);
158 }
159
160 #[cfg(not(feature = "jemalloc"))]
161 #[test]
162 fn stats_returns_non_negative_counters() {
163 let (allocated, peak) = super::stats();
164
165 assert!(allocated >= 0.0);
166 assert!(peak >= 0.0);
167 }
168
169 #[cfg(not(feature = "jemalloc"))]
170 #[test]
171 fn record_alloc_updates_current_and_peak_bytes() {
172 let _guard = reset_tracking_counters();
173
174 super::record_alloc(256);
175 assert_stats_bytes(256, 256);
176
177 super::record_alloc(128);
178 assert_stats_bytes(384, 384);
179 }
180
181 #[cfg(not(feature = "jemalloc"))]
182 #[test]
183 fn record_dealloc_reduces_current_without_lowering_peak() {
184 let _guard = reset_tracking_counters();
185
186 super::record_alloc(512);
187 super::record_dealloc(128);
188
189 assert_stats_bytes(384, 512);
190 }
191
192 #[cfg(not(feature = "jemalloc"))]
193 #[test]
194 fn tracking_alloc_records_alloc_and_dealloc() {
195 let _guard = reset_tracking_counters();
196 let allocator = super::TrackingAlloc;
197 let layout = Layout::from_size_align(64, 8).unwrap();
198
199 let ptr = unsafe { allocator.alloc(layout) };
202 assert!(!ptr.is_null());
203 assert_stats_bytes(64, 64);
204
205 unsafe { allocator.dealloc(ptr, layout) };
208 assert_stats_bytes(0, 64);
209 }
210
211 #[cfg(not(feature = "jemalloc"))]
212 #[test]
213 fn tracking_alloc_zeroed_returns_zeroed_memory_and_records_size() {
214 let _guard = reset_tracking_counters();
215 let allocator = super::TrackingAlloc;
216 let layout = Layout::from_size_align(32, 8).unwrap();
217
218 let ptr = unsafe { allocator.alloc_zeroed(layout) };
221 assert!(!ptr.is_null());
222
223 let bytes = unsafe { std::slice::from_raw_parts(ptr, layout.size()) };
226 assert!(bytes.iter().all(|byte| *byte == 0));
227 assert_stats_bytes(32, 32);
228
229 unsafe { allocator.dealloc(ptr, layout) };
232 assert_stats_bytes(0, 32);
233 }
234
235 #[cfg(not(feature = "jemalloc"))]
236 #[test]
237 fn tracking_alloc_realloc_grow_and_shrink_adjusts_counters() {
238 let _guard = reset_tracking_counters();
239 let allocator = super::TrackingAlloc;
240 let initial_layout = Layout::from_size_align(16, 8).unwrap();
241
242 let ptr = unsafe { allocator.alloc(initial_layout) };
245 assert!(!ptr.is_null());
246
247 unsafe { ptr::write_bytes(ptr, 0xAB, initial_layout.size()) };
249
250 let grown_ptr = unsafe { allocator.realloc(ptr, initial_layout, 64) };
252 assert!(!grown_ptr.is_null());
253 assert_stats_bytes(64, 64);
254
255 let preserved = unsafe { std::slice::from_raw_parts(grown_ptr, initial_layout.size()) };
257 assert!(preserved.iter().all(|byte| *byte == 0xAB));
258
259 let grown_layout = Layout::from_size_align(64, 8).unwrap();
260 let shrunk_ptr = unsafe { allocator.realloc(grown_ptr, grown_layout, 24) };
263 assert!(!shrunk_ptr.is_null());
264 assert_stats_bytes(24, 64);
265
266 let shrunk_layout = Layout::from_size_align(24, 8).unwrap();
267 unsafe { allocator.dealloc(shrunk_ptr, shrunk_layout) };
270 assert_stats_bytes(0, 64);
271 }
272
273 #[cfg(not(feature = "jemalloc"))]
274 #[test]
275 fn tracking_alloc_realloc_same_size_leaves_counters_unchanged() {
276 let _guard = reset_tracking_counters();
277 let allocator = super::TrackingAlloc;
278 let layout = Layout::from_size_align(40, 8).unwrap();
279
280 let ptr = unsafe { allocator.alloc(layout) };
283 assert!(!ptr.is_null());
284 assert_stats_bytes(40, 40);
285
286 let same_size_ptr = unsafe { allocator.realloc(ptr, layout, layout.size()) };
289 assert!(!same_size_ptr.is_null());
290 assert_stats_bytes(40, 40);
291
292 unsafe { allocator.dealloc(same_size_ptr, layout) };
295 assert_stats_bytes(0, 40);
296 }
297
298 #[cfg(all(feature = "jemalloc", not(feature = "jemalloc-stats")))]
299 #[test]
300 fn jemalloc_without_stats_returns_zeroes() {
301 assert_eq!(super::stats(), (0.0, 0.0));
302 }
303}