mnemosyne/stats.rs
1use mnemosyne_core::NUM_SIZE_CLASSES;
2
3use crate::{LocalAllocatorSelector, SizeClassOccupancy};
4
5/// Snapshot of Mnemosyne memory mapping and segment cache state.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct MemoryStats {
8 /// Address space currently mapped from the OS, in bytes. Reserved
9 /// space, not resident: a range stays counted after its physical
10 /// backing is released, because the mapping is still held.
11 pub current_mapped_bytes: usize,
12 /// High-water mark of [`Self::current_mapped_bytes`].
13 pub peak_mapped_bytes: usize,
14 /// Successful map requests to the OS.
15 pub map_calls: usize,
16 /// Successful unmap requests to the OS.
17 pub unmap_calls: usize,
18 /// Number of confirmed backend `page_reset` calls (Linux `MADV_DONTNEED`,
19 /// macOS/FreeBSD `MADV_FREE`, Windows `VirtualAlloc(MEM_RESET)`).
20 pub page_reset_calls: usize,
21 /// Cumulative byte count passed to confirmed `page_reset` calls.
22 pub page_reset_bytes: usize,
23 /// Number of confirmed backend `make_guard` calls (Unix `mprotect(PROT_NONE)`,
24 /// Windows `VirtualProtect(PAGE_NOACCESS)`).
25 pub guard_install_calls: usize,
26 /// Cumulative byte count passed to confirmed `make_guard` calls.
27 pub guard_install_bytes: usize,
28 /// Free segments held in the cache for reuse instead of unmapped.
29 pub retained_free_segments: usize,
30 /// Cap on [`Self::retained_free_segments`]; segments beyond it are
31 /// purged rather than retained.
32 pub max_retained_free_segments: usize,
33 /// Bytes represented by the retained free segments.
34 pub retained_free_bytes: usize,
35 /// Segments returned to the OS by decay.
36 pub purged_segments: usize,
37 /// Decay purge passes performed.
38 pub purge_calls: usize,
39 /// Bytes returned to the OS by those purges.
40 pub purged_bytes: usize,
41 /// Number of segments whose physical backing was released by a
42 /// confirmed `page_reset` while the segment itself remained cached
43 /// in the retained pool.
44 pub reset_segments: usize,
45 /// Number of `reset_segment_pool` invocations.
46 pub reset_calls: usize,
47 /// Number of huge blocks currently retained in the huge-allocation cache
48 /// across all NUMA nodes.
49 pub retained_huge_blocks: usize,
50 /// Total bytes of huge blocks currently retained in the huge-allocation
51 /// cache across all NUMA nodes.
52 pub retained_huge_bytes: usize,
53 /// Allocations currently handed out by the calling thread.
54 pub current_thread_live_allocations: usize,
55 /// Segments the calling thread owns and allocates from without
56 /// coordination.
57 pub current_thread_owned_segments: usize,
58 /// Blocks freed by another thread and drained back into this
59 /// thread's pages.
60 pub cross_thread_reclaimed_blocks: usize,
61 /// Times a size class exhausted its page and acquired another; the
62 /// sum of the three sources below.
63 pub page_refills: usize,
64 /// Refills served from an already-held empty page, the cheapest
65 /// outcome.
66 pub recycled_pages: usize,
67 /// Refills that carved a new page from an owned segment.
68 pub fresh_pages: usize,
69 /// Refills that needed a new segment, the only source reaching the
70 /// OS backend.
71 pub fresh_segments: usize,
72 /// Segments inherited from threads that exited still owning them,
73 /// which keeps their memory reusable rather than stranded.
74 pub orphan_segments_adopted: usize,
75 /// Decay-sweep passes over pages looking for empties to recycle.
76 ///
77 /// Against [`Self::recycled_pages`] this shows whether sweeping is
78 /// paying for itself or scanning without finding reusable pages.
79 pub recycle_sweeps: usize,
80 /// Per-size-class occupancy for the calling thread, indexed by size
81 /// class.
82 pub size_class_occupancy: [SizeClassOccupancy; NUM_SIZE_CLASSES],
83}
84
85impl Default for MemoryStats {
86 fn default() -> Self {
87 Self {
88 current_mapped_bytes: 0,
89 peak_mapped_bytes: 0,
90 map_calls: 0,
91 unmap_calls: 0,
92 page_reset_calls: 0,
93 page_reset_bytes: 0,
94 guard_install_calls: 0,
95 guard_install_bytes: 0,
96 retained_free_segments: 0,
97 max_retained_free_segments: 0,
98 retained_free_bytes: 0,
99 purged_segments: 0,
100 purge_calls: 0,
101 purged_bytes: 0,
102 reset_segments: 0,
103 reset_calls: 0,
104 retained_huge_blocks: 0,
105 retained_huge_bytes: 0,
106 current_thread_live_allocations: 0,
107 current_thread_owned_segments: 0,
108 cross_thread_reclaimed_blocks: 0,
109 page_refills: 0,
110 recycled_pages: 0,
111 fresh_pages: 0,
112 fresh_segments: 0,
113 orphan_segments_adopted: 0,
114 recycle_sweeps: 0,
115 size_class_occupancy: [SizeClassOccupancy::default(); NUM_SIZE_CLASSES],
116 }
117 }
118}
119
120/// Returns current Mnemosyne allocator memory counters for a specific backend.
121pub fn memory_stats_generic<B: mnemosyne_arena::HasSegmentPool + LocalAllocatorSelector<B>>()
122-> MemoryStats {
123 let backend = mnemosyne_backend::backend_memory_stats();
124 let arena = mnemosyne_arena::arena_memory_stats::<B>();
125 let local = mnemosyne_local::thread_allocator_stats::<B>();
126 MemoryStats {
127 current_mapped_bytes: backend.current_mapped_bytes,
128 peak_mapped_bytes: backend.peak_mapped_bytes,
129 map_calls: backend.map_calls,
130 unmap_calls: backend.unmap_calls,
131 page_reset_calls: backend.page_reset_calls,
132 page_reset_bytes: backend.page_reset_bytes,
133 guard_install_calls: backend.guard_install_calls,
134 guard_install_bytes: backend.guard_install_bytes,
135 retained_free_segments: arena.retained_free_segments,
136 max_retained_free_segments: arena.max_retained_free_segments,
137 retained_free_bytes: arena.retained_free_bytes,
138 purged_segments: arena.purged_segments,
139 purge_calls: arena.purge_calls,
140 purged_bytes: arena.purged_bytes,
141 reset_segments: arena.reset_segments,
142 reset_calls: arena.reset_calls,
143 retained_huge_blocks: arena.retained_huge_blocks,
144 retained_huge_bytes: arena.retained_huge_bytes,
145 current_thread_live_allocations: local.current_thread_live_allocations,
146 current_thread_owned_segments: local.current_thread_owned_segments,
147 cross_thread_reclaimed_blocks: local.cross_thread_reclaimed_blocks,
148 page_refills: local.page_refills,
149 recycled_pages: local.recycled_pages,
150 fresh_pages: local.fresh_pages,
151 fresh_segments: local.fresh_segments,
152 orphan_segments_adopted: local.orphan_segments_adopted,
153 recycle_sweeps: local.recycle_sweeps,
154 size_class_occupancy: local.size_class_occupancy,
155 }
156}
157
158/// Returns current Mnemosyne allocator memory counters.
159pub fn memory_stats() -> MemoryStats {
160 memory_stats_generic::<mnemosyne_backend::MemoryBackendWrapper>()
161}
162
163/// Purges the global segment pool for a specific backend, releasing all retained/cached segments back to the OS.
164pub fn purge_generic<B: mnemosyne_arena::HasSegmentPool>() {
165 // Safety: Purging the segment pool releases only free segments that are
166 // no longer actively referenced by any thread allocator cache.
167 unsafe {
168 mnemosyne_arena::purge_segment_pool::<B>();
169 }
170}
171
172/// Purges the global segment pool, releasing all retained/cached segments back to the OS.
173pub fn purge() {
174 purge_generic::<mnemosyne_backend::MemoryBackendWrapper>();
175}
176
177/// Asks the OS to drop the physical backing of every retained free
178/// segment for a specific backend without removing them from the cache.
179///
180/// Use this as a lighter-weight RSS-reduction knob than `purge`: the
181/// segment cache stays warm so subsequent allocations skip the OS
182/// mapping syscall, while the resident memory footprint of idle
183/// segments drops to the kernel's demand-fault baseline.
184pub fn reset_generic<B: mnemosyne_arena::HasSegmentPool>() {
185 // Safety: reset_segment_pool drains the retained pool, issues
186 // page_reset on each segment's mapping, and pushes them back into
187 // the cache; no segment is released or accessed by another path.
188 unsafe {
189 mnemosyne_arena::reset_segment_pool::<B>();
190 }
191}
192
193/// Asks the OS to drop the physical backing of every retained free
194/// segment without removing them from the cache.
195pub fn reset() {
196 reset_generic::<mnemosyne_backend::MemoryBackendWrapper>();
197}
198
199/// Triggers a manual background decay and defragmentation cycle across all active memory backends.
200pub fn decay() {
201 mnemosyne_decay::decay_step();
202}