1use crate::kernel_statistics::KernelStatistics;
6use crate::{
7 InflatedPrincipal, InflatedResource, PrincipalIdentifier, PrincipalType, ResourceReference,
8 ZXName,
9};
10use core::default::Default;
11use fidl_fuchsia_memory_attribution_plugin as fplugin;
12use fplugin::Vmo;
13use serde::Serialize;
14use std::cell::RefCell;
15use std::collections::{HashMap, HashSet};
16use std::fmt::Display;
17
18const FLOAT_COMPARISON_EPSILON: f64 = 1e-10;
20
21#[derive(Debug, PartialEq, Serialize)]
22pub struct ComponentProfileResult {
23 pub kernel: KernelStatistics,
24 pub principals: Vec<PrincipalSummary>,
25 pub undigested: u64,
27}
28
29#[derive(Debug, PartialEq, Serialize)]
35pub struct MemorySummary {
36 pub principals: Vec<PrincipalSummary>,
37 pub undigested: u64,
39}
40
41impl MemorySummary {
42 pub(crate) fn build(
43 principals: &HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
44 resources: &HashMap<u64, RefCell<InflatedResource>>,
45 resource_names: &Vec<ZXName>,
46 ) -> MemorySummary {
47 let mut output = MemorySummary { principals: Default::default(), undigested: 0 };
48 for principal in principals.values() {
49 output.principals.push(MemorySummary::build_one_principal(
50 &principal,
51 &principals,
52 &resources,
53 &resource_names,
54 ));
55 }
56
57 output.principals.sort_unstable_by_key(|p| -(p.populated_total as i64));
58
59 let mut undigested = 0;
60 for (_, resource_ref) in resources {
61 let resource = &resource_ref.borrow();
62 if resource.claims.is_empty() {
63 match &resource.resource.resource_type {
64 fplugin::ResourceType::Job(_) | fplugin::ResourceType::Process(_) => {}
65 fplugin::ResourceType::Vmo(vmo) => {
66 undigested += vmo.scaled_populated_bytes.unwrap();
67 }
68 _ => todo!(),
69 }
70 }
71 }
72 output.undigested = undigested;
73 output
74 }
75
76 fn build_one_principal(
77 principal_cell: &RefCell<InflatedPrincipal>,
78 principals: &HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
79 resources: &HashMap<u64, RefCell<InflatedResource>>,
80 resource_names: &Vec<ZXName>,
81 ) -> PrincipalSummary {
82 let principal = principal_cell.borrow();
83 let mut output = PrincipalSummary {
84 name: principal.name().to_owned(),
85 id: principal.principal.identifier.0,
86 principal_type: match &principal.principal.principal_type {
87 PrincipalType::Runnable => "R",
88 PrincipalType::Part => "P",
89 }
90 .to_owned(),
91 committed_private: 0,
92 committed_scaled: 0.0,
93 committed_total: 0,
94 populated_private: 0,
95 populated_scaled: 0.0,
96 populated_total: 0,
97 attributor: principal
98 .principal
99 .parent
100 .as_ref()
101 .map(|p| principals.get(p))
102 .flatten()
103 .map(|p| p.borrow().name().to_owned()),
104 processes: Vec::new(),
105 vmos: HashMap::new(),
106 };
107
108 for resource_id in &principal.resources {
109 if !resources.contains_key(resource_id) {
110 continue;
111 }
112
113 let resource = resources.get(resource_id).unwrap().borrow();
114 let share_count = resource
115 .claims
116 .iter()
117 .map(|c| c.subject)
118 .collect::<HashSet<PrincipalIdentifier>>()
119 .len();
120 match &resource.resource.resource_type {
121 fplugin::ResourceType::Job(_) => todo!(),
122 fplugin::ResourceType::Process(_) => {
123 output.processes.push(format!(
124 "{} ({})",
125 resource_names.get(resource.resource.name_index).unwrap().clone(),
126 resource.resource.koid
127 ));
128 }
129 fplugin::ResourceType::Vmo(vmo_info) => {
130 output.committed_total += vmo_info.total_committed_bytes.unwrap();
131 output.populated_total += vmo_info.total_populated_bytes.unwrap();
132 output.committed_scaled +=
133 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
134 output.populated_scaled +=
135 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
136 if share_count == 1 {
137 output.committed_private += vmo_info.private_committed_bytes.unwrap();
138 output.populated_private += vmo_info.private_populated_bytes.unwrap();
139 }
140 output
141 .vmos
142 .entry(
143 vmo_name_to_digest_zxname(
144 &resource_names.get(resource.resource.name_index).unwrap(),
145 )
146 .clone(),
147 )
148 .or_default()
149 .merge(vmo_info, share_count);
150 }
151 _ => todo!(),
152 }
153 }
154
155 for (_source, attribution) in &principal.attribution_claims {
156 for resource in &attribution.resources {
157 if let ResourceReference::ProcessMapped {
158 process: process_mapped,
159 base: _,
160 len: _,
161 } = resource
162 {
163 if let Some(process_ref) = resources.get(&process_mapped) {
164 let process = process_ref.borrow();
165 output.processes.push(format!(
166 "{} ({})",
167 resource_names.get(process.resource.name_index).unwrap().clone(),
168 process.resource.koid
169 ));
170 }
171 }
172 }
173 }
174
175 output.processes.sort();
176 output
177 }
178}
179
180impl Display for MemorySummary {
181 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 Ok(())
183 }
184}
185
186#[derive(Debug, Serialize)]
188pub struct PrincipalSummary {
189 pub id: u64,
192 pub name: String,
194 pub principal_type: String,
196 pub committed_private: u64,
198 pub committed_scaled: f64,
201 pub committed_total: u64,
203 pub populated_private: u64,
205 pub populated_scaled: f64,
208 pub populated_total: u64,
210
211 pub attributor: Option<String>,
213 pub processes: Vec<String>,
215 pub vmos: HashMap<ZXName, VmoSummary>,
217}
218
219impl PartialEq for PrincipalSummary {
220 fn eq(&self, other: &Self) -> bool {
221 self.id == other.id
222 && self.name == other.name
223 && self.principal_type == other.principal_type
224 && self.committed_private == other.committed_private
225 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
226 && self.committed_total == other.committed_total
227 && self.populated_private == other.populated_private
228 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
229 && self.populated_total == other.populated_total
230 && self.attributor == other.attributor
231 && self.processes == other.processes
232 && self.vmos == other.vmos
233 }
234}
235
236#[derive(Default, Debug, Serialize)]
238pub struct VmoSummary {
239 pub count: u64,
241 pub committed_private: u64,
244 pub committed_scaled: f64,
247 pub committed_total: u64,
249 pub populated_private: u64,
252 pub populated_scaled: f64,
255 pub populated_total: u64,
257}
258
259impl VmoSummary {
260 fn merge(&mut self, vmo_info: &Vmo, share_count: usize) {
261 self.count += 1;
262 self.committed_total += vmo_info.total_committed_bytes.unwrap();
263 self.populated_total += vmo_info.total_populated_bytes.unwrap();
264 self.committed_scaled +=
265 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
266 self.populated_scaled +=
267 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
268 if share_count == 1 {
269 self.committed_private += vmo_info.private_committed_bytes.unwrap();
270 self.populated_private += vmo_info.private_populated_bytes.unwrap();
271 }
272 }
273}
274
275impl PartialEq for VmoSummary {
276 fn eq(&self, other: &Self) -> bool {
277 self.count == other.count
278 && self.committed_private == other.committed_private
279 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
280 && self.committed_total == other.committed_total
281 && self.populated_private == other.populated_private
282 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
283 && self.populated_total == other.populated_total
284 }
285}
286const VMO_DIGEST_NAME_MAPPING: [(&str, &str); 13] = [
287 ("ld\\.so\\.1-internal-heap|(^stack: msg of.*)", "[process-bootstrap]"),
288 ("^blob-[0-9a-f]+$", "[blobs]"),
289 ("^inactive-blob-[0-9a-f]+$", "[inactive blobs]"),
290 ("^thrd_t:0x.*|initial-thread|pthread_t:0x.*$", "[stacks]"),
291 ("^data[0-9]*:.*$", "[data]"),
292 ("^bss[0-9]*:.*$", "[bss]"),
293 ("^relro:.*$", "[relro]"),
294 ("^$", "[unnamed]"),
295 ("^scudo:.*$", "[scudo]"),
296 ("^.*\\.so.*$", "[bootfs-libraries]"),
297 ("^stack_and_tls:.*$", "[bionic-stack]"),
298 ("^ext4!.*$", "[ext4]"),
299 ("^dalvik-.*$", "[dalvik]"),
300];
301
302pub fn vmo_name_to_digest_name(name: &str) -> &str {
305 static RULES: std::sync::LazyLock<Vec<(regex::Regex, &'static str)>> =
306 std::sync::LazyLock::new(|| {
307 VMO_DIGEST_NAME_MAPPING
308 .iter()
309 .map(|&(pattern, replacement)| (regex::Regex::new(pattern).unwrap(), replacement))
310 .collect()
311 });
312 RULES.iter().find(|(regex, _)| regex.is_match(name)).map_or(name, |rule| rule.1)
313}
314
315pub fn vmo_name_to_digest_zxname(name: &ZXName) -> &ZXName {
316 static RULES: std::sync::LazyLock<Vec<(regex::bytes::Regex, ZXName)>> =
317 std::sync::LazyLock::new(|| {
318 VMO_DIGEST_NAME_MAPPING
319 .iter()
320 .map(|&(pattern, replacement)| {
321 (
322 regex::bytes::Regex::new(pattern).unwrap(),
323 ZXName::try_from_bytes(replacement.as_bytes()).unwrap(),
324 )
325 })
326 .collect()
327 });
328 RULES.iter().find(|(regex, _)| regex.is_match(name.as_bstr())).map_or(name, |rule| &rule.1)
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn rename_zx_test() {
337 pretty_assertions::assert_eq!(
338 vmo_name_to_digest_zxname(&ZXName::from_string_lossy("ld.so.1-internal-heap")),
339 &ZXName::from_string_lossy("[process-bootstrap]"),
340 );
341 }
342
343 #[test]
344 fn rename_zx_test_small_name() {
345 pretty_assertions::assert_eq!(
348 vmo_name_to_digest_zxname(&ZXName::from_string_lossy("blob-1234")),
349 &ZXName::from_string_lossy("[blobs]"),
350 );
351 }
352
353 #[test]
354 fn rename_test() {
355 pretty_assertions::assert_eq!(
356 vmo_name_to_digest_name("ld.so.1-internal-heap"),
357 "[process-bootstrap]"
358 );
359 pretty_assertions::assert_eq!(
360 vmo_name_to_digest_name("stack: msg of 123"),
361 "[process-bootstrap]"
362 );
363 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-123"), "[blobs]");
364 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-15e0da8e"), "[blobs]");
365 pretty_assertions::assert_eq!(
366 vmo_name_to_digest_name("inactive-blob-123"),
367 "[inactive blobs]"
368 );
369 pretty_assertions::assert_eq!(vmo_name_to_digest_name("thrd_t:0x123"), "[stacks]");
370 pretty_assertions::assert_eq!(vmo_name_to_digest_name("initial-thread"), "[stacks]");
371 pretty_assertions::assert_eq!(vmo_name_to_digest_name("pthread_t:0x123"), "[stacks]");
372 pretty_assertions::assert_eq!(vmo_name_to_digest_name("data456:"), "[data]");
373 pretty_assertions::assert_eq!(vmo_name_to_digest_name("bss456:"), "[bss]");
374 pretty_assertions::assert_eq!(vmo_name_to_digest_name("relro:foobar"), "[relro]");
375 pretty_assertions::assert_eq!(vmo_name_to_digest_name(""), "[unnamed]");
376 pretty_assertions::assert_eq!(vmo_name_to_digest_name("scudo:primary"), "[scudo]");
377 pretty_assertions::assert_eq!(vmo_name_to_digest_name("libfoo.so.1"), "[bootfs-libraries]");
378 pretty_assertions::assert_eq!(vmo_name_to_digest_name("foobar"), "foobar");
379 pretty_assertions::assert_eq!(
380 vmo_name_to_digest_name("stack_and_tls:2331"),
381 "[bionic-stack]"
382 );
383 pretty_assertions::assert_eq!(vmo_name_to_digest_name("ext4!foobar"), "[ext4]");
384 pretty_assertions::assert_eq!(vmo_name_to_digest_name("dalvik-data1234"), "[dalvik]");
385 }
386}