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