system_alloc_stats/
lib.rs

1#[cfg(feature = "fmt")]
2use std::fmt;
3use std::{
4    alloc::{GlobalAlloc, Layout, System},
5    cmp, ptr,
6    sync::atomic::{AtomicUsize, Ordering},
7};
8
9#[cfg(feature = "fmt")]
10use humansize::{format_size, BINARY};
11#[cfg(feature = "fmt")]
12use num_format::{Locale, ToFormattedString};
13
14/// The `System` allocator enhanced with stats.
15#[derive(Debug, Default, Clone, Copy)]
16pub struct SystemWithStats;
17
18/// A summary of the `System` allocator's stats.
19#[derive(Debug, Default, Clone)]
20pub struct SystemStats {
21    /// The total number of allocations.
22    pub alloc_count: usize,
23    /// The average size of allocations.
24    pub alloc_avg: Option<usize>,
25    /// The total number of deallocations.
26    pub dealloc_count: usize,
27    /// The average size of deallocations.
28    pub dealloc_avg: Option<usize>,
29    /// The total number of reallocations caused by object growth.
30    pub realloc_growth_count: usize,
31    /// The average size of reallocations caused by object growth.
32    pub realloc_growth_avg: Option<usize>,
33    /// The total number of reallocations caused by object shrinkage.
34    pub realloc_shrink_count: usize,
35    /// The average size of reallocations caused by object shrinkage.
36    pub realloc_shrink_avg: Option<usize>,
37    /// Current heap use.
38    pub use_curr: usize,
39    /// Maximum recorded heap use.
40    pub use_max: usize,
41}
42
43#[cfg(feature = "fmt")]
44impl fmt::Display for SystemStats {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        writeln!(f, "SystemStats {{")?;
47        writeln!(
48            f,
49            "\talloc_count: {}",
50            self.alloc_count.to_formatted_string(&Locale::en)
51        )?;
52        if let Some(alloc_avg) = self.alloc_avg {
53            writeln!(f, "\talloc_avg: {}", format_size(alloc_avg, BINARY))?;
54        }
55        writeln!(
56            f,
57            "\tdealloc_count: {}",
58            self.dealloc_count.to_formatted_string(&Locale::en)
59        )?;
60        if let Some(dealloc_avg) = self.dealloc_avg {
61            writeln!(f, "\tdealloc_avg: {}", format_size(dealloc_avg, BINARY))?;
62        }
63        writeln!(
64            f,
65            "\trealloc_growth_count: {}",
66            self.realloc_growth_count.to_formatted_string(&Locale::en)
67        )?;
68        if let Some(realloc_growth_avg) = self.realloc_growth_avg {
69            writeln!(
70                f,
71                "\trealloc_growth_avg: {}",
72                format_size(realloc_growth_avg, BINARY)
73            )?;
74        }
75        writeln!(
76            f,
77            "\trealloc_shrink_count: {}",
78            self.realloc_shrink_count.to_formatted_string(&Locale::en)
79        )?;
80        if let Some(realloc_shrink_avg) = self.realloc_shrink_avg {
81            writeln!(
82                f,
83                "\trealloc_shrink_avg: {}",
84                format_size(realloc_shrink_avg, BINARY)
85            )?;
86        }
87        writeln!(f, "\tuse_curr: {}", format_size(self.use_curr, BINARY))?;
88        writeln!(f, "\tuse_max: {}", format_size(self.use_max, BINARY))?;
89        writeln!(f, "}}")
90    }
91}
92
93static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
94static ALLOC_SUM: AtomicUsize = AtomicUsize::new(0);
95static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
96static DEALLOC_SUM: AtomicUsize = AtomicUsize::new(0);
97static REALLOC_GROWTH_COUNT: AtomicUsize = AtomicUsize::new(0);
98static REALLOC_GROWTH_SUM: AtomicUsize = AtomicUsize::new(0);
99static REALLOC_SHRINK_COUNT: AtomicUsize = AtomicUsize::new(0);
100static REALLOC_SHRINK_SUM: AtomicUsize = AtomicUsize::new(0);
101static USE_CURR: AtomicUsize = AtomicUsize::new(0);
102static USE_MAX: AtomicUsize = AtomicUsize::new(0);
103
104impl SystemWithStats {
105    /// Returns the total number of allocations.
106    pub fn alloc_count(&self) -> usize {
107        ALLOC_COUNT.load(Ordering::Relaxed)
108    }
109
110    /// Returns the sum of all allocations.
111    pub fn alloc_sum(&self) -> usize {
112        ALLOC_SUM.load(Ordering::Relaxed)
113    }
114
115    /// Returns the total number of deallocations.
116    pub fn dealloc_count(&self) -> usize {
117        DEALLOC_COUNT.load(Ordering::Relaxed)
118    }
119
120    /// Returns the sum of all deallocations.
121    pub fn dealloc_sum(&self) -> usize {
122        DEALLOC_SUM.load(Ordering::Relaxed)
123    }
124
125    /// Returns the total number of reallocations caused by object growth.
126    pub fn realloc_growth_count(&self) -> usize {
127        REALLOC_GROWTH_COUNT.load(Ordering::Relaxed)
128    }
129
130    /// Returns the sum of all reallocations caused by object growth.
131    pub fn realloc_growth_sum(&self) -> usize {
132        REALLOC_GROWTH_SUM.load(Ordering::Relaxed)
133    }
134
135    /// Returns the total number of reallocations caused by object shrinkage.
136    pub fn realloc_shrink_count(&self) -> usize {
137        REALLOC_SHRINK_COUNT.load(Ordering::Relaxed)
138    }
139
140    /// Returns the sum of all reallocations caused by object shrinkage.
141    pub fn realloc_shrink_sum(&self) -> usize {
142        REALLOC_SHRINK_SUM.load(Ordering::Relaxed)
143    }
144
145    /// Returns the average size of allocations.
146    pub fn alloc_avg(&self) -> Option<usize> {
147        let sum = ALLOC_SUM.load(Ordering::Relaxed);
148        let count = ALLOC_COUNT.load(Ordering::Relaxed);
149        sum.checked_div(count)
150    }
151
152    /// Returns the average size of deallocations.
153    pub fn dealloc_avg(&self) -> Option<usize> {
154        let sum = DEALLOC_SUM.load(Ordering::Relaxed);
155        let count = DEALLOC_COUNT.load(Ordering::Relaxed);
156        sum.checked_div(count)
157    }
158
159    /// Returns the average size of reallocations caused by object growth.
160    pub fn realloc_growth_avg(&self) -> Option<usize> {
161        let sum = REALLOC_GROWTH_SUM.load(Ordering::Relaxed);
162        let count = REALLOC_GROWTH_COUNT.load(Ordering::Relaxed);
163        sum.checked_div(count)
164    }
165
166    /// Returns the average size of reallocations caused by object shrinkage.
167    pub fn realloc_shrink_avg(&self) -> Option<usize> {
168        let sum = REALLOC_SHRINK_SUM.load(Ordering::Relaxed);
169        let count = REALLOC_SHRINK_COUNT.load(Ordering::Relaxed);
170        sum.checked_div(count)
171    }
172
173    /// Returns current heap use.
174    pub fn use_curr(&self) -> usize {
175        USE_CURR.load(Ordering::Relaxed)
176    }
177
178    /// Returns maximum recorded heap use.
179    pub fn use_max(&self) -> usize {
180        USE_MAX.load(Ordering::Relaxed)
181    }
182
183    /// Sets the stats to 0, except for current heap use (which is unaffected)
184    /// and maximum heap use, which is reset to the value of current heap use.
185    pub fn reset(&self) {
186        ALLOC_SUM.store(0, Ordering::Relaxed);
187        ALLOC_COUNT.store(0, Ordering::Relaxed);
188        DEALLOC_SUM.store(0, Ordering::Relaxed);
189        DEALLOC_COUNT.store(0, Ordering::Relaxed);
190        REALLOC_GROWTH_COUNT.store(0, Ordering::Relaxed);
191        REALLOC_GROWTH_SUM.store(0, Ordering::Relaxed);
192        REALLOC_SHRINK_COUNT.store(0, Ordering::Relaxed);
193        REALLOC_SHRINK_SUM.store(0, Ordering::Relaxed);
194        USE_MAX.store(self.use_curr(), Ordering::Relaxed);
195    }
196
197    /// Returns a summary of the allocator's stats.
198    pub fn stats(&self) -> SystemStats {
199        let alloc_count = self.alloc_count();
200        let alloc_sum = self.alloc_sum();
201        let alloc_avg = alloc_sum.checked_div(alloc_count);
202
203        let dealloc_count = self.dealloc_count();
204        let dealloc_sum = self.dealloc_sum();
205        let dealloc_avg = dealloc_sum.checked_div(dealloc_count);
206
207        let realloc_growth_count = self.realloc_growth_count();
208        let realloc_growth_sum = self.realloc_growth_sum();
209        let realloc_growth_avg = realloc_growth_sum.checked_div(realloc_growth_count);
210
211        let realloc_shrink_count = self.realloc_shrink_count();
212        let realloc_shrink_sum = self.realloc_shrink_sum();
213        let realloc_shrink_avg = realloc_shrink_sum.checked_div(realloc_shrink_count);
214
215        let use_curr = self.use_curr();
216        let use_max = self.use_max();
217
218        SystemStats {
219            alloc_count,
220            alloc_avg,
221            dealloc_count,
222            dealloc_avg,
223            realloc_growth_count,
224            realloc_growth_avg,
225            realloc_shrink_count,
226            realloc_shrink_avg,
227            use_curr,
228            use_max,
229        }
230    }
231}
232
233unsafe impl GlobalAlloc for SystemWithStats {
234    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
235        let ret = System.alloc(layout);
236        if !ret.is_null() {
237            let size = layout.size();
238            ALLOC_SUM.fetch_add(size, Ordering::Relaxed);
239            ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
240            let curr = USE_CURR.fetch_add(size, Ordering::Relaxed) + size;
241            USE_MAX.fetch_max(curr, Ordering::Relaxed);
242        }
243        ret
244    }
245
246    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
247        System.dealloc(ptr, layout);
248        let size = layout.size();
249        USE_CURR.fetch_sub(size, Ordering::Relaxed);
250        DEALLOC_SUM.fetch_add(size, Ordering::Relaxed);
251        DEALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
252    }
253
254    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
255        let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
256        let new_ptr = unsafe { self.alloc(new_layout) };
257        if !new_ptr.is_null() {
258            if new_size > layout.size() {
259                let diff = new_size - layout.size();
260                REALLOC_GROWTH_COUNT.fetch_add(1, Ordering::Relaxed);
261                REALLOC_GROWTH_SUM.fetch_add(diff, Ordering::Relaxed);
262            } else {
263                let diff = layout.size() - new_size;
264                REALLOC_SHRINK_COUNT.fetch_add(1, Ordering::Relaxed);
265                REALLOC_SHRINK_SUM.fetch_add(diff, Ordering::Relaxed);
266            }
267
268            unsafe {
269                ptr::copy_nonoverlapping(ptr, new_ptr, cmp::min(layout.size(), new_size));
270                self.dealloc(ptr, layout);
271            }
272        }
273        new_ptr
274    }
275}