Skip to content

Commit 4b5c0d4

Browse files
authored
Add a public API to expose allocation reports (#226)
1 parent 3dfb9e6 commit 4b5c0d4

File tree

8 files changed

+158
-89
lines changed

8 files changed

+158
-89
lines changed

src/allocator/dedicated_block_allocator/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ impl SubAllocator for DedicatedBlockAllocator {
116116
.name
117117
.clone()
118118
.unwrap_or_else(|| "<Unnamed Dedicated allocation>".to_owned()),
119+
offset: 0,
119120
size: self.size,
120121
#[cfg(feature = "visualizer")]
121122
backtrace: self.backtrace.clone(),

src/allocator/free_list_allocator/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ impl SubAllocator for FreeListAllocator {
398398
.name
399399
.clone()
400400
.unwrap_or_else(|| "<Unnamed FreeList allocation>".to_owned()),
401+
offset: chunk.offset,
401402
size: chunk.size,
402403
#[cfg(feature = "visualizer")]
403404
backtrace: chunk.backtrace.clone(),

src/allocator/mod.rs

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{backtrace::Backtrace, sync::Arc};
1+
use std::{backtrace::Backtrace, fmt, ops::Range, sync::Arc};
22

33
use log::*;
44

@@ -29,20 +29,83 @@ impl AllocationType {
2929
}
3030
}
3131

32+
/// Describes an allocation in the [`AllocatorReport`].
3233
#[derive(Clone)]
33-
pub(crate) struct AllocationReport {
34-
pub(crate) name: String,
35-
pub(crate) size: u64,
34+
pub struct AllocationReport {
35+
/// The name provided to the `allocate()` function.
36+
pub name: String,
37+
/// The offset in bytes of the allocation in its memory block.
38+
pub offset: u64,
39+
/// The size in bytes of the allocation.
40+
pub size: u64,
3641
#[cfg(feature = "visualizer")]
3742
pub(crate) backtrace: Arc<Backtrace>,
3843
}
3944

45+
/// Describes a memory block in the [`AllocatorReport`].
46+
#[derive(Clone)]
47+
pub struct MemoryBlockReport {
48+
/// The size in bytes of this memory block.
49+
pub size: u64,
50+
/// The range of allocations in [`AllocatorReport::allocations`] that are associated
51+
/// to this memory block.
52+
pub allocations: Range<usize>,
53+
}
54+
55+
/// A report that can be generated for informational purposes using `Allocator::generate_report()`.
56+
#[derive(Clone)]
57+
pub struct AllocatorReport {
58+
/// All live allocations, sub-allocated from memory blocks.
59+
pub allocations: Vec<AllocationReport>,
60+
/// All memory blocks.
61+
pub blocks: Vec<MemoryBlockReport>,
62+
/// Sum of the memory used by all allocations, in bytes.
63+
pub total_allocated_bytes: u64,
64+
/// Sum of the memory reserved by all memory blocks including unallocated regions, in bytes.
65+
pub total_reserved_bytes: u64,
66+
}
67+
68+
impl fmt::Debug for AllocationReport {
69+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70+
let name = if !self.name.is_empty() {
71+
self.name.as_str()
72+
} else {
73+
"--"
74+
};
75+
write!(f, "{name:?}: {}", fmt_bytes(self.size))
76+
}
77+
}
78+
79+
impl fmt::Debug for AllocatorReport {
80+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81+
let mut allocations = self.allocations.clone();
82+
allocations.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
83+
84+
let max_num_allocations_to_print = f.precision().unwrap_or(usize::MAX);
85+
allocations.truncate(max_num_allocations_to_print);
86+
87+
f.debug_struct("AllocatorReport")
88+
.field(
89+
"summary",
90+
&std::format_args!(
91+
"{} / {}",
92+
fmt_bytes(self.total_allocated_bytes),
93+
fmt_bytes(self.total_reserved_bytes)
94+
),
95+
)
96+
.field("blocks", &self.blocks.len())
97+
.field("allocations", &self.allocations.len())
98+
.field("largest", &allocations.as_slice())
99+
.finish()
100+
}
101+
}
102+
40103
#[cfg(feature = "visualizer")]
41104
pub(crate) trait SubAllocatorBase: crate::visualizer::SubAllocatorVisualizer {}
42105
#[cfg(not(feature = "visualizer"))]
43106
pub(crate) trait SubAllocatorBase {}
44107

45-
pub(crate) trait SubAllocator: SubAllocatorBase + std::fmt::Debug + Sync + Send {
108+
pub(crate) trait SubAllocator: SubAllocatorBase + fmt::Debug + Sync + Send {
46109
fn allocate(
47110
&mut self,
48111
size: u64,
@@ -82,8 +145,6 @@ pub(crate) trait SubAllocator: SubAllocatorBase + std::fmt::Debug + Sync + Send
82145
}
83146
}
84147

85-
pub(crate) const VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN: usize = 40;
86-
87148
pub(crate) fn fmt_bytes(mut amount: u64) -> String {
88149
const SUFFIX: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
89150

src/d3d12/mod.rs

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ use super::allocator;
8888
use super::allocator::AllocationType;
8989

9090
use crate::{
91-
allocator::fmt_bytes, AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation,
92-
Result,
91+
allocator::{AllocatorReport, MemoryBlockReport},
92+
AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result,
9393
};
9494

9595
/// [`ResourceCategory`] is used for supporting [`D3D12_RESOURCE_HEAP_TIER_1`].
@@ -1102,50 +1102,38 @@ impl Allocator {
11021102
Ok(())
11031103
}
11041104
}
1105-
}
11061105

1107-
impl fmt::Debug for Allocator {
1108-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1109-
let mut allocation_report = vec![];
1110-
let mut total_reserved_size_in_bytes = 0;
1106+
pub fn generate_report(&self) -> AllocatorReport {
1107+
let mut allocations = vec![];
1108+
let mut blocks = vec![];
1109+
let mut total_reserved_bytes = 0;
11111110

11121111
for memory_type in &self.memory_types {
11131112
for block in memory_type.memory_blocks.iter().flatten() {
1114-
total_reserved_size_in_bytes += block.size;
1115-
allocation_report.extend(block.sub_allocator.report_allocations())
1113+
total_reserved_bytes += block.size;
1114+
let first_allocation = allocations.len();
1115+
allocations.extend(block.sub_allocator.report_allocations());
1116+
blocks.push(MemoryBlockReport {
1117+
size: block.size,
1118+
allocations: first_allocation..allocations.len(),
1119+
});
11161120
}
11171121
}
11181122

1119-
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
1120-
1121-
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
1122-
1123-
writeln!(
1124-
f,
1125-
"================================================================",
1126-
)?;
1127-
writeln!(
1128-
f,
1129-
"ALLOCATION BREAKDOWN ({} / {})",
1130-
fmt_bytes(total_used_size_in_bytes),
1131-
fmt_bytes(total_reserved_size_in_bytes),
1132-
)?;
1123+
let total_allocated_bytes = allocations.iter().map(|report| report.size).sum();
11331124

1134-
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
1135-
for (idx, alloc) in allocation_report.iter().enumerate() {
1136-
if idx >= max_num_allocations_to_print {
1137-
break;
1138-
}
1139-
writeln!(
1140-
f,
1141-
"{:max_len$.max_len$}\t- {}",
1142-
alloc.name,
1143-
fmt_bytes(alloc.size),
1144-
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
1145-
)?;
1125+
AllocatorReport {
1126+
allocations,
1127+
blocks,
1128+
total_allocated_bytes,
1129+
total_reserved_bytes,
11461130
}
1131+
}
1132+
}
11471133

1148-
Ok(())
1134+
impl fmt::Debug for Allocator {
1135+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1136+
self.generate_report().fmt(f)
11491137
}
11501138
}
11511139

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ pub use result::*;
212212

213213
pub(crate) mod allocator;
214214

215+
pub use allocator::{AllocationReport, AllocatorReport, MemoryBlockReport};
216+
215217
#[cfg(feature = "visualizer")]
216218
pub mod visualizer;
217219

src/metal/mod.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
use std::{backtrace::Backtrace, sync::Arc};
33

44
use crate::{
5-
allocator, AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result,
5+
allocator::{self, AllocatorReport, MemoryBlockReport},
6+
AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result,
67
};
78
use log::{debug, Level};
89

@@ -146,7 +147,7 @@ pub struct CommittedAllocationStatistics {
146147
}
147148
struct MemoryBlock {
148149
heap: Arc<metal::Heap>,
149-
_size: u64,
150+
size: u64,
150151
sub_allocator: Box<dyn allocator::SubAllocator>,
151152
}
152153

@@ -169,7 +170,7 @@ impl MemoryBlock {
169170

170171
Ok(Self {
171172
heap,
172-
_size: size,
173+
size,
173174
sub_allocator,
174175
})
175176
}
@@ -485,4 +486,31 @@ impl Allocator {
485486
pub fn report_memory_leaks(&self, _log_level: Level) {
486487
todo!()
487488
}
489+
490+
pub fn generate_report(&self) -> AllocatorReport {
491+
let mut allocations = vec![];
492+
let mut blocks = vec![];
493+
let mut total_reserved_bytes = 0;
494+
495+
for memory_type in &self.memory_types {
496+
for block in memory_type.memory_blocks.iter().flatten() {
497+
total_reserved_bytes += block.size;
498+
let first_allocation = allocations.len();
499+
allocations.extend(block.sub_allocator.report_allocations());
500+
blocks.push(MemoryBlockReport {
501+
size: block.size,
502+
allocations: first_allocation..allocations.len(),
503+
});
504+
}
505+
}
506+
507+
let total_allocated_bytes = allocations.iter().map(|report| report.size).sum();
508+
509+
AllocatorReport {
510+
allocations,
511+
blocks,
512+
total_allocated_bytes,
513+
total_reserved_bytes,
514+
}
515+
}
488516
}

src/visualizer/allocation_reports.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ pub(crate) fn render_allocation_reports_ui(
115115
name,
116116
size,
117117
backtrace,
118+
..
118119
} = alloc;
119120

120121
row.col(|ui| {

src/vulkan/mod.rs

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use log::{debug, Level};
1212

1313
use super::allocator;
1414
use crate::{
15-
allocator::fmt_bytes, AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation,
16-
Result,
15+
allocator::{AllocatorReport, MemoryBlockReport},
16+
AllocationError, AllocationSizes, AllocatorDebugSettings, MemoryLocation, Result,
1717
};
1818

1919
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -691,47 +691,7 @@ pub struct Allocator {
691691

692692
impl fmt::Debug for Allocator {
693693
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694-
let mut allocation_report = vec![];
695-
let mut total_reserved_size_in_bytes = 0;
696-
697-
for memory_type in &self.memory_types {
698-
for block in memory_type.memory_blocks.iter().flatten() {
699-
total_reserved_size_in_bytes += block.size;
700-
allocation_report.extend(block.sub_allocator.report_allocations())
701-
}
702-
}
703-
704-
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
705-
706-
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
707-
708-
writeln!(
709-
f,
710-
"================================================================",
711-
)?;
712-
writeln!(
713-
f,
714-
"ALLOCATION BREAKDOWN ({} / {})",
715-
fmt_bytes(total_used_size_in_bytes),
716-
fmt_bytes(total_reserved_size_in_bytes),
717-
)?;
718-
719-
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
720-
for (idx, alloc) in allocation_report.iter().enumerate() {
721-
if idx >= max_num_allocations_to_print {
722-
break;
723-
}
724-
725-
writeln!(
726-
f,
727-
"{:max_len$.max_len$}\t- {}",
728-
alloc.name,
729-
fmt_bytes(alloc.size),
730-
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
731-
)?;
732-
}
733-
734-
Ok(())
694+
self.generate_report().fmt(f)
735695
}
736696
}
737697

@@ -972,6 +932,33 @@ impl Allocator {
972932
})
973933
.map(|memory_type| memory_type.memory_type_index as _)
974934
}
935+
936+
pub fn generate_report(&self) -> AllocatorReport {
937+
let mut allocations = vec![];
938+
let mut blocks = vec![];
939+
let mut total_reserved_bytes = 0;
940+
941+
for memory_type in &self.memory_types {
942+
for block in memory_type.memory_blocks.iter().flatten() {
943+
total_reserved_bytes += block.size;
944+
let first_allocation = allocations.len();
945+
allocations.extend(block.sub_allocator.report_allocations());
946+
blocks.push(MemoryBlockReport {
947+
size: block.size,
948+
allocations: first_allocation..allocations.len(),
949+
});
950+
}
951+
}
952+
953+
let total_allocated_bytes = allocations.iter().map(|report| report.size).sum();
954+
955+
AllocatorReport {
956+
allocations,
957+
blocks,
958+
total_allocated_bytes,
959+
total_reserved_bytes,
960+
}
961+
}
975962
}
976963

977964
impl Drop for Allocator {

0 commit comments

Comments
 (0)