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