mnemosyne_backend/
recorders.rs1use core::sync::atomic::{AtomicUsize, Ordering};
10
11static CURRENT_MAPPED_BYTES: AtomicUsize = AtomicUsize::new(0);
12static PEAK_MAPPED_BYTES: AtomicUsize = AtomicUsize::new(0);
13static MAP_CALLS: AtomicUsize = AtomicUsize::new(0);
14static UNMAP_CALLS: AtomicUsize = AtomicUsize::new(0);
15static PAGE_RESET_CALLS: AtomicUsize = AtomicUsize::new(0);
16static PAGE_RESET_BYTES: AtomicUsize = AtomicUsize::new(0);
17static GUARD_INSTALL_CALLS: AtomicUsize = AtomicUsize::new(0);
18static GUARD_INSTALL_BYTES: AtomicUsize = AtomicUsize::new(0);
19static DECOMMIT_CALLS: AtomicUsize = AtomicUsize::new(0);
20static DECOMMIT_BYTES: AtomicUsize = AtomicUsize::new(0);
21
22#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
24pub struct BackendMemoryStats {
25 pub current_mapped_bytes: usize,
26 pub peak_mapped_bytes: usize,
27 pub map_calls: usize,
28 pub unmap_calls: usize,
29 pub page_reset_calls: usize,
35 pub page_reset_bytes: usize,
37 pub guard_install_calls: usize,
44 pub guard_install_bytes: usize,
46 pub decommit_calls: usize,
53 pub decommit_bytes: usize,
56}
57
58#[inline]
59pub(crate) fn record_map(size: usize) {
60 MAP_CALLS.fetch_add(1, Ordering::Relaxed);
61 let current = CURRENT_MAPPED_BYTES.fetch_add(size, Ordering::Relaxed) + size;
62 if current > PEAK_MAPPED_BYTES.load(Ordering::Relaxed) {
63 PEAK_MAPPED_BYTES.fetch_max(current, Ordering::Relaxed);
64 }
65}
66
67#[inline]
68pub(crate) fn record_unmap(size: usize) {
69 UNMAP_CALLS.fetch_add(1, Ordering::Relaxed);
70 CURRENT_MAPPED_BYTES.fetch_sub(size, Ordering::Relaxed);
71}
72
73#[inline]
78pub(crate) fn record_unmap_failure() {
79 UNMAP_CALLS.fetch_add(1, Ordering::Relaxed);
80}
81
82#[inline]
89pub(crate) fn record_page_reset(size: usize) {
90 PAGE_RESET_CALLS.fetch_add(1, Ordering::Relaxed);
91 PAGE_RESET_BYTES.fetch_add(size, Ordering::Relaxed);
92}
93
94#[inline]
101pub(crate) fn record_guard_install(size: usize) {
102 GUARD_INSTALL_CALLS.fetch_add(1, Ordering::Relaxed);
103 GUARD_INSTALL_BYTES.fetch_add(size, Ordering::Relaxed);
104}
105
106#[inline]
112pub(crate) fn record_decommit(size: usize) {
113 DECOMMIT_CALLS.fetch_add(1, Ordering::Relaxed);
114 DECOMMIT_BYTES.fetch_add(size, Ordering::Relaxed);
115}
116
117pub fn backend_memory_stats() -> BackendMemoryStats {
122 BackendMemoryStats {
123 current_mapped_bytes: CURRENT_MAPPED_BYTES.load(Ordering::Relaxed),
124 peak_mapped_bytes: PEAK_MAPPED_BYTES.load(Ordering::Relaxed),
125 map_calls: MAP_CALLS.load(Ordering::Relaxed),
126 unmap_calls: UNMAP_CALLS.load(Ordering::Relaxed),
127 page_reset_calls: PAGE_RESET_CALLS.load(Ordering::Relaxed),
128 page_reset_bytes: PAGE_RESET_BYTES.load(Ordering::Relaxed),
129 guard_install_calls: GUARD_INSTALL_CALLS.load(Ordering::Relaxed),
130 guard_install_bytes: GUARD_INSTALL_BYTES.load(Ordering::Relaxed),
131 decommit_calls: DECOMMIT_CALLS.load(Ordering::Relaxed),
132 decommit_bytes: DECOMMIT_BYTES.load(Ordering::Relaxed),
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 extern crate std;
139
140 use super::*;
141
142 static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
143
144 #[test]
145 fn mapping_telemetry_tracks_deltas_and_peak() {
146 let _guard = TEST_LOCK
147 .lock()
148 .expect("backend telemetry test lock was poisoned");
149 let before = backend_memory_stats();
150 let size = 4096;
151
152 record_map(size);
153 let during = backend_memory_stats();
154
155 assert_eq!(
156 during.current_mapped_bytes,
157 before.current_mapped_bytes + size
158 );
159 assert_eq!(during.map_calls, before.map_calls + 1);
160 assert_eq!(during.unmap_calls, before.unmap_calls);
161 assert!(
162 during.peak_mapped_bytes >= during.current_mapped_bytes,
163 "peak {} below current {} after record_map",
164 during.peak_mapped_bytes,
165 during.current_mapped_bytes
166 );
167 assert!(
168 during.peak_mapped_bytes >= before.peak_mapped_bytes,
169 "peak {} below pre-map peak {}",
170 during.peak_mapped_bytes,
171 before.peak_mapped_bytes
172 );
173
174 record_unmap(size);
175 let after = backend_memory_stats();
176
177 assert_eq!(after.current_mapped_bytes, before.current_mapped_bytes);
178 assert_eq!(after.map_calls, before.map_calls + 1);
179 assert_eq!(after.unmap_calls, before.unmap_calls + 1);
180 assert!(
181 after.peak_mapped_bytes >= during.peak_mapped_bytes,
182 "peak {} regressed below mid-cycle peak {}",
183 after.peak_mapped_bytes,
184 during.peak_mapped_bytes
185 );
186 }
187
188 #[test]
189 fn failed_release_increments_call_count_without_byte_delta() {
190 let _guard = TEST_LOCK
191 .lock()
192 .expect("backend telemetry test lock was poisoned");
193 let before = backend_memory_stats();
194 let size = 4096;
195
196 record_map(size);
197 let mapped = backend_memory_stats();
198 assert_eq!(
199 mapped.current_mapped_bytes,
200 before.current_mapped_bytes + size
201 );
202
203 record_unmap_failure();
206 let failed = backend_memory_stats();
207 assert_eq!(failed.current_mapped_bytes, mapped.current_mapped_bytes);
208 assert_eq!(failed.unmap_calls, mapped.unmap_calls + 1);
209 assert_eq!(failed.map_calls, mapped.map_calls);
210
211 record_unmap(size);
212 let cleared = backend_memory_stats();
213 assert_eq!(cleared.current_mapped_bytes, before.current_mapped_bytes);
214 }
215
216 #[test]
217 fn page_reset_telemetry_increments_call_and_byte_counters_only() {
218 let _guard = TEST_LOCK
219 .lock()
220 .expect("backend telemetry test lock was poisoned");
221 let before = backend_memory_stats();
225 let size = 8192;
226
227 record_page_reset(size);
228 let after = backend_memory_stats();
229
230 assert_eq!(
231 after.page_reset_calls,
232 before.page_reset_calls + 1,
233 "page_reset_calls counter did not advance"
234 );
235 assert_eq!(
236 after.page_reset_bytes,
237 before.page_reset_bytes + size,
238 "page_reset_bytes counter did not advance by the reset size"
239 );
240 assert_eq!(
241 after.current_mapped_bytes, before.current_mapped_bytes,
242 "page_reset must not decrement current_mapped_bytes"
243 );
244 assert_eq!(
245 after.unmap_calls, before.unmap_calls,
246 "page_reset must not increment unmap_calls"
247 );
248 }
249
250 #[test]
251 fn decommit_telemetry_increments_call_and_byte_counters_only() {
252 let _guard = TEST_LOCK
253 .lock()
254 .expect("backend telemetry test lock was poisoned");
255 let before = backend_memory_stats();
259 let size = 64 * 1024;
260
261 record_decommit(size);
262 let after = backend_memory_stats();
263
264 assert_eq!(
265 after.decommit_calls,
266 before.decommit_calls + 1,
267 "decommit_calls counter did not advance"
268 );
269 assert_eq!(
270 after.decommit_bytes,
271 before.decommit_bytes + size,
272 "decommit_bytes counter did not advance by the decommit size"
273 );
274 assert_eq!(
275 after.current_mapped_bytes, before.current_mapped_bytes,
276 "decommit must not decrement current_mapped_bytes (reservation persists)"
277 );
278 assert_eq!(after.unmap_calls, before.unmap_calls);
279 assert_eq!(after.page_reset_calls, before.page_reset_calls);
280 }
281
282 #[test]
283 fn guard_telemetry_increments_call_and_byte_counters_only() {
284 let _guard = TEST_LOCK
285 .lock()
286 .expect("backend telemetry test lock was poisoned");
287 let before = backend_memory_stats();
290 let size = 4096;
291 record_guard_install(size);
292 let after = backend_memory_stats();
293 assert_eq!(
294 after.guard_install_calls,
295 before.guard_install_calls + 1,
296 "guard_install_calls counter did not advance"
297 );
298 assert_eq!(
299 after.guard_install_bytes,
300 before.guard_install_bytes + size,
301 "guard_install_bytes counter did not advance by the guard size"
302 );
303 assert_eq!(
304 after.current_mapped_bytes, before.current_mapped_bytes,
305 "make_guard must not decrement current_mapped_bytes"
306 );
307 assert_eq!(after.page_reset_calls, before.page_reset_calls);
308 assert_eq!(after.unmap_calls, before.unmap_calls);
309 }
310}