Skip to main content

bump_scope/
stats.rs

1//! Contains types for inspecting memory usage in bump allocators.
2//!
3//! This module defines both generic types like [`Stats`] and type-erased counterparts prefixed
4//! with `Any*`. The generic types are slightly more efficient to use.
5//! You can turn the generic types into their `Any*` variants using `from` and `into`.
6//!
7//! The `Any*` types are returned by the [`BumpAllocatorCore::any_stats`](crate::traits::BumpAllocatorCore::any_stats) trait
8//! whereas `Stats` is returned from [`Bump(Scope)::stats`](crate::Bump::stats).
9
10use core::{
11    fmt::{self, Debug},
12    iter::FusedIterator,
13    marker::PhantomData,
14    ptr::NonNull,
15};
16
17use crate::{
18    raw_bump::{NonDummyChunk, RawChunk},
19    settings::{BumpAllocatorSettings, BumpSettings, False},
20};
21
22#[cfg(debug_assertions)]
23use crate::chunk::ChunkHeader;
24
25mod any;
26
27pub use any::{AnyChunk, AnyChunkNextIter, AnyChunkPrevIter, AnyStats};
28
29/// Provides statistics about the memory usage of the bump allocator.
30///
31/// This is returned from [`Bump(Scope)::stats`](crate::Bump::stats).
32pub struct Stats<'a, S = BumpSettings>
33where
34    S: BumpAllocatorSettings,
35{
36    chunk: RawChunk<S>,
37    marker: PhantomData<&'a ()>,
38}
39
40impl<S> Clone for Stats<'_, S>
41where
42    S: BumpAllocatorSettings,
43{
44    fn clone(&self) -> Self {
45        *self
46    }
47}
48
49impl<S> Copy for Stats<'_, S> where S: BumpAllocatorSettings {}
50
51impl<S> PartialEq for Stats<'_, S>
52where
53    S: BumpAllocatorSettings,
54{
55    fn eq(&self, other: &Self) -> bool {
56        self.chunk.header() == other.chunk.header()
57    }
58}
59
60impl<S> Eq for Stats<'_, S> where S: BumpAllocatorSettings {}
61
62impl<S> Debug for Stats<'_, S>
63where
64    S: BumpAllocatorSettings,
65{
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        AnyStats::from(*self).debug_format("Stats", f)
68    }
69}
70
71impl<'a, S> Stats<'a, S>
72where
73    S: BumpAllocatorSettings,
74{
75    #[inline]
76    pub(crate) fn from_raw_chunk(chunk: RawChunk<S>) -> Self {
77        Self {
78            chunk,
79            marker: PhantomData,
80        }
81    }
82
83    /// Returns the number of chunks.
84    #[must_use]
85    pub fn count(self) -> usize {
86        let Some(current) = self.current_chunk() else { return 0 };
87
88        let mut sum = 1;
89        current.iter_prev().for_each(|_| sum += 1);
90        current.iter_next().for_each(|_| sum += 1);
91        sum
92    }
93
94    /// Returns the total size of all chunks.
95    #[must_use]
96    pub fn size(self) -> usize {
97        let Some(current) = self.current_chunk() else { return 0 };
98
99        let mut sum = current.size();
100        current.iter_prev().for_each(|chunk| sum += chunk.size());
101        current.iter_next().for_each(|chunk| sum += chunk.size());
102        sum
103    }
104
105    /// Returns the total capacity of all chunks.
106    #[must_use]
107    pub fn capacity(self) -> usize {
108        let Some(current) = self.current_chunk() else { return 0 };
109
110        let mut sum = current.capacity();
111        current.iter_prev().for_each(|chunk| sum += chunk.capacity());
112        current.iter_next().for_each(|chunk| sum += chunk.capacity());
113        sum
114    }
115
116    /// Returns the amount of allocated bytes.
117    /// This includes padding and wasted space due to reallocations.
118    ///
119    /// This is equal to the `allocated` bytes of the current chunk
120    /// plus the `capacity` of all previous chunks.
121    #[must_use]
122    pub fn allocated(self) -> usize {
123        let Some(current) = self.current_chunk() else { return 0 };
124
125        let mut sum = current.allocated();
126        current.iter_prev().for_each(|chunk| sum += chunk.capacity());
127        sum
128    }
129
130    /// Returns the remaining capacity in bytes.
131    ///
132    /// This is equal to the `remaining` capacity of the current chunk
133    /// plus the `capacity` of all following chunks.
134    #[must_use]
135    pub fn remaining(self) -> usize {
136        let Some(current) = self.current_chunk() else { return 0 };
137
138        let mut sum = current.remaining();
139        current.iter_next().for_each(|chunk| sum += chunk.capacity());
140        sum
141    }
142
143    /// Returns an iterator from smallest to biggest chunk.
144    #[must_use]
145    pub fn small_to_big(self) -> ChunkNextIter<'a, S> {
146        let Some(mut start) = self.current_chunk() else {
147            return ChunkNextIter { chunk: None };
148        };
149
150        while let Some(chunk) = start.prev() {
151            start = chunk;
152        }
153
154        ChunkNextIter { chunk: Some(start) }
155    }
156
157    /// Returns an iterator from biggest to smallest chunk.
158    #[must_use]
159    pub fn big_to_small(self) -> ChunkPrevIter<'a, S> {
160        let Some(mut start) = self.current_chunk() else {
161            return ChunkPrevIter { chunk: None };
162        };
163
164        while let Some(chunk) = start.next() {
165            start = chunk;
166        }
167
168        ChunkPrevIter { chunk: Some(start) }
169    }
170
171    /// This is the chunk we are currently allocating on.
172    #[must_use]
173    pub fn current_chunk(self) -> Option<Chunk<'a, S>> {
174        Some(Chunk {
175            chunk: self.chunk.as_non_dummy()?,
176            marker: self.marker,
177        })
178    }
179}
180
181impl<'a, S> From<Chunk<'a, S>> for Stats<'a, S>
182where
183    S: BumpAllocatorSettings,
184{
185    fn from(chunk: Chunk<'a, S>) -> Self {
186        Stats {
187            chunk: *chunk.chunk,
188            marker: PhantomData,
189        }
190    }
191}
192
193impl<S> Default for Stats<'_, S>
194where
195    S: BumpAllocatorSettings<GuaranteedAllocated = False>,
196{
197    fn default() -> Self {
198        Self {
199            chunk: RawChunk::UNALLOCATED,
200            marker: PhantomData,
201        }
202    }
203}
204
205/// Refers to a chunk of memory that was allocated by the bump allocator.
206///
207/// See [`Stats`].
208#[repr(transparent)]
209pub struct Chunk<'a, S>
210where
211    S: BumpAllocatorSettings,
212{
213    pub(crate) chunk: NonDummyChunk<S>,
214    marker: PhantomData<&'a ()>,
215}
216
217impl<S> Clone for Chunk<'_, S>
218where
219    S: BumpAllocatorSettings,
220{
221    fn clone(&self) -> Self {
222        *self
223    }
224}
225
226impl<S> Copy for Chunk<'_, S> where S: BumpAllocatorSettings {}
227
228impl<S> PartialEq for Chunk<'_, S>
229where
230    S: BumpAllocatorSettings,
231{
232    fn eq(&self, other: &Self) -> bool {
233        self.chunk.header() == other.chunk.header()
234    }
235}
236
237impl<S> Eq for Chunk<'_, S> where S: BumpAllocatorSettings {}
238
239impl<S> Debug for Chunk<'_, S>
240where
241    S: BumpAllocatorSettings,
242{
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        f.debug_struct("Chunk")
245            .field("allocated", &self.allocated())
246            .field("capacity", &self.capacity())
247            .finish()
248    }
249}
250
251impl<'a, S> Chunk<'a, S>
252where
253    S: BumpAllocatorSettings,
254{
255    #[cfg(debug_assertions)]
256    pub(crate) fn header(self) -> NonNull<ChunkHeader> {
257        self.chunk.header().cast()
258    }
259
260    /// Returns the previous (smaller) chunk.
261    #[must_use]
262    #[inline(always)]
263    pub fn prev(self) -> Option<Self> {
264        Some(Chunk {
265            chunk: self.chunk.prev()?,
266            marker: PhantomData,
267        })
268    }
269
270    /// Returns the next (bigger) chunk.
271    #[must_use]
272    #[inline(always)]
273    pub fn next(self) -> Option<Self> {
274        Some(Chunk {
275            chunk: self.chunk.next()?,
276            marker: PhantomData,
277        })
278    }
279
280    /// Returns an iterator over all previous (smaller) chunks.
281    #[must_use]
282    #[inline(always)]
283    pub fn iter_prev(self) -> ChunkPrevIter<'a, S> {
284        ChunkPrevIter { chunk: self.prev() }
285    }
286
287    /// Returns an iterator over all next (bigger) chunks.
288    #[must_use]
289    #[inline(always)]
290    pub fn iter_next(self) -> ChunkNextIter<'a, S> {
291        ChunkNextIter { chunk: self.next() }
292    }
293
294    /// Returns the size of this chunk in bytes.
295    #[must_use]
296    #[inline]
297    pub fn size(self) -> usize {
298        self.chunk.size().get()
299    }
300
301    /// Returns the capacity of this chunk in bytes.
302    #[must_use]
303    #[inline]
304    pub fn capacity(self) -> usize {
305        self.chunk.capacity()
306    }
307
308    /// Returns the amount of allocated bytes.
309    /// This includes padding and wasted space due to reallocations.
310    ///
311    /// This property can be misleading for chunks that come after the current chunk because
312    /// their `bump_position` and consequently the `allocated` property is not reset until
313    /// they become the current chunk again.
314    #[must_use]
315    #[inline]
316    pub fn allocated(self) -> usize {
317        self.chunk.allocated()
318    }
319
320    /// Returns the remaining capacity.
321    ///
322    /// This property can be misleading for chunks that come after the current chunk because
323    /// their `bump_position` and consequently the `remaining` property is not reset until
324    /// they become the current chunk again.
325    #[must_use]
326    #[inline]
327    pub fn remaining(self) -> usize {
328        self.chunk.remaining()
329    }
330
331    /// Returns a pointer to the start of the chunk.
332    #[must_use]
333    #[inline]
334    pub fn chunk_start(self) -> NonNull<u8> {
335        self.chunk.chunk_start()
336    }
337
338    /// Returns a pointer to the end of the chunk.
339    #[must_use]
340    #[inline]
341    pub fn chunk_end(self) -> NonNull<u8> {
342        self.chunk.chunk_end()
343    }
344
345    /// Returns a pointer to the start of the chunk's content.
346    #[must_use]
347    #[inline]
348    pub fn content_start(self) -> NonNull<u8> {
349        self.chunk.content_start()
350    }
351
352    /// Returns a pointer to the end of the chunk's content.
353    #[must_use]
354    #[inline]
355    pub fn content_end(self) -> NonNull<u8> {
356        self.chunk.content_end()
357    }
358
359    /// Returns the bump pointer. It lies within the chunk's content range.
360    ///
361    /// This property can be misleading for chunks that come after the current chunk because
362    /// their `bump_position` is not reset until they become the current chunk again.
363    #[must_use]
364    #[inline]
365    pub fn bump_position(self) -> NonNull<u8> {
366        self.chunk.pos()
367    }
368}
369
370/// Iterator that iterates over previous chunks by continuously calling [`Chunk::prev`].
371pub struct ChunkPrevIter<'a, S>
372where
373    S: BumpAllocatorSettings,
374{
375    #[expect(missing_docs)]
376    pub chunk: Option<Chunk<'a, S>>,
377}
378
379impl<S> Clone for ChunkPrevIter<'_, S>
380where
381    S: BumpAllocatorSettings,
382{
383    fn clone(&self) -> Self {
384        *self
385    }
386}
387
388impl<S> Copy for ChunkPrevIter<'_, S> where S: BumpAllocatorSettings {}
389
390impl<S> PartialEq for ChunkPrevIter<'_, S>
391where
392    S: BumpAllocatorSettings,
393{
394    fn eq(&self, other: &Self) -> bool {
395        self.chunk == other.chunk
396    }
397}
398
399impl<S> Eq for ChunkPrevIter<'_, S> where S: BumpAllocatorSettings {}
400
401impl<S> Default for ChunkPrevIter<'_, S>
402where
403    S: BumpAllocatorSettings,
404{
405    fn default() -> Self {
406        Self { chunk: None }
407    }
408}
409
410impl<'a, S> Iterator for ChunkPrevIter<'a, S>
411where
412    S: BumpAllocatorSettings,
413{
414    type Item = Chunk<'a, S>;
415
416    #[inline(always)]
417    fn next(&mut self) -> Option<Self::Item> {
418        let chunk = self.chunk?;
419        self.chunk = chunk.prev();
420        Some(chunk)
421    }
422}
423
424impl<S> FusedIterator for ChunkPrevIter<'_, S> where S: BumpAllocatorSettings {}
425
426impl<S> Debug for ChunkPrevIter<'_, S>
427where
428    S: BumpAllocatorSettings,
429{
430    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
431        f.debug_list().entries(*self).finish()
432    }
433}
434
435/// Iterator that iterates over next chunks by continuously calling [`Chunk::next`].
436pub struct ChunkNextIter<'a, S>
437where
438    S: BumpAllocatorSettings,
439{
440    #[expect(missing_docs)]
441    pub chunk: Option<Chunk<'a, S>>,
442}
443
444impl<S> Clone for ChunkNextIter<'_, S>
445where
446    S: BumpAllocatorSettings,
447{
448    fn clone(&self) -> Self {
449        *self
450    }
451}
452
453impl<S> Copy for ChunkNextIter<'_, S> where S: BumpAllocatorSettings {}
454
455impl<S> PartialEq for ChunkNextIter<'_, S>
456where
457    S: BumpAllocatorSettings,
458{
459    fn eq(&self, other: &Self) -> bool {
460        self.chunk == other.chunk
461    }
462}
463
464impl<S> Eq for ChunkNextIter<'_, S> where S: BumpAllocatorSettings {}
465
466impl<S> Default for ChunkNextIter<'_, S>
467where
468    S: BumpAllocatorSettings,
469{
470    fn default() -> Self {
471        Self { chunk: None }
472    }
473}
474
475impl<'a, S> Iterator for ChunkNextIter<'a, S>
476where
477    S: BumpAllocatorSettings,
478{
479    type Item = Chunk<'a, S>;
480
481    #[inline(always)]
482    fn next(&mut self) -> Option<Self::Item> {
483        let chunk = self.chunk?;
484        self.chunk = chunk.next();
485        Some(chunk)
486    }
487}
488
489impl<S> FusedIterator for ChunkNextIter<'_, S> where S: BumpAllocatorSettings {}
490
491impl<S> Debug for ChunkNextIter<'_, S>
492where
493    S: BumpAllocatorSettings,
494{
495    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
496        f.debug_list().entries(self.map(Chunk::size)).finish()
497    }
498}