1use crate::{PrincipalType, ResourceReference};
6use core::default::Default;
7use std::cell::RefCell;
8use std::collections::{HashMap, HashSet};
9
10use fidl_fuchsia_memory_attribution_plugin as fplugin;
11use fplugin::Vmo;
12
13use crate::{InflatedPrincipal, InflatedResource, PrincipalIdentifier};
14
15const FLOAT_COMPARISON_EPSILON: f64 = 1e-10;
17
18pub struct MemorySummary {
24 pub principals: Vec<PrincipalSummary>,
25 pub undigested: u64,
27}
28
29impl MemorySummary {
30 pub(crate) fn build(
31 principals: &HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
32 resources: &HashMap<u64, RefCell<InflatedResource>>,
33 resource_names: &Vec<String>,
34 ) -> MemorySummary {
35 let mut output = MemorySummary { principals: Default::default(), undigested: 0 };
36 for principal in principals.values() {
37 output.principals.push(MemorySummary::build_one_principal(
38 &principal,
39 &principals,
40 &resources,
41 &resource_names,
42 ));
43 }
44
45 output.principals.sort_unstable_by_key(|p| -(p.populated_total as i64));
46
47 let mut undigested = 0;
48 for (_, resource_ref) in resources {
49 let resource = &resource_ref.borrow();
50 if resource.claims.is_empty() {
51 match &resource.resource.resource_type {
52 fplugin::ResourceType::Job(_) | fplugin::ResourceType::Process(_) => {}
53 fplugin::ResourceType::Vmo(vmo) => {
54 undigested += vmo.scaled_populated_bytes.unwrap();
55 }
56 _ => todo!(),
57 }
58 }
59 }
60 output.undigested = undigested;
61 output
62 }
63
64 fn build_one_principal(
65 principal_cell: &RefCell<InflatedPrincipal>,
66 principals: &HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
67 resources: &HashMap<u64, RefCell<InflatedResource>>,
68 resource_names: &Vec<String>,
69 ) -> PrincipalSummary {
70 let principal = principal_cell.borrow();
71 let mut output = PrincipalSummary {
72 name: principal.name().to_owned(),
73 id: principal.principal.identifier.0,
74 principal_type: match &principal.principal.principal_type {
75 PrincipalType::Runnable => "R",
76 PrincipalType::Part => "P",
77 }
78 .to_owned(),
79 committed_private: 0,
80 committed_scaled: 0.0,
81 committed_total: 0,
82 populated_private: 0,
83 populated_scaled: 0.0,
84 populated_total: 0,
85 attributor: principal
86 .principal
87 .parent
88 .as_ref()
89 .map(|p| principals.get(p))
90 .flatten()
91 .map(|p| p.borrow().name().to_owned()),
92 processes: Vec::new(),
93 vmos: HashMap::new(),
94 };
95
96 for resource_id in &principal.resources {
97 if !resources.contains_key(resource_id) {
98 continue;
99 }
100
101 let resource = resources.get(resource_id).unwrap().borrow();
102 let share_count = resource
103 .claims
104 .iter()
105 .map(|c| c.subject)
106 .collect::<HashSet<PrincipalIdentifier>>()
107 .len();
108 match &resource.resource.resource_type {
109 fplugin::ResourceType::Job(_) => todo!(),
110 fplugin::ResourceType::Process(_) => {
111 output.processes.push(format!(
112 "{} ({})",
113 resource_names.get(resource.resource.name_index).unwrap().clone(),
114 resource.resource.koid
115 ));
116 }
117 fplugin::ResourceType::Vmo(vmo_info) => {
118 output.committed_total += vmo_info.total_committed_bytes.unwrap();
119 output.populated_total += vmo_info.total_populated_bytes.unwrap();
120 output.committed_scaled +=
121 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
122 output.populated_scaled +=
123 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
124 if share_count == 1 {
125 output.committed_private += vmo_info.private_committed_bytes.unwrap();
126 output.populated_private += vmo_info.private_populated_bytes.unwrap();
127 }
128 output
129 .vmos
130 .entry(
131 vmo_name_to_digest_name(
132 &resource_names.get(resource.resource.name_index).unwrap(),
133 )
134 .to_owned(),
135 )
136 .or_default()
137 .merge(vmo_info, share_count);
138 }
139 _ => todo!(),
140 }
141 }
142
143 for (_source, attribution) in &principal.attribution_claims {
144 for resource in &attribution.resources {
145 if let ResourceReference::ProcessMapped {
146 process: process_mapped,
147 base: _,
148 len: _,
149 } = resource
150 {
151 if let Some(process_ref) = resources.get(&process_mapped) {
152 let process = process_ref.borrow();
153 output.processes.push(format!(
154 "{} ({})",
155 resource_names.get(process.resource.name_index).unwrap().clone(),
156 process.resource.koid
157 ));
158 }
159 }
160 }
161 }
162
163 output.processes.sort();
164 output
165 }
166}
167
168#[derive(Debug)]
170pub struct PrincipalSummary {
171 pub id: u64,
174 pub name: String,
176 pub principal_type: String,
178 pub committed_private: u64,
180 pub committed_scaled: f64,
183 pub committed_total: u64,
185 pub populated_private: u64,
187 pub populated_scaled: f64,
190 pub populated_total: u64,
192
193 pub attributor: Option<String>,
195 pub processes: Vec<String>,
197 pub vmos: HashMap<String, VmoSummary>,
199}
200
201impl PartialEq for PrincipalSummary {
202 fn eq(&self, other: &Self) -> bool {
203 self.id == other.id
204 && self.name == other.name
205 && self.principal_type == other.principal_type
206 && self.committed_private == other.committed_private
207 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
208 && self.committed_total == other.committed_total
209 && self.populated_private == other.populated_private
210 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
211 && self.populated_total == other.populated_total
212 && self.attributor == other.attributor
213 && self.processes == other.processes
214 && self.vmos == other.vmos
215 }
216}
217
218#[derive(Default, Debug)]
220pub struct VmoSummary {
221 pub count: u64,
223 pub committed_private: u64,
226 pub committed_scaled: f64,
229 pub committed_total: u64,
231 pub populated_private: u64,
234 pub populated_scaled: f64,
237 pub populated_total: u64,
239}
240
241impl VmoSummary {
242 fn merge(&mut self, vmo_info: &Vmo, share_count: usize) {
243 self.count += 1;
244 self.committed_total += vmo_info.total_committed_bytes.unwrap();
245 self.populated_total += vmo_info.total_populated_bytes.unwrap();
246 self.committed_scaled +=
247 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
248 self.populated_scaled +=
249 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
250 if share_count == 1 {
251 self.committed_private += vmo_info.private_committed_bytes.unwrap();
252 self.populated_private += vmo_info.private_populated_bytes.unwrap();
253 }
254 }
255}
256
257impl PartialEq for VmoSummary {
258 fn eq(&self, other: &Self) -> bool {
259 self.count == other.count
260 && self.committed_private == other.committed_private
261 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
262 && self.committed_total == other.committed_total
263 && self.populated_private == other.populated_private
264 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
265 && self.populated_total == other.populated_total
266 }
267}
268
269pub fn vmo_name_to_digest_name(name: &str) -> &str {
272 static RULES: std::sync::LazyLock<[(regex::Regex, &'static str); 13]> =
274 std::sync::LazyLock::new(|| {
275 [
276 (
277 regex::Regex::new("ld\\.so\\.1-internal-heap|(^stack: msg of.*)").unwrap(),
278 "[process-bootstrap]",
279 ),
280 (regex::Regex::new("^blob-[0-9a-f]+$").unwrap(), "[blobs]"),
281 (regex::Regex::new("^inactive-blob-[0-9a-f]+$").unwrap(), "[inactive blobs]"),
282 (
283 regex::Regex::new("^thrd_t:0x.*|initial-thread|pthread_t:0x.*$").unwrap(),
284 "[stacks]",
285 ),
286 (regex::Regex::new("^data[0-9]*:.*$").unwrap(), "[data]"),
287 (regex::Regex::new("^bss[0-9]*:.*$").unwrap(), "[bss]"),
288 (regex::Regex::new("^relro:.*$").unwrap(), "[relro]"),
289 (regex::Regex::new("^$").unwrap(), "[unnamed]"),
290 (regex::Regex::new("^scudo:.*$").unwrap(), "[scudo]"),
291 (regex::Regex::new("^.*\\.so.*$").unwrap(), "[bootfs-libraries]"),
292 (regex::Regex::new("^stack_and_tls:.*$").unwrap(), "[bionic-stack]"),
293 (regex::Regex::new("^ext4!.*$").unwrap(), "[ext4]"),
294 (regex::Regex::new("^dalvik-.*$").unwrap(), "[dalvik]"),
295 ]
296 });
297 RULES.iter().find(|(regex, _)| regex.is_match(name)).map_or(name, |rule| rule.1)
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn rename_test() {
306 pretty_assertions::assert_eq!(
307 vmo_name_to_digest_name("ld.so.1-internal-heap"),
308 "[process-bootstrap]"
309 );
310 pretty_assertions::assert_eq!(
311 vmo_name_to_digest_name("stack: msg of 123"),
312 "[process-bootstrap]"
313 );
314 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-123"), "[blobs]");
315 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-15e0da8e"), "[blobs]");
316 pretty_assertions::assert_eq!(
317 vmo_name_to_digest_name("inactive-blob-123"),
318 "[inactive blobs]"
319 );
320 pretty_assertions::assert_eq!(vmo_name_to_digest_name("thrd_t:0x123"), "[stacks]");
321 pretty_assertions::assert_eq!(vmo_name_to_digest_name("initial-thread"), "[stacks]");
322 pretty_assertions::assert_eq!(vmo_name_to_digest_name("pthread_t:0x123"), "[stacks]");
323 pretty_assertions::assert_eq!(vmo_name_to_digest_name("data456:"), "[data]");
324 pretty_assertions::assert_eq!(vmo_name_to_digest_name("bss456:"), "[bss]");
325 pretty_assertions::assert_eq!(vmo_name_to_digest_name("relro:foobar"), "[relro]");
326 pretty_assertions::assert_eq!(vmo_name_to_digest_name(""), "[unnamed]");
327 pretty_assertions::assert_eq!(vmo_name_to_digest_name("scudo:primary"), "[scudo]");
328 pretty_assertions::assert_eq!(vmo_name_to_digest_name("libfoo.so.1"), "[bootfs-libraries]");
329 pretty_assertions::assert_eq!(vmo_name_to_digest_name("foobar"), "foobar");
330 pretty_assertions::assert_eq!(
331 vmo_name_to_digest_name("stack_and_tls:2331"),
332 "[bionic-stack]"
333 );
334 pretty_assertions::assert_eq!(vmo_name_to_digest_name("ext4!foobar"), "[ext4]");
335 pretty_assertions::assert_eq!(vmo_name_to_digest_name("dalvik-data1234"), "[dalvik]");
336 }
337}