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