1use crate::digest::Digest;
6use crate::macros::vmo_digests;
7use crate::{
8 Claim, GlobalPrincipalIdentifier, InflatedPrincipal, InflatedResource, PrincipalType, ZXName,
9 fplugin_serde,
10};
11use bstr::ByteSlice;
12use core::default::Default;
13use fidl_fuchsia_memory_attribution_plugin_common as fplugin;
14use fplugin::Vmo;
15#[cfg(target_os = "fuchsia")]
16use fuchsia_trace::duration;
17use rustc_hash::FxHashMap;
18use serde::Serialize;
19use std::collections::{HashMap, HashSet};
20use std::fmt::Display;
21
22const FLOAT_COMPARISON_EPSILON: f64 = 1e-10;
24
25#[derive(Debug, Default, PartialEq, Serialize)]
26pub struct ComponentSummaryProfileResult {
27 pub kernel: fplugin_serde::KernelStatistics,
28 pub principals: Vec<PrincipalSummary>,
29 pub unclaimed: u64,
31 #[serde(with = "fplugin_serde::PerformanceImpactMetricsDef")]
32 pub performance: fplugin::PerformanceImpactMetrics,
33 pub digest: Option<Digest>,
34}
35
36#[derive(Debug, PartialEq, Serialize)]
42pub struct MemorySummary {
43 pub principals: Vec<PrincipalSummary>,
44 pub unclaimed: u64,
46}
47
48fn compute_share_count(
49 claims: &HashSet<Claim>,
50 subjects_buf: &mut Vec<GlobalPrincipalIdentifier>,
51) -> usize {
52 match claims.len() {
53 0 => 0,
54 1 => 1,
55 2 => {
56 let mut iter = claims.iter();
57 let s1 = iter.next().unwrap().subject;
58 let s2 = iter.next().unwrap().subject;
59 if s1 == s2 { 1 } else { 2 }
60 }
61 _ => {
62 subjects_buf.clear();
63 subjects_buf.extend(claims.iter().map(|c| c.subject));
64 subjects_buf.sort_unstable();
65 subjects_buf.dedup();
66 subjects_buf.len()
67 }
68 }
69}
70
71impl MemorySummary {
72 pub(crate) fn build(
73 principals: &FxHashMap<GlobalPrincipalIdentifier, InflatedPrincipal>,
74 resources: &FxHashMap<u64, InflatedResource>,
75 resource_names: &Vec<ZXName>,
76 ) -> MemorySummary {
77 #[cfg(target_os = "fuchsia")]
78 duration!(crate::CATEGORY_MEMORY_CAPTURE, c"MemorySummary::build");
79 let digested_names: Vec<&ZXName> =
80 resource_names.iter().map(vmo_name_to_digest_zxname).collect();
81 let mut subjects_buf = Vec::new();
82 let share_counts: FxHashMap<u64, usize> = resources
83 .iter()
84 .map(|(&koid, resource)| {
85 (koid, compute_share_count(&resource.claims, &mut subjects_buf))
86 })
87 .collect();
88
89 let mut output = MemorySummary { principals: Default::default(), unclaimed: 0 };
90 for principal in principals.values() {
91 output.principals.push(MemorySummary::build_one_principal(
92 &principal,
93 &principals,
94 &resources,
95 &resource_names,
96 &digested_names,
97 &share_counts,
98 ));
99 }
100
101 output.principals.sort_unstable_by(|a, b| b.populated_total.cmp(&a.populated_total));
102
103 let mut unclaimed = 0;
104 for (_, resource) in resources {
105 if resource.claims.is_empty() {
106 match &resource.resource.resource_type {
107 fplugin::ResourceType::Job(_) | fplugin::ResourceType::Process(_) => {}
108 fplugin::ResourceType::Vmo(vmo) => {
109 unclaimed += vmo.scaled_populated_bytes.unwrap();
110 }
111 _ => todo!(),
112 }
113 }
114 }
115 output.unclaimed = unclaimed;
116 output
117 }
118
119 fn build_one_principal(
120 principal: &InflatedPrincipal,
121 principals: &FxHashMap<GlobalPrincipalIdentifier, InflatedPrincipal>,
122 resources: &FxHashMap<u64, InflatedResource>,
123 resource_names: &Vec<ZXName>,
124 digested_names: &[&ZXName],
125 share_counts: &FxHashMap<u64, usize>,
126 ) -> PrincipalSummary {
127 let mut output = PrincipalSummary {
128 name: principal.name().to_owned(),
129 id: principal.principal.identifier.0.into(),
130 principal_type: match &principal.principal.principal_type {
131 PrincipalType::Runnable => "R",
132 PrincipalType::Part => "P",
133 }
134 .to_owned(),
135 committed_private: 0,
136 committed_scaled: 0.0,
137 committed_total: 0,
138 populated_private: 0,
139 populated_scaled: 0.0,
140 populated_total: 0,
141 attributor: principal
142 .principal
143 .parent
144 .as_ref()
145 .and_then(|p| principals.get(p))
146 .map(|p| p.name().to_owned()),
147 processes: Vec::new(),
148 vmos: HashMap::new(),
149 };
150
151 for resource_id in &principal.resources {
152 let Some(resource) = resources.get(resource_id) else {
153 continue;
154 };
155 let share_count = *share_counts.get(resource_id).unwrap();
156 match &resource.resource.resource_type {
157 fplugin::ResourceType::Job(_) => todo!(),
158 fplugin::ResourceType::Process(_) => {
159 output.processes.push(format!(
160 "{} ({})",
161 resource_names.get(resource.resource.name_index).unwrap(),
162 resource.resource.koid
163 ));
164 }
165 fplugin::ResourceType::Vmo(vmo_info) => {
166 output.committed_total += vmo_info.total_committed_bytes.unwrap();
167 output.populated_total += vmo_info.total_populated_bytes.unwrap();
168 output.committed_scaled +=
169 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
170 output.populated_scaled +=
171 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
172 if share_count == 1 {
173 output.committed_private += vmo_info.private_committed_bytes.unwrap();
174 output.populated_private += vmo_info.private_populated_bytes.unwrap();
175 }
176 let digest_name = digested_names[resource.resource.name_index];
177 if let Some(summary) = output.vmos.get_mut(digest_name) {
180 summary.merge(vmo_info, share_count);
181 } else {
182 let mut summary = VmoSummary::default();
183 summary.merge(vmo_info, share_count);
184 output.vmos.insert(digest_name.clone(), summary);
185 }
186 }
187 _ => todo!(),
188 }
189 }
190
191 for process_mapped in &principal.mapped_processes {
192 if let Some(process) = resources.get(process_mapped) {
193 output.processes.push(format!(
194 "{} ({})",
195 resource_names.get(process.resource.name_index).unwrap(),
196 process.resource.koid
197 ));
198 }
199 }
200
201 output.processes.sort();
202 output
203 }
204}
205
206impl Display for MemorySummary {
207 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 Ok(())
209 }
210}
211
212#[derive(Debug, Serialize)]
214pub struct PrincipalSummary {
215 pub id: u64,
218 pub name: String,
220 pub principal_type: String,
222 pub committed_private: u64,
224 pub committed_scaled: f64,
227 pub committed_total: u64,
229 pub populated_private: u64,
231 pub populated_scaled: f64,
234 pub populated_total: u64,
236 pub attributor: Option<String>,
238 pub processes: Vec<String>,
240 pub vmos: HashMap<ZXName, VmoSummary>,
242}
243
244impl PartialEq for PrincipalSummary {
245 fn eq(&self, other: &Self) -> bool {
246 self.id == other.id
247 && self.name == other.name
248 && self.principal_type == other.principal_type
249 && self.committed_private == other.committed_private
250 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
251 && self.committed_total == other.committed_total
252 && self.populated_private == other.populated_private
253 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
254 && self.populated_total == other.populated_total
255 && self.attributor == other.attributor
256 && self.processes == other.processes
257 && self.vmos == other.vmos
258 }
259}
260
261#[derive(Default, Debug, Serialize)]
263pub struct VmoSummary {
264 pub count: u64,
266 pub committed_private: u64,
269 pub committed_scaled: f64,
272 pub committed_total: u64,
274 pub populated_private: u64,
277 pub populated_scaled: f64,
280 pub populated_total: u64,
282}
283
284impl VmoSummary {
285 fn merge(&mut self, vmo_info: &Vmo, share_count: usize) {
286 self.count += 1;
287 self.committed_total += vmo_info.total_committed_bytes.unwrap();
288 self.populated_total += vmo_info.total_populated_bytes.unwrap();
289 self.committed_scaled +=
290 vmo_info.scaled_committed_bytes.unwrap() as f64 / share_count as f64;
291 self.populated_scaled +=
292 vmo_info.scaled_populated_bytes.unwrap() as f64 / share_count as f64;
293 if share_count == 1 {
294 self.committed_private += vmo_info.private_committed_bytes.unwrap();
295 self.populated_private += vmo_info.private_populated_bytes.unwrap();
296 }
297 }
298}
299
300impl PartialEq for VmoSummary {
301 fn eq(&self, other: &Self) -> bool {
302 self.count == other.count
303 && self.committed_private == other.committed_private
304 && (self.committed_scaled - other.committed_scaled).abs() < FLOAT_COMPARISON_EPSILON
305 && self.committed_total == other.committed_total
306 && self.populated_private == other.populated_private
307 && (self.populated_scaled - other.populated_scaled).abs() < FLOAT_COMPARISON_EPSILON
308 && self.populated_total == other.populated_total
309 }
310}
311vmo_digests! {
312 (
313 ProcessBootstrap,
314 "[process-bootstrap]",
315 or(contains("ld.so.1-internal-heap"), starts_with("stack: msg of"))
316 ),
317 (Blobs, "[blobs]", exact("blob-", hex())),
318 (InactiveBlobs, "[inactive blobs]", exact("inactive-blob-", hex())),
319 (
320 Stacks,
321 "[stacks]",
322 or(
323 starts_with("thrd_t:0x"),
324 contains("initial-thread"),
325 contains("pthread_t:0x"),
326 contains("pthread_create:0x")
327 )
328 ),
329 (Data, "[data]", starts_with("data", digits(), ":")),
330 (Bss, "[bss]", starts_with("bss", digits(), ":")),
331 (Relro, "[relro]", starts_with("relro:")),
332 (Unnamed, "[unnamed]", exact("")),
333 (Scudo, "[scudo]", starts_with("scudo:")),
334 (BootfsLibraries, "[bootfs-libraries]", contains(".so")),
335 (BionicStack, "[bionic-stack]", starts_with("stack_and_tls:")),
336 (Ext4, "[ext4]", starts_with("ext4!")),
337 (Dalvik, "[dalvik]", starts_with("dalvik-")),
338 (Bootfs, "[bootfs]", or(exact("bootfs"), starts_with("bootfs:"))),
339 (RestrictedStateVmo, "[restricted_state_vmo]", exact("restricted_state_vmo:", digits())),
340}
341
342#[cfg(test)]
343const VMO_DIGEST_NAME_MAPPING: [(&str, &str); 15] = [
344 ("ld\\.so\\.1-internal-heap|(^stack: msg of.*)", "[process-bootstrap]"),
345 ("^blob-[0-9a-f]+$", "[blobs]"),
346 ("^inactive-blob-[0-9a-f]+$", "[inactive blobs]"),
347 ("^thrd_t:0x.*|initial-thread|pthread_(t|create):0x.*$", "[stacks]"),
348 ("^data[0-9]*:.*$", "[data]"),
349 ("^bss[0-9]*:.*$", "[bss]"),
350 ("^relro:.*$", "[relro]"),
351 ("^$", "[unnamed]"),
352 ("^scudo:.*$", "[scudo]"),
353 ("^.*\\.so.*$", "[bootfs-libraries]"),
354 ("^stack_and_tls:.*$", "[bionic-stack]"),
355 ("^ext4!.*$", "[ext4]"),
356 ("^dalvik-.*$", "[dalvik]"),
357 ("^bootfs(:.*)?$", "[bootfs]"),
358 ("^restricted_state_vmo:[0-9]+$", "[restricted_state_vmo]"),
359];
360
361pub fn vmo_name_to_digest_name(name: &str) -> &str {
364 if let Some(category) = match_vmo_digest(name.trim()) { category.as_str() } else { name }
365}
366
367pub fn vmo_name_to_digest_zxname(name: &ZXName) -> &ZXName {
368 if let Ok(name_str) = name.as_bstr().to_str() {
369 if let Some(category) = match_vmo_digest(name_str) {
370 return category.as_zxname();
371 }
372 }
373 name
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::{Claim, ClaimType, GlobalPrincipalIdentifier, InflatedPrincipal, InflatedResource};
380
381 #[test]
382 fn rename_zx_test() {
383 pretty_assertions::assert_eq!(
384 vmo_name_to_digest_zxname(&ZXName::from_string_lossy("ld.so.1-internal-heap")),
385 &ZXName::from_string_lossy("[process-bootstrap]"),
386 );
387 }
388
389 #[test]
390 fn rename_zx_test_small_name() {
391 pretty_assertions::assert_eq!(
394 vmo_name_to_digest_zxname(&ZXName::from_string_lossy("blob-1234")),
395 &ZXName::from_string_lossy("[blobs]"),
396 );
397 }
398
399 #[test]
400 fn rename_test() {
401 pretty_assertions::assert_eq!(
402 vmo_name_to_digest_name("ld.so.1-internal-heap"),
403 "[process-bootstrap]"
404 );
405 pretty_assertions::assert_eq!(
406 vmo_name_to_digest_name("stack: msg of 123"),
407 "[process-bootstrap]"
408 );
409 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-123"), "[blobs]");
410 pretty_assertions::assert_eq!(vmo_name_to_digest_name("blob-15e0da8e"), "[blobs]");
411 pretty_assertions::assert_eq!(
412 vmo_name_to_digest_name("inactive-blob-123"),
413 "[inactive blobs]"
414 );
415 pretty_assertions::assert_eq!(vmo_name_to_digest_name("thrd_t:0x123"), "[stacks]");
416 pretty_assertions::assert_eq!(vmo_name_to_digest_name("initial-thread"), "[stacks]");
417 pretty_assertions::assert_eq!(vmo_name_to_digest_name("pthread_t:0x123"), "[stacks]");
418 pretty_assertions::assert_eq!(
419 vmo_name_to_digest_name("pthread_create:0xfa124714"),
420 "[stacks]"
421 );
422 pretty_assertions::assert_eq!(vmo_name_to_digest_name("data456:"), "[data]");
423 pretty_assertions::assert_eq!(vmo_name_to_digest_name("bss456:"), "[bss]");
424 pretty_assertions::assert_eq!(vmo_name_to_digest_name("relro:foobar"), "[relro]");
425 pretty_assertions::assert_eq!(vmo_name_to_digest_name(""), "[unnamed]");
426 pretty_assertions::assert_eq!(vmo_name_to_digest_name("scudo:primary"), "[scudo]");
427 pretty_assertions::assert_eq!(vmo_name_to_digest_name("libfoo.so.1"), "[bootfs-libraries]");
428 pretty_assertions::assert_eq!(vmo_name_to_digest_name("foobar"), "foobar");
429 pretty_assertions::assert_eq!(
430 vmo_name_to_digest_name("stack_and_tls:2331"),
431 "[bionic-stack]"
432 );
433 pretty_assertions::assert_eq!(vmo_name_to_digest_name("ext4!foobar"), "[ext4]");
434 pretty_assertions::assert_eq!(vmo_name_to_digest_name("dalvik-data1234"), "[dalvik]");
435 pretty_assertions::assert_eq!(
436 vmo_name_to_digest_name("restricted_state_vmo:119723"),
437 "[restricted_state_vmo]"
438 );
439 }
440
441 #[test]
443 fn test_vmo_digest_rules_match_regex() {
444 let test_strings = [
445 "ld.so.1-internal-heap",
446 "prefix-ld.so.1-internal-heap",
447 "stack: msg of something",
448 "stack: msg of",
449 "stack: msg",
450 "blob-1234",
451 "blob-abcdef",
452 "blob-0123456789abcdef",
453 "blob-",
454 "blob-123g",
455 "blob-ABC",
456 "inactive-blob-1234",
457 "inactive-blob-abcdef",
458 "inactive-blob-",
459 "inactive-blob-xyz",
460 "thrd_t:0x123",
461 "thrd_t:0x",
462 "prefix-thrd_t:0x123",
463 "initial-thread",
464 "prefix-initial-thread-suffix",
465 "pthread_t:0x123",
466 "pthread_create:0xfa124714",
467 "data:",
468 "data0:",
469 "data123:foo",
470 "data:bar",
471 "data_foo:",
472 "data",
473 "bss:",
474 "bss99:",
475 "bss456:bar",
476 "bss_foo:",
477 "bss",
478 "relro:",
479 "relro:foo",
480 "relro_other",
481 "",
482 "scudo:",
483 "scudo:primary",
484 "scudo_other",
485 "libfoo.so.1",
486 "test.so",
487 ".so",
488 "stack_and_tls:123",
489 "stack_and_tls:",
490 "ext4!foobar",
491 "ext4!",
492 "dalvik-data",
493 "dalvik-",
494 "bootfs",
495 "bootfs:",
496 "bootfs:bin",
497 "bootfs_other",
498 "restricted_state_vmo:",
499 "restricted_state_vmo:0",
500 "restricted_state_vmo:12345",
501 "restricted_state_vmo:abc",
502 "restricted_state_vmo:12a",
503 "foobar",
504 "random_string_123",
505 "other-blob-1234",
506 ];
507
508 static RULES: std::sync::LazyLock<Vec<(regex_lite::Regex, &'static str)>> =
509 std::sync::LazyLock::new(|| {
510 VMO_DIGEST_NAME_MAPPING
511 .iter()
512 .map(|&(pattern, replacement)| {
513 (regex_lite::Regex::new(pattern).unwrap(), replacement)
514 })
515 .collect()
516 });
517
518 for s in test_strings {
519 let expected =
520 RULES.iter().find(|(regex, _)| regex.is_match(s)).map_or(s, |rule| rule.1);
521 let actual = vmo_name_to_digest_name(s);
522 assert_eq!(actual, expected, "Mismatch for string: {:?}", s);
523
524 let zx_in = ZXName::from_string_lossy(s);
525 let zx_expected = ZXName::from_string_lossy(expected);
526 let zx_actual = vmo_name_to_digest_zxname(&zx_in);
527 assert_eq!(zx_actual, &zx_expected, "ZXName mismatch for string: {:?}", s);
528 }
529 }
530
531 fn make_test_principal(id: u64, name: &str) -> InflatedPrincipal {
532 InflatedPrincipal::new(
533 fplugin::Principal {
534 identifier: Some(fplugin::PrincipalIdentifier { id }),
535 description: Some(fplugin::Description::Component(name.to_owned())),
536 principal_type: Some(fplugin::PrincipalType::Runnable),
537 parent: None,
538 ..Default::default()
539 }
540 .into(),
541 )
542 }
543
544 fn make_test_vmo_resource(
545 koid: u64,
546 name_index: usize,
547 committed: u64,
548 populated: u64,
549 claims: Vec<(u64, u64)>,
550 ) -> InflatedResource {
551 let mut res = InflatedResource::new(
552 fplugin::Resource {
553 koid: Some(koid),
554 name_index: Some(name_index as u64),
555 resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
556 private_committed_bytes: Some(committed),
557 private_populated_bytes: Some(populated),
558 scaled_committed_bytes: Some(committed),
559 scaled_populated_bytes: Some(populated),
560 total_committed_bytes: Some(committed),
561 total_populated_bytes: Some(populated),
562 ..Default::default()
563 })),
564 ..Default::default()
565 }
566 .into(),
567 );
568 for (source, subject) in claims {
569 res.claims.insert(Claim {
570 source: GlobalPrincipalIdentifier::new_for_test(source),
571 subject: GlobalPrincipalIdentifier::new_for_test(subject),
572 claim_type: ClaimType::Direct,
573 });
574 }
575 res
576 }
577
578 #[test]
587 fn test_memory_summary_build_sorting_and_overflow() {
588 let mut principals = FxHashMap::default();
589 let mut p1 = make_test_principal(1, "small_principal");
590 p1.resources.push(101);
591 let mut p2 = make_test_principal(2, "large_principal");
592 p2.resources.push(102);
593 let mut p3 = make_test_principal(3, "medium_principal");
594 p3.resources.push(103);
595 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
596 principals.insert(GlobalPrincipalIdentifier::new_for_test(2), p2);
597 principals.insert(GlobalPrincipalIdentifier::new_for_test(3), p3);
598
599 let mut resources = FxHashMap::default();
600 resources
601 .insert(101, make_test_vmo_resource(101, 0, 100_000_000, 100_000_000, vec![(1, 1)]));
602 resources.insert(
603 102,
604 make_test_vmo_resource(102, 1, 1_000_000_000, 1_000_000_000, vec![(2, 2)]),
605 );
606 resources
607 .insert(103, make_test_vmo_resource(103, 2, 500_000_000, 500_000_000, vec![(3, 3)]));
608
609 let resource_names = vec![
610 ZXName::from_string_lossy("vmo_1"),
611 ZXName::from_string_lossy("vmo_2"),
612 ZXName::from_string_lossy("vmo_3"),
613 ];
614
615 let summary = MemorySummary::build(&principals, &resources, &resource_names);
616 assert_eq!(summary.principals.len(), 3);
617 assert_eq!(summary.principals[0].name, "large_principal");
618 assert_eq!(summary.principals[0].populated_total, 1_000_000_000);
619 assert_eq!(summary.principals[1].name, "medium_principal");
620 assert_eq!(summary.principals[1].populated_total, 500_000_000);
621 assert_eq!(summary.principals[2].name, "small_principal");
622 assert_eq!(summary.principals[2].populated_total, 100_000_000);
623 }
624
625 #[test]
634 fn test_memory_summary_vmo_digest_aggregation() {
635 let mut principals = FxHashMap::default();
636 let mut p1 = make_test_principal(1, "blob_owner");
637 p1.resources.push(1001);
638 p1.resources.push(1002);
639 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
640
641 let mut resources = FxHashMap::default();
642 resources.insert(1001, make_test_vmo_resource(1001, 0, 100, 200, vec![(1, 1)]));
643 resources.insert(1002, make_test_vmo_resource(1002, 1, 300, 400, vec![(1, 1)]));
644
645 let resource_names =
646 vec![ZXName::from_string_lossy("blob-1111"), ZXName::from_string_lossy("blob-2222")];
647
648 let summary = MemorySummary::build(&principals, &resources, &resource_names);
649 assert_eq!(summary.principals.len(), 1);
650 let p_summary = &summary.principals[0];
651 assert_eq!(p_summary.vmos.len(), 1);
652
653 let blob_digest = ZXName::from_string_lossy("[blobs]");
654 let vmo_summary = p_summary.vmos.get(&blob_digest).expect("Should aggregate under [blobs]");
655 assert_eq!(vmo_summary.count, 2);
656 assert_eq!(vmo_summary.committed_total, 400);
657 assert_eq!(vmo_summary.populated_total, 600);
658 assert_eq!(vmo_summary.committed_private, 400);
659 assert_eq!(vmo_summary.populated_private, 600);
660 }
661
662 #[test]
670 fn test_memory_summary_process_formatting_and_sorting() {
671 let mut principals = FxHashMap::default();
672 let mut p1 = make_test_principal(1, "proc_owner");
673 p1.resources.push(2001);
674 p1.resources.push(2002);
675 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
676
677 let mut resources = FxHashMap::default();
678 let r1 = InflatedResource::new(
679 fplugin::Resource {
680 koid: Some(2001),
681 name_index: Some(0),
682 resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
683 vmos: Some(vec![]),
684 mappings: None,
685 ..Default::default()
686 })),
687 ..Default::default()
688 }
689 .into(),
690 );
691 let r2 = InflatedResource::new(
692 fplugin::Resource {
693 koid: Some(2002),
694 name_index: Some(1),
695 resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
696 vmos: Some(vec![]),
697 mappings: None,
698 ..Default::default()
699 })),
700 ..Default::default()
701 }
702 .into(),
703 );
704 resources.insert(2001, r1);
705 resources.insert(2002, r2);
706
707 let resource_names = vec![
708 ZXName::from_string_lossy("zeta_process"),
709 ZXName::from_string_lossy("alpha_process"),
710 ];
711
712 let summary = MemorySummary::build(&principals, &resources, &resource_names);
713 assert_eq!(summary.principals.len(), 1);
714 assert_eq!(
715 summary.principals[0].processes,
716 vec!["alpha_process (2002)".to_owned(), "zeta_process (2001)".to_owned()]
717 );
718 }
719
720 #[test]
729 fn test_memory_summary_share_count_calculation() {
730 let mut principals = FxHashMap::default();
731 let mut p1 = make_test_principal(1, "owner1");
732 let mut p2 = make_test_principal(2, "owner2");
733 p1.resources.push(3001);
734 p2.resources.push(3001);
735 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
736 principals.insert(GlobalPrincipalIdentifier::new_for_test(2), p2);
737
738 let mut resources = FxHashMap::default();
739 resources.insert(3001, make_test_vmo_resource(3001, 0, 1000, 2000, vec![(1, 1), (2, 2)]));
740
741 let resource_names = vec![ZXName::from_string_lossy("shared_mem")];
742 let summary = MemorySummary::build(&principals, &resources, &resource_names);
743
744 assert_eq!(summary.principals.len(), 2);
745 for p_sum in &summary.principals {
746 assert_eq!(p_sum.committed_total, 1000);
747 assert_eq!(p_sum.populated_total, 2000);
748 assert_eq!(p_sum.committed_scaled, 500.0);
749 assert_eq!(p_sum.populated_scaled, 1000.0);
750 assert_eq!(p_sum.committed_private, 0);
751 assert_eq!(p_sum.populated_private, 0);
752 }
753 }
754
755 #[test]
762 fn test_memory_summary_unclaimed_vmos() {
763 let principals = FxHashMap::default();
764 let mut resources = FxHashMap::default();
765 resources.insert(4001, make_test_vmo_resource(4001, 0, 500, 1234, vec![]));
766
767 let resource_names = vec![ZXName::from_string_lossy("unclaimed_vmo")];
768 let summary = MemorySummary::build(&principals, &resources, &resource_names);
769 assert_eq!(summary.unclaimed, 1234);
770 }
771
772 #[test]
774 fn test_compute_share_count() {
775 let mut subjects_buf = Vec::new();
776
777 let empty_claims = HashSet::new();
778 assert_eq!(compute_share_count(&empty_claims, &mut subjects_buf), 0);
779
780 let mut single_claim = HashSet::new();
781 single_claim.insert(Claim {
782 source: GlobalPrincipalIdentifier::new_for_test(1),
783 subject: GlobalPrincipalIdentifier::new_for_test(1),
784 claim_type: ClaimType::Direct,
785 });
786 assert_eq!(compute_share_count(&single_claim, &mut subjects_buf), 1);
787
788 let mut two_same_subject = HashSet::new();
789 two_same_subject.insert(Claim {
790 source: GlobalPrincipalIdentifier::new_for_test(1),
791 subject: GlobalPrincipalIdentifier::new_for_test(10),
792 claim_type: ClaimType::Direct,
793 });
794 two_same_subject.insert(Claim {
795 source: GlobalPrincipalIdentifier::new_for_test(2),
796 subject: GlobalPrincipalIdentifier::new_for_test(10),
797 claim_type: ClaimType::Indirect,
798 });
799 assert_eq!(compute_share_count(&two_same_subject, &mut subjects_buf), 1);
800
801 let mut two_diff_subject = HashSet::new();
802 two_diff_subject.insert(Claim {
803 source: GlobalPrincipalIdentifier::new_for_test(1),
804 subject: GlobalPrincipalIdentifier::new_for_test(10),
805 claim_type: ClaimType::Direct,
806 });
807 two_diff_subject.insert(Claim {
808 source: GlobalPrincipalIdentifier::new_for_test(2),
809 subject: GlobalPrincipalIdentifier::new_for_test(20),
810 claim_type: ClaimType::Direct,
811 });
812 assert_eq!(compute_share_count(&two_diff_subject, &mut subjects_buf), 2);
813
814 let mut multi_claims = HashSet::new();
815 multi_claims.insert(Claim {
816 source: GlobalPrincipalIdentifier::new_for_test(1),
817 subject: GlobalPrincipalIdentifier::new_for_test(10),
818 claim_type: ClaimType::Direct,
819 });
820 multi_claims.insert(Claim {
821 source: GlobalPrincipalIdentifier::new_for_test(2),
822 subject: GlobalPrincipalIdentifier::new_for_test(10),
823 claim_type: ClaimType::Indirect,
824 });
825 multi_claims.insert(Claim {
826 source: GlobalPrincipalIdentifier::new_for_test(3),
827 subject: GlobalPrincipalIdentifier::new_for_test(20),
828 claim_type: ClaimType::Direct,
829 });
830 multi_claims.insert(Claim {
831 source: GlobalPrincipalIdentifier::new_for_test(4),
832 subject: GlobalPrincipalIdentifier::new_for_test(30),
833 claim_type: ClaimType::Direct,
834 });
835 assert_eq!(compute_share_count(&multi_claims, &mut subjects_buf), 3);
836 }
837
838 #[test]
845 fn test_memory_summary_share_count_multi_and_duplicate_claims() {
846 let mut principals = FxHashMap::default();
847 let mut p1 = make_test_principal(1, "principal1");
848 let mut p2 = make_test_principal(2, "principal2");
849 p1.resources.push(5001);
850 p2.resources.push(5001);
851 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
852 principals.insert(GlobalPrincipalIdentifier::new_for_test(2), p2);
853
854 let mut resources = FxHashMap::default();
855 resources
857 .insert(5001, make_test_vmo_resource(5001, 0, 600, 1200, vec![(1, 1), (2, 1), (2, 2)]));
858
859 let resource_names = vec![ZXName::from_string_lossy("multi_claim_vmo")];
860 let summary = MemorySummary::build(&principals, &resources, &resource_names);
861
862 assert_eq!(summary.principals.len(), 2);
863 for p_sum in &summary.principals {
864 assert_eq!(p_sum.committed_total, 600);
865 assert_eq!(p_sum.populated_total, 1200);
866 assert_eq!(p_sum.committed_scaled, 300.0);
867 assert_eq!(p_sum.populated_scaled, 600.0);
868 assert_eq!(p_sum.committed_private, 0);
869 assert_eq!(p_sum.populated_private, 0);
870 }
871 }
872
873 #[test]
880 fn test_memory_summary_skips_missing_resource_id() {
881 let mut principals = FxHashMap::default();
882 let mut p1 = make_test_principal(1, "principal_with_missing_res");
883 p1.resources.push(5001);
884 p1.resources.push(99999);
885 principals.insert(GlobalPrincipalIdentifier::new_for_test(1), p1);
886
887 let mut resources = FxHashMap::default();
888 resources.insert(5001, make_test_vmo_resource(5001, 0, 400, 800, vec![(1, 1)]));
889
890 let resource_names = vec![ZXName::from_string_lossy("valid_vmo")];
891 let summary = MemorySummary::build(&principals, &resources, &resource_names);
892
893 assert_eq!(summary.principals.len(), 1);
894 assert_eq!(summary.principals[0].committed_total, 400);
895 assert_eq!(summary.principals[0].populated_total, 800);
896 }
897}