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, A, S = BumpSettings>
33where
34    S: BumpAllocatorSettings,
35{
36    chunk: RawChunk<A, S>,
37    marker: PhantomData<&'a ()>,
38}
39
40impl<A, S> Clone for Stats<'_, A, S>
41where
42    S: BumpAllocatorSettings,
43{
44    fn clone(&self) -> Self {
45        *self
46    }
47}
48
49impl<A, S> Copy for Stats<'_, A, S> where S: BumpAllocatorSettings {}
50
51impl<A, S> PartialEq for Stats<'_, A, S>
52where
53    S: BumpAllocatorSettings,
54{
55    fn eq(&self, other: &Self) -> bool {
56        self.chunk.header() == other.chunk.header()
57    }
58}
59
60impl<A, S> Eq for Stats<'_, A, S> where S: BumpAllocatorSettings {}
61
62impl<A, S> Debug for Stats<'_, A, 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, A, S> Stats<'a, A, S>
72where
73    S: BumpAllocatorSettings,
74{
75    #[inline]
76    pub(crate) fn from_raw_chunk(chunk: RawChunk<A, 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, 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, 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, A, S>> {
174        Some(Chunk {
175            chunk: self.chunk.as_non_dummy()?,
176            marker: self.marker,
177        })
178    }
179
180    /// Returns a reference to the base allocator.
181    #[inline]
182    #[must_use]
183    pub fn allocator(self) -> Option<&'a A> {
184        Some(self.current_chunk()?.allocator())
185    }
186}
187
188impl<'a, A, S> From<Chunk<'a, A, S>> for Stats<'a, A, S>
189where
190    S: BumpAllocatorSettings,
191{
192    fn from(chunk: Chunk<'a, A, S>) -> Self {
193        Stats {
194            chunk: *chunk.chunk,
195            marker: PhantomData,
196        }
197    }
198}
199
200impl<A, S> Default for Stats<'_, A, S>
201where
202    S: BumpAllocatorSettings<GuaranteedAllocated = False>,
203{
204    fn default() -> Self {
205        Self {
206            chunk: RawChunk::UNALLOCATED,
207            marker: PhantomData,
208        }
209    }
210}
211
212/// Refers to a chunk of memory that was allocated by the bump allocator.
213///
214/// See [`Stats`].
215#[repr(transparent)]
216pub struct Chunk<'a, A, S>
217where
218    S: BumpAllocatorSettings,
219{
220    pub(crate) chunk: NonDummyChunk<A, S>,
221    marker: PhantomData<&'a ()>,
222}
223
224impl<A, S> Clone for Chunk<'_, A, S>
225where
226    S: BumpAllocatorSettings,
227{
228    fn clone(&self) -> Self {
229        *self
230    }
231}
232
233impl<A, S> Copy for Chunk<'_, A, S> where S: BumpAllocatorSettings {}
234
235impl<A, S> PartialEq for Chunk<'_, A, S>
236where
237    S: BumpAllocatorSettings,
238{
239    fn eq(&self, other: &Self) -> bool {
240        self.chunk.header() == other.chunk.header()
241    }
242}
243
244impl<A, S> Eq for Chunk<'_, A, S> where S: BumpAllocatorSettings {}
245
246impl<A, S> Debug for Chunk<'_, A, S>
247where
248    S: BumpAllocatorSettings,
249{
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        f.debug_struct("Chunk")
252            .field("allocated", &self.allocated())
253            .field("capacity", &self.capacity())
254            .finish()
255    }
256}
257
258impl<'a, A, S> Chunk<'a, A, S>
259where
260    S: BumpAllocatorSettings,
261{
262    #[cfg(debug_assertions)]
263    pub(crate) fn header(self) -> NonNull<ChunkHeader<A>> {
264        self.chunk.header()
265    }
266
267    /// Returns the previous (smaller) chunk.
268    #[must_use]
269    #[inline(always)]
270    pub fn prev(self) -> Option<Self> {
271        Some(Chunk {
272            chunk: self.chunk.prev()?,
273            marker: PhantomData,
274        })
275    }
276
277    /// Returns the next (bigger) chunk.
278    #[must_use]
279    #[inline(always)]
280    pub fn next(self) -> Option<Self> {
281        Some(Chunk {
282            chunk: self.chunk.next()?,
283            marker: PhantomData,
284        })
285    }
286
287    /// Returns an iterator over all previous (smaller) chunks.
288    #[must_use]
289    #[inline(always)]
290    pub fn iter_prev(self) -> ChunkPrevIter<'a, A, S> {
291        ChunkPrevIter { chunk: self.prev() }
292    }
293
294    /// Returns an iterator over all next (bigger) chunks.
295    #[must_use]
296    #[inline(always)]
297    pub fn iter_next(self) -> ChunkNextIter<'a, A, S> {
298        ChunkNextIter { chunk: self.next() }
299    }
300
301    /// Returns the size of this chunk in bytes.
302    #[must_use]
303    #[inline]
304    pub fn size(self) -> usize {
305        self.chunk.size().get()
306    }
307
308    /// Returns the capacity of this chunk in bytes.
309    #[inline]
310    #[must_use]
311    pub fn capacity(self) -> usize {
312        self.chunk.capacity()
313    }
314
315    /// Returns the amount of allocated bytes.
316    /// This includes padding and wasted space due to reallocations.
317    ///
318    /// This property can be misleading for chunks that come after the current chunk because
319    /// their `bump_position` and consequently the `allocated` property is not reset until
320    /// they become the current chunk again.
321    #[inline]
322    #[must_use]
323    pub fn allocated(self) -> usize {
324        self.chunk.allocated()
325    }
326
327    /// Returns the remaining capacity.
328    ///
329    /// This property can be misleading for chunks that come after the current chunk because
330    /// their `bump_position` and consequently the `remaining` property is not reset until
331    /// they become the current chunk again.
332    #[inline]
333    #[must_use]
334    pub fn remaining(self) -> usize {
335        self.chunk.remaining()
336    }
337
338    /// Returns a pointer to the start of the chunk.
339    #[inline]
340    #[must_use]
341    pub fn chunk_start(self) -> NonNull<u8> {
342        self.chunk.chunk_start()
343    }
344
345    /// Returns a pointer to the end of the chunk.
346    #[inline]
347    #[must_use]
348    pub fn chunk_end(self) -> NonNull<u8> {
349        self.chunk.chunk_end()
350    }
351
352    /// Returns a pointer to the start of the chunk's content.
353    #[inline]
354    #[must_use]
355    pub fn content_start(self) -> NonNull<u8> {
356        self.chunk.content_start()
357    }
358
359    /// Returns a pointer to the end of the chunk's content.
360    #[inline]
361    #[must_use]
362    pub fn content_end(self) -> NonNull<u8> {
363        self.chunk.content_end()
364    }
365
366    /// Returns the bump pointer. It lies within the chunk's content range.
367    ///
368    /// This property can be misleading for chunks that come after the current chunk because
369    /// their `bump_position` is not reset until they become the current chunk again.
370    #[inline]
371    #[must_use]
372    pub fn bump_position(self) -> NonNull<u8> {
373        self.chunk.pos()
374    }
375
376    /// Returns a reference to the base allocator.
377    #[inline]
378    #[must_use]
379    pub fn allocator(self) -> &'a A {
380        self.chunk.allocator()
381    }
382}
383
384/// Iterator that iterates over previous chunks by continuously calling [`Chunk::prev`].
385pub struct ChunkPrevIter<'a, A, S>
386where
387    S: BumpAllocatorSettings,
388{
389    #[expect(missing_docs)]
390    pub chunk: Option<Chunk<'a, A, S>>,
391}
392
393impl<A, S> Clone for ChunkPrevIter<'_, A, S>
394where
395    S: BumpAllocatorSettings,
396{
397    fn clone(&self) -> Self {
398        *self
399    }
400}
401
402impl<A, S> Copy for ChunkPrevIter<'_, A, S> where S: BumpAllocatorSettings {}
403
404impl<A, S> PartialEq for ChunkPrevIter<'_, A, S>
405where
406    S: BumpAllocatorSettings,
407{
408    fn eq(&self, other: &Self) -> bool {
409        self.chunk == other.chunk
410    }
411}
412
413impl<A, S> Eq for ChunkPrevIter<'_, A, S> where S: BumpAllocatorSettings {}
414
415impl<A, S> Default for ChunkPrevIter<'_, A, S>
416where
417    S: BumpAllocatorSettings,
418{
419    fn default() -> Self {
420        Self { chunk: None }
421    }
422}
423
424impl<'a, A, S> Iterator for ChunkPrevIter<'a, A, S>
425where
426    S: BumpAllocatorSettings,
427{
428    type Item = Chunk<'a, A, S>;
429
430    #[inline(always)]
431    fn next(&mut self) -> Option<Self::Item> {
432        let chunk = self.chunk?;
433        self.chunk = chunk.prev();
434        Some(chunk)
435    }
436}
437
438impl<A, S> FusedIterator for ChunkPrevIter<'_, A, S> where S: BumpAllocatorSettings {}
439
440impl<A, S> Debug for ChunkPrevIter<'_, A, S>
441where
442    S: BumpAllocatorSettings,
443{
444    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
445        f.debug_list().entries(*self).finish()
446    }
447}
448
449/// Iterator that iterates over next chunks by continuously calling [`Chunk::next`].
450pub struct ChunkNextIter<'a, A, S>
451where
452    S: BumpAllocatorSettings,
453{
454    #[expect(missing_docs)]
455    pub chunk: Option<Chunk<'a, A, S>>,
456}
457
458impl<A, S> Clone for ChunkNextIter<'_, A, S>
459where
460    S: BumpAllocatorSettings,
461{
462    fn clone(&self) -> Self {
463        *self
464    }
465}
466
467impl<A, S> Copy for ChunkNextIter<'_, A, S> where S: BumpAllocatorSettings {}
468
469impl<A, S> PartialEq for ChunkNextIter<'_, A, S>
470where
471    S: BumpAllocatorSettings,
472{
473    fn eq(&self, other: &Self) -> bool {
474        self.chunk == other.chunk
475    }
476}
477
478impl<A, S> Eq for ChunkNextIter<'_, A, S> where S: BumpAllocatorSettings {}
479
480impl<A, S> Default for ChunkNextIter<'_, A, S>
481where
482    S: BumpAllocatorSettings,
483{
484    fn default() -> Self {
485        Self { chunk: None }
486    }
487}
488
489impl<'a, A, S> Iterator for ChunkNextIter<'a, A, S>
490where
491    S: BumpAllocatorSettings,
492{
493    type Item = Chunk<'a, A, S>;
494
495    #[inline(always)]
496    fn next(&mut self) -> Option<Self::Item> {
497        let chunk = self.chunk?;
498        self.chunk = chunk.next();
499        Some(chunk)
500    }
501}
502
503impl<A, S> FusedIterator for ChunkNextIter<'_, A, S> where S: BumpAllocatorSettings {}
504
505impl<A, S> Debug for ChunkNextIter<'_, A, S>
506where
507    S: BumpAllocatorSettings,
508{
509    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
510        f.debug_list().entries(self.map(Chunk::size)).finish()
511    }
512}