code_moniker_workspace/
memory.rs1use std::collections::HashSet;
8use std::mem::{size_of, size_of_val};
9use std::sync::Arc;
10
11use code_moniker_core::core::code_graph::{DefRecord, RefRecord};
12
13use crate::snapshot::{CodeIndex, LinkageSnapshot, WorkspaceSnapshot};
14use crate::source::{CodeIndexMaterial, IndexedSourceFile};
15
16#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17pub struct SnapshotMemoryEstimate {
18 pub source_bytes: usize,
19 pub index_bytes: usize,
20 pub graph_bytes: usize,
21}
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
24pub struct RetainedMaterialMemoryEstimate {
25 pub source_bytes: usize,
26 pub graph_bytes: usize,
27 pub lookup_bytes: usize,
28 pub metadata_bytes: usize,
29 pub total_bytes: usize,
30}
31
32impl RetainedMaterialMemoryEstimate {
33 pub fn from_material(material: &CodeIndexMaterial) -> Self {
34 let source_bytes = material
35 .files
36 .iter()
37 .map(|file| file.source.capacity())
38 .sum();
39 let metadata_bytes = material.files.capacity() * size_of::<Arc<IndexedSourceFile>>()
40 + material
41 .files
42 .iter()
43 .map(|file| {
44 size_of::<IndexedSourceFile>()
45 + file.source_uri.capacity()
46 + file.path.as_os_str().len()
47 + file.rel_path.as_os_str().len()
48 + file.anchor.as_os_str().len()
49 })
50 .sum::<usize>();
51 let graph_bytes =
52 material
53 .files
54 .iter()
55 .map(|file| {
56 file.graph.def_count() * size_of::<DefRecord>()
57 + file.graph.ref_count() * size_of::<RefRecord>()
58 + file
59 .graph
60 .defs()
61 .map(definition_payload_bytes)
62 .sum::<usize>() + file
63 .graph
64 .refs()
65 .map(reference_payload_bytes)
66 .sum::<usize>() + file
67 .graph
68 .defs()
69 .map(|definition| definition.moniker.as_encoded().len())
70 .sum::<usize>()
71 })
72 .sum();
73 let lookup_bytes = material
74 .symbols_by_moniker
75 .iter()
76 .map(|(moniker, symbol)| moniker.as_encoded().len() + size_of_val(symbol))
77 .sum();
78 Self {
79 source_bytes,
80 graph_bytes,
81 lookup_bytes,
82 metadata_bytes,
83 total_bytes: source_bytes + graph_bytes + lookup_bytes + metadata_bytes,
84 }
85 }
86}
87
88impl SnapshotMemoryEstimate {
89 pub fn from_snapshot(snapshot: &WorkspaceSnapshot) -> Self {
90 Self {
91 source_bytes: source_bytes(&snapshot.index),
92 index_bytes: Self::from_index(&snapshot.index),
93 graph_bytes: Self::from_linkage(&snapshot.linkage),
94 }
95 }
96
97 pub fn from_index(index: &CodeIndex) -> usize {
98 let sources = index.sources.iter().fold(
99 index.sources.capacity() * size_of::<crate::snapshot::SourceFileRecord>(),
100 |total, source| {
101 total
102 + source.uri.capacity()
103 + source.path.capacity()
104 + source.rel_path.capacity()
105 + source.anchor.capacity()
106 + source.language.capacity()
107 + source.text.capacity()
108 },
109 );
110 let symbols = index.symbols.estimated_heap_bytes()
111 + index.symbols.iter().fold(0, |total, symbol| {
112 total
113 + symbol.name.capacity()
114 + symbol.kind.capacity()
115 + symbol.visibility.capacity()
116 + symbol.signature.capacity()
117 + symbol.call_name.as_ref().map_or(0, String::capacity)
118 });
119 let references = index.references.estimated_heap_bytes()
120 + index.references.iter().fold(0, |total, reference| {
121 total
122 + reference.kind.capacity()
123 + reference.call_name.as_ref().map_or(0, String::capacity)
124 + reference.confidence.as_ref().map_or(0, String::capacity)
125 + reference.receiver.as_ref().map_or(0, String::capacity)
126 + reference.alias.as_ref().map_or(0, String::capacity)
127 }) + unique_arc_str_payload(
128 index
129 .references
130 .iter()
131 .map(|reference| &reference.target_identity),
132 );
133 size_of_val(index) + sources + symbols + references + index.inventory.estimated_heap_bytes()
134 }
135
136 pub fn from_linkage(linkage: &LinkageSnapshot) -> usize {
137 let mut bytes = size_of_val(linkage)
138 + linkage.resolved.capacity() * size_of::<crate::snapshot::LinkageEdge>()
139 + linkage.candidates.capacity() * size_of::<crate::snapshot::CandidateReference>()
140 + linkage.external.capacity() * size_of::<crate::snapshot::ExternalReference>()
141 + linkage.dynamic.capacity() * size_of::<crate::snapshot::DynamicReference>()
142 + linkage.blocked.capacity() * size_of::<crate::snapshot::UnresolvedReference>()
143 + linkage.manifest_blocked.capacity()
144 * size_of::<crate::snapshot::UnresolvedReference>()
145 + linkage.unresolved.capacity() * size_of::<crate::snapshot::UnresolvedReference>()
146 + linkage.read_index.estimated_heap_bytes();
147 bytes += linkage
148 .candidates
149 .iter()
150 .map(|candidate| candidate.targets.capacity() * size_of::<crate::snapshot::SymbolId>())
151 .sum::<usize>();
152 bytes += linkage
153 .dynamic
154 .iter()
155 .map(|reference| {
156 reference.candidates.capacity() * size_of::<crate::snapshot::SymbolId>()
157 })
158 .sum::<usize>();
159 bytes += unique_arc_str_payload(
160 linkage
161 .external
162 .iter()
163 .map(|reference| &reference.target_identity)
164 .chain(
165 linkage
166 .dynamic
167 .iter()
168 .map(|reference| &reference.target_identity),
169 )
170 .chain(
171 linkage
172 .blocked
173 .iter()
174 .map(|reference| &reference.target_identity),
175 )
176 .chain(
177 linkage
178 .manifest_blocked
179 .iter()
180 .map(|reference| &reference.target_identity),
181 )
182 .chain(
183 linkage
184 .unresolved
185 .iter()
186 .map(|reference| &reference.target_identity),
187 ),
188 );
189 bytes
190 }
191}
192
193fn source_bytes(index: &CodeIndex) -> usize {
194 index
195 .sources
196 .iter()
197 .map(|source| {
198 if source.text.is_empty() {
199 std::fs::metadata(&source.path)
200 .ok()
201 .and_then(|metadata| usize::try_from(metadata.len()).ok())
202 .unwrap_or(0)
203 } else {
204 source.text.len()
205 }
206 })
207 .sum()
208}
209
210fn unique_arc_str_payload<'a>(values: impl Iterator<Item = &'a Arc<str>>) -> usize {
211 let mut seen = HashSet::<(usize, usize)>::new();
212 values
213 .filter(|value| seen.insert((value.as_ptr() as usize, value.len())))
214 .map(|value| value.len())
215 .sum()
216}
217
218fn definition_payload_bytes(definition: &DefRecord) -> usize {
219 definition.moniker.as_encoded().len()
220 + definition.kind.len()
221 + definition.visibility.len()
222 + definition.signature.len()
223 + definition.call_name.len()
224 + definition.binding.len()
225 + definition.origin.len()
226}
227
228fn reference_payload_bytes(reference: &RefRecord) -> usize {
229 reference.target.as_encoded().len()
230 + reference.kind.len()
231 + reference.receiver_hint.len()
232 + reference.alias.len()
233 + reference.confidence.len()
234 + reference.call_name.len()
235 + reference.binding.len()
236}
237
238#[cfg(test)]
239mod tests {
240 use super::SnapshotMemoryEstimate;
241 use crate::snapshot::{
242 CodeIndex, LinkageEdge, LinkageSnapshot, ReferenceId, ResourceGeneration, SymbolId,
243 SymbolRecord,
244 };
245
246 #[test]
247 fn estimates_grow_with_index_and_read_graph_storage() {
248 let generation = ResourceGeneration::new(1);
249 let empty_index = CodeIndex::new(generation, generation, Vec::new());
250 let populated_index = CodeIndex::new(
251 generation,
252 generation,
253 vec![SymbolRecord::new(
254 SymbolId::at(0, 0),
255 crate::snapshot::SourceId::at(0),
256 "main",
257 "function",
258 )],
259 );
260 assert!(
261 SnapshotMemoryEstimate::from_index(&populated_index)
262 > SnapshotMemoryEstimate::from_index(&empty_index)
263 );
264
265 let empty_graph = LinkageSnapshot::new(generation, generation, 0, 0);
266 let populated_graph = LinkageSnapshot::with_refs(
267 generation,
268 generation,
269 vec![LinkageEdge::new(ReferenceId::at(0, 0), SymbolId::at(0, 0))],
270 Vec::new(),
271 );
272 assert!(
273 SnapshotMemoryEstimate::from_linkage(&populated_graph)
274 > SnapshotMemoryEstimate::from_linkage(&empty_graph)
275 );
276 }
277}