1#![deny(unsafe_op_in_unsafe_fn)]
9#![deny(clippy::undocumented_unsafe_blocks)]
10#![cfg_attr(
11 not(test),
12 deny(
13 clippy::unwrap_used,
14 clippy::unreachable,
15 clippy::expect_used,
16 clippy::panic,
17 clippy::unimplemented,
18 clippy::todo
19 )
20)]
21
22#[cfg(not(feature = "jemalloc"))]
23use std::alloc::{GlobalAlloc, Layout, System};
24#[cfg(not(feature = "jemalloc"))]
25use std::sync::atomic::{AtomicUsize, Ordering};
26
27#[cfg(not(feature = "jemalloc"))]
29pub static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
30#[cfg(not(feature = "jemalloc"))]
32pub static PEAK: AtomicUsize = AtomicUsize::new(0);
33
34#[cfg(not(feature = "jemalloc"))]
36pub struct TrackingAlloc;
37
38#[cfg(not(feature = "jemalloc"))]
39#[inline]
40fn record_alloc(size: usize) {
41 let current = ALLOCATED.fetch_add(size, Ordering::Relaxed) + size;
42 PEAK.fetch_max(current, Ordering::Relaxed);
43}
44
45#[cfg(not(feature = "jemalloc"))]
46#[inline]
47fn record_dealloc(size: usize) {
48 ALLOCATED.fetch_sub(size, Ordering::Relaxed);
49}
50
51#[cfg(not(feature = "jemalloc"))]
52unsafe impl GlobalAlloc for TrackingAlloc {
56 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
57 let ptr = unsafe { System.alloc(layout) };
60 if !ptr.is_null() {
61 record_alloc(layout.size());
62 }
63 ptr
64 }
65
66 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
67 let ptr = unsafe { System.alloc_zeroed(layout) };
70 if !ptr.is_null() {
71 record_alloc(layout.size());
72 }
73 ptr
74 }
75
76 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
77 record_dealloc(layout.size());
78 unsafe { System.dealloc(ptr, layout) };
81 }
82
83 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
84 let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
87 if !new_ptr.is_null() {
88 match new_size.cmp(&layout.size()) {
89 std::cmp::Ordering::Greater => record_alloc(new_size - layout.size()),
90 std::cmp::Ordering::Less => record_dealloc(layout.size() - new_size),
91 std::cmp::Ordering::Equal => {}
92 }
93 }
94 new_ptr
95 }
96}
97
98#[cfg(not(feature = "jemalloc"))]
100pub fn stats() -> (f64, f64) {
101 let allocated = ALLOCATED.load(Ordering::Relaxed) as f64 / 1_048_576.0;
102 let peak = PEAK.load(Ordering::Relaxed) as f64 / 1_048_576.0;
103 (allocated, peak)
104}
105
106#[cfg(feature = "jemalloc-stats")]
108pub fn stats() -> (f64, f64) {
109 if let Err(error) = tikv_jemalloc_ctl::epoch::advance() {
110 tracing::warn!(error = %error, "failed to refresh jemalloc stats epoch");
111 }
112
113 let allocated = tikv_jemalloc_ctl::stats::allocated::read().unwrap_or(0) as f64 / 1_048_576.0;
114 let resident = tikv_jemalloc_ctl::stats::resident::read().unwrap_or(0) as f64 / 1_048_576.0;
115 (allocated, resident)
116}
117
118#[cfg(all(feature = "jemalloc", not(feature = "jemalloc-stats")))]
120pub fn stats() -> (f64, f64) {
121 (0.0, 0.0)
122}
123
124#[cfg(test)]
125mod tests {
126 #[cfg(not(feature = "jemalloc"))]
127 use std::{
128 alloc::{GlobalAlloc, Layout},
129 ptr,
130 sync::{Mutex, MutexGuard},
131 };
132
133 #[cfg(not(feature = "jemalloc"))]
134 static TEST_ALLOC_LOCK: Mutex<()> = Mutex::new(());
135
136 #[cfg(not(feature = "jemalloc"))]
137 fn reset_tracking_counters() -> MutexGuard<'static, ()> {
138 let guard = TEST_ALLOC_LOCK.lock().unwrap();
139 super::ALLOCATED.store(0, std::sync::atomic::Ordering::Relaxed);
140 super::PEAK.store(0, std::sync::atomic::Ordering::Relaxed);
141 guard
142 }
143
144 #[cfg(not(feature = "jemalloc"))]
145 fn assert_stats_bytes(allocated: usize, peak: usize) {
146 let (allocated_mib, peak_mib) = super::stats();
147
148 assert_eq!(allocated_mib, allocated as f64 / 1_048_576.0);
149 assert_eq!(peak_mib, peak as f64 / 1_048_576.0);
150 }
151
152 #[cfg(not(feature = "jemalloc"))]
153 #[test]
154 fn stats_returns_non_negative_counters() {
155 let (allocated, peak) = super::stats();
156
157 assert!(allocated >= 0.0);
158 assert!(peak >= 0.0);
159 }
160
161 #[cfg(not(feature = "jemalloc"))]
162 #[test]
163 fn record_alloc_updates_current_and_peak_bytes() {
164 let _guard = reset_tracking_counters();
165
166 super::record_alloc(256);
167 assert_stats_bytes(256, 256);
168
169 super::record_alloc(128);
170 assert_stats_bytes(384, 384);
171 }
172
173 #[cfg(not(feature = "jemalloc"))]
174 #[test]
175 fn record_dealloc_reduces_current_without_lowering_peak() {
176 let _guard = reset_tracking_counters();
177
178 super::record_alloc(512);
179 super::record_dealloc(128);
180
181 assert_stats_bytes(384, 512);
182 }
183
184 #[cfg(not(feature = "jemalloc"))]
185 #[test]
186 fn tracking_alloc_records_alloc_and_dealloc() {
187 let _guard = reset_tracking_counters();
188 let allocator = super::TrackingAlloc;
189 let layout = Layout::from_size_align(64, 8).unwrap();
190
191 let ptr = unsafe { allocator.alloc(layout) };
194 assert!(!ptr.is_null());
195 assert_stats_bytes(64, 64);
196
197 unsafe { allocator.dealloc(ptr, layout) };
200 assert_stats_bytes(0, 64);
201 }
202
203 #[cfg(not(feature = "jemalloc"))]
204 #[test]
205 fn tracking_alloc_zeroed_returns_zeroed_memory_and_records_size() {
206 let _guard = reset_tracking_counters();
207 let allocator = super::TrackingAlloc;
208 let layout = Layout::from_size_align(32, 8).unwrap();
209
210 let ptr = unsafe { allocator.alloc_zeroed(layout) };
213 assert!(!ptr.is_null());
214
215 let bytes = unsafe { std::slice::from_raw_parts(ptr, layout.size()) };
218 assert!(bytes.iter().all(|byte| *byte == 0));
219 assert_stats_bytes(32, 32);
220
221 unsafe { allocator.dealloc(ptr, layout) };
224 assert_stats_bytes(0, 32);
225 }
226
227 #[cfg(not(feature = "jemalloc"))]
228 #[test]
229 fn tracking_alloc_realloc_grow_and_shrink_adjusts_counters() {
230 let _guard = reset_tracking_counters();
231 let allocator = super::TrackingAlloc;
232 let initial_layout = Layout::from_size_align(16, 8).unwrap();
233
234 let ptr = unsafe { allocator.alloc(initial_layout) };
237 assert!(!ptr.is_null());
238
239 unsafe { ptr::write_bytes(ptr, 0xAB, initial_layout.size()) };
241
242 let grown_ptr = unsafe { allocator.realloc(ptr, initial_layout, 64) };
244 assert!(!grown_ptr.is_null());
245 assert_stats_bytes(64, 64);
246
247 let preserved = unsafe { std::slice::from_raw_parts(grown_ptr, initial_layout.size()) };
249 assert!(preserved.iter().all(|byte| *byte == 0xAB));
250
251 let grown_layout = Layout::from_size_align(64, 8).unwrap();
252 let shrunk_ptr = unsafe { allocator.realloc(grown_ptr, grown_layout, 24) };
255 assert!(!shrunk_ptr.is_null());
256 assert_stats_bytes(24, 64);
257
258 let shrunk_layout = Layout::from_size_align(24, 8).unwrap();
259 unsafe { allocator.dealloc(shrunk_ptr, shrunk_layout) };
262 assert_stats_bytes(0, 64);
263 }
264
265 #[cfg(not(feature = "jemalloc"))]
266 #[test]
267 fn tracking_alloc_realloc_same_size_leaves_counters_unchanged() {
268 let _guard = reset_tracking_counters();
269 let allocator = super::TrackingAlloc;
270 let layout = Layout::from_size_align(40, 8).unwrap();
271
272 let ptr = unsafe { allocator.alloc(layout) };
275 assert!(!ptr.is_null());
276 assert_stats_bytes(40, 40);
277
278 let same_size_ptr = unsafe { allocator.realloc(ptr, layout, layout.size()) };
281 assert!(!same_size_ptr.is_null());
282 assert_stats_bytes(40, 40);
283
284 unsafe { allocator.dealloc(same_size_ptr, layout) };
287 assert_stats_bytes(0, 40);
288 }
289
290 #[cfg(all(feature = "jemalloc", not(feature = "jemalloc-stats")))]
291 #[test]
292 fn jemalloc_without_stats_returns_zeroes() {
293 assert_eq!(super::stats(), (0.0, 0.0));
294 }
295}