mnemosyne_backend/recorders/
stats.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);
21static PURGE_BYTES: AtomicUsize = AtomicUsize::new(0);
23static HUGEPAGE_HINT_CALLS: AtomicUsize = AtomicUsize::new(0);
24
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27pub struct BackendMemoryStats {
28 pub current_mapped_bytes: usize,
34 pub peak_mapped_bytes: usize,
40 pub map_calls: usize,
45 pub unmap_calls: usize,
47 pub page_reset_calls: usize,
53 pub page_reset_bytes: usize,
55 pub guard_install_calls: usize,
62 pub guard_install_bytes: usize,
64 pub decommit_calls: usize,
71 pub decommit_bytes: usize,
74 pub purged_bytes: usize,
81 pub hugepage_hint_calls: usize,
91}
92
93#[inline(always)]
99fn record_counter_and_bytes(calls: &AtomicUsize, bytes: &AtomicUsize, size: usize) {
100 calls.fetch_add(1, Ordering::Relaxed);
101 bytes.fetch_add(size, Ordering::Relaxed);
102}
103
104#[inline]
105pub(crate) fn record_map(size: usize) {
106 MAP_CALLS.fetch_add(1, Ordering::Relaxed);
107 let current = CURRENT_MAPPED_BYTES.fetch_add(size, Ordering::Relaxed) + size;
108 if current > PEAK_MAPPED_BYTES.load(Ordering::Relaxed) {
109 PEAK_MAPPED_BYTES.fetch_max(current, Ordering::Relaxed);
110 }
111}
112
113#[inline]
114pub(crate) fn record_unmap(size: usize) {
115 UNMAP_CALLS.fetch_add(1, Ordering::Relaxed);
116 CURRENT_MAPPED_BYTES.fetch_sub(size, Ordering::Relaxed);
117}
118
119#[inline]
124pub(crate) fn record_unmap_failure() {
125 UNMAP_CALLS.fetch_add(1, Ordering::Relaxed);
126}
127
128#[inline]
129pub(crate) fn record_page_reset(size: usize) {
130 record_counter_and_bytes(&PAGE_RESET_CALLS, &PAGE_RESET_BYTES, size);
131}
132
133#[inline]
135pub(crate) fn record_guard_install(size: usize) {
136 record_counter_and_bytes(&GUARD_INSTALL_CALLS, &GUARD_INSTALL_BYTES, size);
137}
138
139#[inline]
141pub(crate) fn record_decommit(size: usize) {
142 record_counter_and_bytes(&DECOMMIT_CALLS, &DECOMMIT_BYTES, size);
143}
144
145#[cfg(all(target_os = "linux", not(miri)))]
152#[inline]
153pub(crate) fn record_hugepage_hint() {
154 HUGEPAGE_HINT_CALLS.fetch_add(1, Ordering::Relaxed);
155}
156
157#[cfg(all(target_os = "linux", not(miri)))]
165#[inline]
166pub(crate) fn record_purge_only(size: usize) {
167 PURGE_BYTES.fetch_add(size, Ordering::Relaxed);
168}
169
170pub fn backend_memory_stats() -> BackendMemoryStats {
175 BackendMemoryStats {
176 current_mapped_bytes: CURRENT_MAPPED_BYTES.load(Ordering::Relaxed),
177 peak_mapped_bytes: PEAK_MAPPED_BYTES.load(Ordering::Relaxed),
178 map_calls: MAP_CALLS.load(Ordering::Relaxed),
179 unmap_calls: UNMAP_CALLS.load(Ordering::Relaxed),
180 page_reset_calls: PAGE_RESET_CALLS.load(Ordering::Relaxed),
181 page_reset_bytes: PAGE_RESET_BYTES.load(Ordering::Relaxed),
182 guard_install_calls: GUARD_INSTALL_CALLS.load(Ordering::Relaxed),
183 guard_install_bytes: GUARD_INSTALL_BYTES.load(Ordering::Relaxed),
184 decommit_calls: DECOMMIT_CALLS.load(Ordering::Relaxed),
185 decommit_bytes: DECOMMIT_BYTES.load(Ordering::Relaxed),
186 purged_bytes: PURGE_BYTES.load(Ordering::Relaxed),
187 hugepage_hint_calls: HUGEPAGE_HINT_CALLS.load(Ordering::Relaxed),
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 extern crate std;
194
195 use super::*;
196 use crate::test_support::lock_test;
197
198 #[test]
199 fn mapping_telemetry_tracks_deltas_and_peak() {
200 let _guard = lock_test();
201 let before = backend_memory_stats();
202 let size = 4096;
203
204 record_map(size);
205 let during = backend_memory_stats();
206
207 assert_eq!(
208 during.current_mapped_bytes,
209 before.current_mapped_bytes + size
210 );
211 assert_eq!(during.map_calls, before.map_calls + 1);
212 assert_eq!(during.unmap_calls, before.unmap_calls);
213 assert!(
214 during.peak_mapped_bytes >= during.current_mapped_bytes,
215 "peak {} below current {} after record_map",
216 during.peak_mapped_bytes,
217 during.current_mapped_bytes
218 );
219 assert!(
220 during.peak_mapped_bytes >= before.peak_mapped_bytes,
221 "peak {} below pre-map peak {}",
222 during.peak_mapped_bytes,
223 before.peak_mapped_bytes
224 );
225
226 record_unmap(size);
227 let after = backend_memory_stats();
228
229 assert_eq!(after.current_mapped_bytes, before.current_mapped_bytes);
230 assert_eq!(after.map_calls, before.map_calls + 1);
231 assert_eq!(after.unmap_calls, before.unmap_calls + 1);
232 assert!(
233 after.peak_mapped_bytes >= during.peak_mapped_bytes,
234 "peak {} regressed below mid-cycle peak {}",
235 after.peak_mapped_bytes,
236 during.peak_mapped_bytes
237 );
238 }
239
240 #[test]
241 fn failed_release_increments_call_count_without_byte_delta() {
242 let _guard = lock_test();
243 let before = backend_memory_stats();
244 let size = 4096;
245
246 record_map(size);
247 let mapped = backend_memory_stats();
248 assert_eq!(
249 mapped.current_mapped_bytes,
250 before.current_mapped_bytes + size
251 );
252
253 record_unmap_failure();
256 let failed = backend_memory_stats();
257 assert_eq!(failed.current_mapped_bytes, mapped.current_mapped_bytes);
258 assert_eq!(failed.unmap_calls, mapped.unmap_calls + 1);
259 assert_eq!(failed.map_calls, mapped.map_calls);
260
261 record_unmap(size);
262 let cleared = backend_memory_stats();
263 assert_eq!(cleared.current_mapped_bytes, before.current_mapped_bytes);
264 }
265
266 #[test]
267 fn page_reset_telemetry_increments_call_and_byte_counters_only() {
268 let _guard = lock_test();
269 let before = backend_memory_stats();
273 let size = 8192;
274
275 record_page_reset(size);
276 let after = backend_memory_stats();
277
278 assert_eq!(
279 after.page_reset_calls,
280 before.page_reset_calls + 1,
281 "page_reset_calls counter did not advance"
282 );
283 assert_eq!(
284 after.page_reset_bytes,
285 before.page_reset_bytes + size,
286 "page_reset_bytes counter did not advance by the reset size"
287 );
288 assert_eq!(
289 after.current_mapped_bytes, before.current_mapped_bytes,
290 "page_reset must not decrement current_mapped_bytes"
291 );
292 assert_eq!(
293 after.unmap_calls, before.unmap_calls,
294 "page_reset must not increment unmap_calls"
295 );
296 }
297
298 #[test]
299 fn decommit_telemetry_increments_call_and_byte_counters_only() {
300 let _guard = lock_test();
301 let before = backend_memory_stats();
305 let size = 64 * 1024;
306
307 record_decommit(size);
308 let after = backend_memory_stats();
309
310 assert_eq!(
311 after.decommit_calls,
312 before.decommit_calls + 1,
313 "decommit_calls counter did not advance"
314 );
315 assert_eq!(
316 after.decommit_bytes,
317 before.decommit_bytes + size,
318 "decommit_bytes counter did not advance by the decommit size"
319 );
320 assert_eq!(
321 after.current_mapped_bytes, before.current_mapped_bytes,
322 "decommit must not decrement current_mapped_bytes (reservation persists)"
323 );
324 assert_eq!(after.unmap_calls, before.unmap_calls);
325 assert_eq!(after.page_reset_calls, before.page_reset_calls);
326 }
327
328 #[cfg(all(target_os = "linux", not(miri)))]
331 #[test]
332 fn hugepage_hint_telemetry_increments_only_its_own_call_counter() {
333 let _guard = lock_test();
334 let before = backend_memory_stats();
337
338 record_hugepage_hint();
339 let after = backend_memory_stats();
340
341 assert_eq!(
342 after.hugepage_hint_calls,
343 before.hugepage_hint_calls + 1,
344 "hugepage_hint_calls counter did not advance"
345 );
346 assert_eq!(
347 after.current_mapped_bytes, before.current_mapped_bytes,
348 "an advisory hint must not move the mapped-byte accounting"
349 );
350 assert_eq!(after.map_calls, before.map_calls);
351 assert_eq!(after.page_reset_calls, before.page_reset_calls);
352 assert_eq!(after.decommit_calls, before.decommit_calls);
353 }
354
355 #[test]
356 fn guard_telemetry_increments_call_and_byte_counters_only() {
357 let _guard = lock_test();
358 let before = backend_memory_stats();
361 let size = 4096;
362 record_guard_install(size);
363 let after = backend_memory_stats();
364 assert_eq!(
365 after.guard_install_calls,
366 before.guard_install_calls + 1,
367 "guard_install_calls counter did not advance"
368 );
369 assert_eq!(
370 after.guard_install_bytes,
371 before.guard_install_bytes + size,
372 "guard_install_bytes counter did not advance by the guard size"
373 );
374 assert_eq!(
375 after.current_mapped_bytes, before.current_mapped_bytes,
376 "make_guard must not decrement current_mapped_bytes"
377 );
378 assert_eq!(after.page_reset_calls, before.page_reset_calls);
379 assert_eq!(after.unmap_calls, before.unmap_calls);
380 }
381}