1use crate::{ProcessedAttributionData, ZXName};
6use anyhow::Result;
7use bstr::ByteSlice;
8use fidl_fuchsia_kernel as fkernel;
9use fidl_fuchsia_memory_attribution_plugin as fplugin;
10use regex_lite::Regex;
11use serde::de::Error;
12use serde::{Deserialize, Deserializer, Serialize};
13use std::collections::HashMap;
14use std::collections::hash_map::Entry::Occupied;
15#[cfg(target_os = "fuchsia")]
16use {crate::CATEGORY_MEMORY_CAPTURE, fuchsia_trace::duration};
17
18const UNDIGESTED: &str = "Undigested";
19const ORPHANED: &str = "Orphaned";
20const KERNEL: &str = "Kernel";
21const FREE: &str = "Free";
22const PAGER_TOTAL: &str = "[Addl]PagerTotal";
23const PAGER_NEWEST: &str = "[Addl]PagerNewest";
24const PAGER_OLDEST: &str = "[Addl]PagerOldest";
25const DISCARDABLE_LOCKED: &str = "[Addl]DiscardableLocked";
26const DISCARDABLE_UNLOCKED: &str = "[Addl]DiscardableUnlocked";
27const ZRAM_COMPRESSED_BYTES: &str = "[Addl]ZramCompressedBytes";
28const POPULATED_ANONYMOUS_BYTES: &str = "[Addl]PopulatedAnonymousBytes";
29
30#[derive(Clone, Debug, Deserialize)]
38pub struct BucketDefinition {
39 pub name: String,
40 #[serde(deserialize_with = "deserialize_regex")]
41 pub process: Option<Regex>,
42 #[serde(deserialize_with = "deserialize_regex")]
43 pub vmo: Option<Regex>,
44 #[serde(default, deserialize_with = "deserialize_regex")]
45 pub principal: Option<Regex>,
46 pub event_code: u64,
47}
48
49impl BucketDefinition {
50 fn process_match(&self, process: &ZXName) -> bool {
52 self.process.as_ref().is_none_or(|process_regex| {
53 process
54 .as_bstr()
55 .to_str()
56 .is_ok_and(|process_name| process_regex.is_match(process_name))
57 })
58 }
59
60 fn vmo_match(&self, vmo: &ZXName) -> bool {
62 self.vmo.as_ref().is_none_or(|vmo_regex| {
63 vmo.as_bstr().to_str().is_ok_and(|vmo_name| vmo_regex.is_match(vmo_name))
64 })
65 }
66
67 fn principals_match(&self, principals: &Vec<&str>) -> bool {
69 self.principal.as_ref().is_none_or(|a| principals.iter().any(|name| a.is_match(name)))
70 }
71}
72
73fn deserialize_regex<'de, D>(d: D) -> Result<Option<Regex>, D::Error>
75where
76 D: Deserializer<'de>,
77{
78 Option::<String>::deserialize(d)
80 .and_then(|os| {
82 os
83 .map(|s| {
85 Regex::new(&s)
86 .map_err(D::Error::custom)
89 })
90 .transpose()
93 })
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
98pub struct Bucket {
99 pub name: String,
100 pub populated_size: u64,
101 pub committed_size: u64,
102 pub vmos: Option<Vec<NamedVmo>>,
103}
104
105#[derive(Debug, Default, PartialEq, Eq, Serialize)]
108pub struct Digest {
109 pub buckets: Vec<Bucket>,
110}
111
112struct UndigestedVmo<'a> {
114 populated_size: u64,
115 committed_size: u64,
116 name: &'a ZXName,
117 principals: &'a Vec<&'a str>,
118}
119
120#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
121pub struct NamedVmo {
123 pub name: ZXName,
124 pub populated_size: u64,
125 pub committed_size: u64,
126 pub principals: Vec<String>,
127}
128
129impl Digest {
130 pub fn compute(
133 attribution_data: &ProcessedAttributionData,
134 kmem_stats: &fkernel::MemoryStats,
135 kmem_stats_compression: &fkernel::MemoryStatsCompression,
136 bucket_definitions: &[BucketDefinition],
137 detailed_vmos: bool,
138 ) -> Result<Digest> {
139 #[cfg(target_os = "fuchsia")]
140 duration!(CATEGORY_MEMORY_CAPTURE, c"Digest::compute");
141
142 let owners: HashMap<u64, Vec<&str>> = {
146 let koid_to_principal = attribution_data
147 .principals
148 .iter()
149 .flat_map(|(_, p)| p.resources.iter().map(|r| (*r, p.name())));
150
151 let mut owners: HashMap<u64, Vec<_>> = HashMap::new();
152 for (koid, principal) in koid_to_principal {
153 let principals = owners.entry(koid).or_default();
154 principals.push(principal);
155 }
156 owners
157 };
158
159 let no_principals = vec![];
160 let mut populated_reclaimable_bytes = 0;
161 let mut undigested_vmos: HashMap<u64, UndigestedVmo<'_>> = attribution_data
162 .resources
163 .iter()
164 .filter_map(|(koid, r)| match &r.resource.resource_type {
165 fplugin::ResourceType::Vmo(vmo) => {
166 attribution_data.resource_names.get(r.resource.name_index).and_then(|name| {
167 let populated_size = vmo.scaled_populated_bytes?;
168 let committed_size = vmo.scaled_committed_bytes?;
169 if vmo.flags.map_or(false, |flags| {
170 flags
171 & (zx_types::ZX_INFO_VMO_PAGER_BACKED
172 | zx_types::ZX_INFO_VMO_DISCARDABLE)
173 != 0
174 }) {
175 populated_reclaimable_bytes += populated_size;
176 }
177 Some((
178 *koid,
179 UndigestedVmo {
180 name,
181 populated_size,
182 committed_size,
183 principals: owners.get(koid).unwrap_or(&no_principals),
184 },
185 ))
186 })
187 }
188 _ => None,
189 })
190 .collect();
191 let processes: Vec<(&ZXName, &fplugin::Process)> = attribution_data
192 .resources
193 .values()
194 .filter_map(|r| match &r.resource.resource_type {
195 fplugin::ResourceType::Process(process) => attribution_data
196 .resource_names
197 .get(r.resource.name_index)
198 .map(|name| (name, process)),
199 _ => None,
200 })
201 .collect();
202
203 let mut buckets: Vec<Bucket> = bucket_definitions
204 .iter()
205 .map(|bd| {
206 let mut bucket = Bucket {
207 name: bd.name.to_owned(),
208 populated_size: 0,
209 committed_size: 0,
210 vmos: None,
211 };
212 processes.iter().for_each(|(process_name, process)| {
213 if bd.process_match(process_name) {
214 for koid in process.vmos.iter().flatten() {
215 let (populated_size, committed_size) = match undigested_vmos
216 .entry(*koid)
217 {
218 Occupied(e) => {
219 let UndigestedVmo { name, principals, .. } = e.get();
220 if bd.vmo_match(&name) && bd.principals_match(principals) {
221 let (_, vmo) = e.remove_entry();
222 if detailed_vmos {
223 bucket.vmos.get_or_insert_default().push(NamedVmo {
224 name: vmo.name.clone(),
225 populated_size: vmo.populated_size,
226 committed_size: vmo.committed_size,
227 principals: vmo
228 .principals
229 .into_iter()
230 .map(|&name| name.to_owned())
231 .collect(),
232 });
233 }
234 (vmo.populated_size, vmo.committed_size)
235 } else {
236 (0, 0)
237 }
238 }
239 _ => (0, 0),
240 };
241 bucket.committed_size += committed_size;
242 bucket.populated_size += populated_size;
243 }
244 };
245 });
246 bucket
247 })
248 .collect();
249
250 let undigested = {
253 let (populated_size, committed_size) = undigested_vmos
254 .values()
255 .map(|UndigestedVmo { populated_size, committed_size, .. }| {
256 (*populated_size, *committed_size)
257 })
258 .fold((0, 0), |(total_populated, total_committed), (populated, committed)| {
259 (total_populated + populated, total_committed + committed)
260 });
261
262 Bucket {
263 name: UNDIGESTED.to_string(),
264 populated_size: populated_size,
265 committed_size,
266 vmos: if detailed_vmos {
267 Some(
268 undigested_vmos
269 .values()
270 .map(|vmo| NamedVmo {
271 name: vmo.name.clone(),
272 populated_size: vmo.populated_size,
273 committed_size: vmo.committed_size,
274 principals: vmo
275 .principals
276 .into_iter()
277 .map(|&name| name.to_owned())
278 .collect(),
279 })
280 .collect(),
281 )
282 } else {
283 None
284 },
285 }
286 };
287
288 let total_vmo_size: u64 = undigested.committed_size
289 + buckets.iter().map(|Bucket { committed_size, .. }| committed_size).sum::<u64>();
290
291 buckets.extend([
294 undigested,
295 {
298 let size = kmem_stats.vmo_bytes.unwrap_or(0).saturating_sub(total_vmo_size);
299 Bucket {
300 name: ORPHANED.to_string(),
301 populated_size: size,
302 committed_size: size,
303 vmos: None,
304 }
305 },
306 {
308 let size = (|| {
309 Some(
310 kmem_stats.wired_bytes?
311 + kmem_stats.total_heap_bytes?
312 + kmem_stats.mmu_overhead_bytes?
313 + kmem_stats.ipc_bytes?
314 + kmem_stats.other_bytes?
315 + kmem_stats.slab_bytes?
316 + kmem_stats.cache_bytes?,
317 )
318 })()
319 .unwrap_or(0);
320 Bucket {
321 name: KERNEL.to_string(),
322 populated_size: size,
323 committed_size: size,
324 vmos: None,
325 }
326 },
327 {
329 let size = kmem_stats.free_bytes.unwrap_or(0);
330 Bucket {
331 name: FREE.to_string(),
332 populated_size: size,
333 committed_size: size,
334 vmos: None,
335 }
336 },
337 {
339 let size = kmem_stats.vmo_reclaim_total_bytes.unwrap_or(0);
340 Bucket {
341 name: PAGER_TOTAL.to_string(),
342 populated_size: size,
343 committed_size: size,
344 vmos: None,
345 }
346 },
347 {
348 let size = kmem_stats.vmo_reclaim_newest_bytes.unwrap_or(0);
349 Bucket {
350 name: PAGER_NEWEST.to_string(),
351 populated_size: size,
352 committed_size: size,
353 vmos: None,
354 }
355 },
356 {
357 let size = kmem_stats.vmo_reclaim_oldest_bytes.unwrap_or(0);
358 Bucket {
359 name: PAGER_OLDEST.to_string(),
360 populated_size: size,
361 committed_size: size,
362 vmos: None,
363 }
364 },
365 {
367 let size = kmem_stats.vmo_discardable_locked_bytes.unwrap_or(0);
368 Bucket {
369 name: DISCARDABLE_LOCKED.to_string(),
370 populated_size: size,
371 committed_size: size,
372 vmos: None,
373 }
374 },
375 {
376 let size = kmem_stats.vmo_discardable_unlocked_bytes.unwrap_or(0);
377 Bucket {
378 name: DISCARDABLE_UNLOCKED.to_string(),
379 populated_size: size,
380 committed_size: size,
381 vmos: None,
382 }
383 },
384 {
386 let size = kmem_stats_compression.compressed_storage_bytes.unwrap_or(0);
387 Bucket {
388 name: ZRAM_COMPRESSED_BYTES.to_string(),
389 populated_size: size,
390 committed_size: size,
391 vmos: None,
392 }
393 },
394 {
396 let size = (kmem_stats.total_bytes.unwrap_or(0)
397 + kmem_stats_compression.uncompressed_storage_bytes.unwrap_or(0))
398 .saturating_sub(kmem_stats.free_bytes.unwrap_or(0))
399 .saturating_sub(kmem_stats.zram_bytes.unwrap_or(0))
400 .saturating_sub(populated_reclaimable_bytes);
401
402 Bucket {
403 name: POPULATED_ANONYMOUS_BYTES.to_string(),
404 populated_size: size,
405 committed_size: size,
406 vmos: None,
407 }
408 },
409 ]);
410 Ok(Digest { buckets })
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::{
418 Attribution, AttributionData, GlobalPrincipalIdentifier, Principal, PrincipalDescription,
419 PrincipalType, ProcessedAttributionData, Resource, ResourceReference, attribute_vmos,
420 };
421 use fidl_fuchsia_memory_attribution_plugin as fplugin;
422 use regex_lite::Regex;
423
424 fn get_attribution_data() -> ProcessedAttributionData {
425 attribute_vmos(AttributionData {
426 principals_vec: vec![
427 Principal {
428 identifier: GlobalPrincipalIdentifier::new_for_test(1),
429 description: Some(PrincipalDescription::Component("principal".to_owned())),
430 principal_type: PrincipalType::Runnable,
431 parent: Some(GlobalPrincipalIdentifier::new_for_test(2)),
432 },
433 Principal {
434 identifier: GlobalPrincipalIdentifier::new_for_test(2),
435 description: Some(PrincipalDescription::Component("parent".to_owned())),
436 principal_type: PrincipalType::Runnable,
437 parent: None,
438 },
439 ],
440 resources_vec: vec![
441 Resource {
442 koid: 10,
443 name_index: 0,
444 resource_type: fplugin::ResourceType::Vmo(fplugin::Vmo {
445 parent: None,
446 private_committed_bytes: Some(1024),
447 private_populated_bytes: Some(2048),
448 scaled_committed_bytes: Some(512),
449 scaled_populated_bytes: Some(2048),
450 total_committed_bytes: Some(1024),
451 total_populated_bytes: Some(2048),
452 ..Default::default()
453 }),
454 },
455 Resource {
456 koid: 20,
457 name_index: 1,
458 resource_type: fplugin::ResourceType::Vmo(fplugin::Vmo {
459 parent: None,
460 private_committed_bytes: Some(1024),
461 private_populated_bytes: Some(2048),
462 scaled_committed_bytes: Some(512),
463 scaled_populated_bytes: Some(2048),
464 total_committed_bytes: Some(1024),
465 total_populated_bytes: Some(2048),
466 ..Default::default()
467 }),
468 },
469 Resource {
470 koid: 30,
471 name_index: 1,
472 resource_type: fplugin::ResourceType::Process(fplugin::Process {
473 vmos: Some(vec![10, 20]),
474 ..Default::default()
475 }),
476 },
477 ],
478 resource_names: vec![
479 ZXName::try_from_bytes(b"resource").unwrap(),
480 ZXName::try_from_bytes(b"matched").unwrap(),
481 ],
482 attributions: vec![Attribution {
483 source: GlobalPrincipalIdentifier::new_for_test(1),
484 subject: GlobalPrincipalIdentifier::new_for_test(1),
485 resources: vec![ResourceReference::KernelObject(20)],
486 }],
487 })
488 }
489
490 fn get_kernel_stats() -> (fkernel::MemoryStats, fkernel::MemoryStatsCompression) {
491 (
492 fkernel::MemoryStats {
493 total_bytes: Some(20),
494 free_bytes: Some(2),
495 wired_bytes: Some(3),
496 total_heap_bytes: Some(4),
497 free_heap_bytes: Some(5),
498 vmo_bytes: Some(10000),
499 mmu_overhead_bytes: Some(7),
500 ipc_bytes: Some(8),
501 other_bytes: Some(9),
502 free_loaned_bytes: Some(10),
503 cache_bytes: Some(11),
504 slab_bytes: Some(12),
505 zram_bytes: Some(13),
506 vmo_reclaim_total_bytes: Some(14),
507 vmo_reclaim_newest_bytes: Some(15),
508 vmo_reclaim_oldest_bytes: Some(16),
509 vmo_reclaim_disabled_bytes: Some(17),
510 vmo_discardable_locked_bytes: Some(18),
511 vmo_discardable_unlocked_bytes: Some(19),
512 ..Default::default()
513 },
514 fkernel::MemoryStatsCompression {
515 uncompressed_storage_bytes: Some(1),
516 compressed_storage_bytes: Some(21),
517 compressed_fragmentation_bytes: Some(22),
518 compression_time: Some(23),
519 decompression_time: Some(24),
520 total_page_compression_attempts: Some(25),
521 failed_page_compression_attempts: Some(26),
522 total_page_decompressions: Some(27),
523 compressed_page_evictions: Some(28),
524 eager_page_compressions: Some(29),
525 memory_pressure_page_compressions: Some(30),
526 critical_memory_page_compressions: Some(31),
527 pages_decompressed_unit_ns: Some(32),
528 pages_decompressed_within_log_time: Some([40, 41, 42, 43, 44, 45, 46, 47]),
529 ..Default::default()
530 },
531 )
532 }
533
534 fn sort_buckets_for_assert(digest: &mut Digest) {
535 for bucket in digest.buckets.iter_mut() {
536 for vmos in bucket.vmos.iter_mut() {
537 vmos.sort_by(|vmo1, vmo2| vmo1.name.cmp(&vmo2.name));
538 }
539 }
540 }
541
542 #[test]
543 fn test_digest_no_definitions() {
544 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
545 let digest = {
546 let mut digest = Digest::compute(
547 &get_attribution_data(),
548 &kernel_stats,
549 &kernel_stats_compression,
550 &vec![],
551 true,
552 )
553 .unwrap();
554 sort_buckets_for_assert(&mut digest);
555 digest
556 };
557 let expected_buckets = vec![
558 Bucket {
560 name: UNDIGESTED.to_string(),
561 populated_size: 4096,
562 committed_size: 1024,
563 vmos: Some(vec![
564 NamedVmo {
565 name: ZXName::from_string_lossy("matched"),
566 populated_size: 2048,
567 committed_size: 512,
568 principals: vec!["principal".to_string()],
569 },
570 NamedVmo {
571 name: ZXName::from_string_lossy("resource"),
572 populated_size: 2048,
573 committed_size: 512,
574 principals: vec![],
575 },
576 ]),
577 },
578 Bucket {
580 name: ORPHANED.to_string(),
581 populated_size: 8976,
582 committed_size: 8976,
583 vmos: None,
584 },
585 Bucket { name: KERNEL.to_string(), populated_size: 54, committed_size: 54, vmos: None },
587 Bucket { name: FREE.to_string(), populated_size: 2, committed_size: 2, vmos: None },
588 Bucket {
589 name: PAGER_TOTAL.to_string(),
590 populated_size: 14,
591 committed_size: 14,
592 vmos: None,
593 },
594 Bucket {
595 name: PAGER_NEWEST.to_string(),
596 populated_size: 15,
597 committed_size: 15,
598 vmos: None,
599 },
600 Bucket {
601 name: PAGER_OLDEST.to_string(),
602 populated_size: 16,
603 committed_size: 16,
604 vmos: None,
605 },
606 Bucket {
607 name: DISCARDABLE_LOCKED.to_string(),
608 populated_size: 18,
609 committed_size: 18,
610 vmos: None,
611 },
612 Bucket {
613 name: DISCARDABLE_UNLOCKED.to_string(),
614 populated_size: 19,
615 committed_size: 19,
616 vmos: None,
617 },
618 Bucket {
619 name: ZRAM_COMPRESSED_BYTES.to_string(),
620 populated_size: 21,
621 committed_size: 21,
622 vmos: None,
623 },
624 Bucket {
625 name: POPULATED_ANONYMOUS_BYTES.to_string(),
626 populated_size: 6,
627 committed_size: 6,
628 vmos: None,
629 },
630 ];
631
632 assert_eq!(digest.buckets, expected_buckets);
633 }
634
635 #[test]
636 fn test_digest_with_matching_vmo() -> Result<(), anyhow::Error> {
637 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
638 let digest = {
639 let mut digest = Digest::compute(
640 &get_attribution_data(),
641 &kernel_stats,
642 &kernel_stats_compression,
643 &vec![BucketDefinition {
644 name: "matched".to_string(),
645 process: None,
646 vmo: Some(Regex::new("matched")?),
647 principal: None,
648 event_code: Default::default(),
649 }],
650 true,
651 )
652 .unwrap();
653 sort_buckets_for_assert(&mut digest);
654 digest
655 };
656 let expected_buckets = vec![
657 Bucket {
659 name: "matched".to_string(),
660 populated_size: 2048,
661 committed_size: 512,
662 vmos: Some(vec![NamedVmo {
663 name: ZXName::from_string_lossy("matched"),
664 populated_size: 2048,
665 committed_size: 512,
666 principals: vec!["principal".to_owned()],
667 }]),
668 },
669 Bucket {
671 name: UNDIGESTED.to_string(),
672 populated_size: 2048,
673 committed_size: 512,
674 vmos: Some(vec![NamedVmo {
675 name: ZXName::from_string_lossy("resource"),
676 populated_size: 2048,
677 committed_size: 512,
678 principals: vec![],
679 }]),
680 },
681 Bucket {
683 name: ORPHANED.to_string(),
684 populated_size: 8976,
685 committed_size: 8976,
686 vmos: None,
687 },
688 Bucket { name: KERNEL.to_string(), populated_size: 54, committed_size: 54, vmos: None },
690 Bucket { name: FREE.to_string(), populated_size: 2, committed_size: 2, vmos: None },
691 Bucket {
692 name: PAGER_TOTAL.to_string(),
693 populated_size: 14,
694 committed_size: 14,
695 vmos: None,
696 },
697 Bucket {
698 name: PAGER_NEWEST.to_string(),
699 populated_size: 15,
700 committed_size: 15,
701 vmos: None,
702 },
703 Bucket {
704 name: PAGER_OLDEST.to_string(),
705 populated_size: 16,
706 committed_size: 16,
707 vmos: None,
708 },
709 Bucket {
710 name: DISCARDABLE_LOCKED.to_string(),
711 populated_size: 18,
712 committed_size: 18,
713 vmos: None,
714 },
715 Bucket {
716 name: DISCARDABLE_UNLOCKED.to_string(),
717 populated_size: 19,
718 committed_size: 19,
719 vmos: None,
720 },
721 Bucket {
722 name: ZRAM_COMPRESSED_BYTES.to_string(),
723 populated_size: 21,
724 committed_size: 21,
725 vmos: None,
726 },
727 Bucket {
728 name: POPULATED_ANONYMOUS_BYTES.to_string(),
729 populated_size: 6,
730 committed_size: 6,
731 vmos: None,
732 },
733 ];
734
735 assert_eq!(digest.buckets, expected_buckets);
736 Ok(())
737 }
738
739 #[test]
740 fn test_digest_with_matching_process() -> Result<(), anyhow::Error> {
741 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
742 let digest = {
743 let mut digest = Digest::compute(
744 &get_attribution_data(),
745 &kernel_stats,
746 &kernel_stats_compression,
747 &vec![BucketDefinition {
748 name: "matched".to_string(),
749 process: Some(Regex::new("matched")?),
750 vmo: None,
751 principal: None,
752 event_code: Default::default(),
753 }],
754 true,
755 )
756 .unwrap();
757 sort_buckets_for_assert(&mut digest);
758 digest
759 };
760 let expected_buckets = vec![
761 Bucket {
763 name: "matched".to_string(),
764 populated_size: 4096,
765 committed_size: 1024,
766 vmos: Some(vec![
767 NamedVmo {
768 name: ZXName::from_string_lossy("matched"),
769 populated_size: 2048,
770 committed_size: 512,
771 principals: vec!["principal".to_owned()],
772 },
773 NamedVmo {
774 name: ZXName::from_string_lossy("resource"),
775 populated_size: 2048,
776 committed_size: 512,
777 principals: vec![],
778 },
779 ]),
780 },
781 Bucket {
783 name: UNDIGESTED.to_string(),
784 populated_size: 0,
785 committed_size: 0,
786 vmos: Some(vec![]),
787 },
788 Bucket {
790 name: ORPHANED.to_string(),
791 populated_size: 8976,
792 committed_size: 8976,
793 vmos: None,
794 },
795 Bucket { name: KERNEL.to_string(), populated_size: 54, committed_size: 54, vmos: None },
797 Bucket { name: FREE.to_string(), populated_size: 2, committed_size: 2, vmos: None },
798 Bucket {
799 name: PAGER_TOTAL.to_string(),
800 populated_size: 14,
801 committed_size: 14,
802 vmos: None,
803 },
804 Bucket {
805 name: PAGER_NEWEST.to_string(),
806 populated_size: 15,
807 committed_size: 15,
808 vmos: None,
809 },
810 Bucket {
811 name: PAGER_OLDEST.to_string(),
812 populated_size: 16,
813 committed_size: 16,
814 vmos: None,
815 },
816 Bucket {
817 name: DISCARDABLE_LOCKED.to_string(),
818 populated_size: 18,
819 committed_size: 18,
820 vmos: None,
821 },
822 Bucket {
823 name: DISCARDABLE_UNLOCKED.to_string(),
824 populated_size: 19,
825 committed_size: 19,
826 vmos: None,
827 },
828 Bucket {
829 name: ZRAM_COMPRESSED_BYTES.to_string(),
830 populated_size: 21,
831 committed_size: 21,
832 vmos: None,
833 },
834 Bucket {
835 name: POPULATED_ANONYMOUS_BYTES.to_string(),
836 populated_size: 6,
837 committed_size: 6,
838 vmos: None,
839 },
840 ];
841
842 assert_eq!(digest.buckets, expected_buckets);
843 Ok(())
844 }
845
846 #[test]
847 fn test_digest_with_matching_principal() -> Result<(), anyhow::Error> {
848 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
849 let digest = {
850 let mut digest = Digest::compute(
851 &get_attribution_data(),
852 &kernel_stats,
853 &kernel_stats_compression,
854 &vec![BucketDefinition {
855 name: "matched".to_string(),
856 process: None,
857 vmo: None,
858 principal: Some(Regex::new("principal")?),
859 event_code: Default::default(),
860 }],
861 true,
862 )
863 .unwrap();
864 sort_buckets_for_assert(&mut digest);
865 digest
866 };
867 let expected_buckets = vec![
868 Bucket {
870 name: "matched".to_string(),
871 populated_size: 2048,
872 committed_size: 512,
873 vmos: Some(vec![NamedVmo {
874 name: ZXName::from_string_lossy("matched"),
875 populated_size: 2048,
876 committed_size: 512,
877 principals: vec!["principal".to_owned()],
878 }]),
879 },
880 Bucket {
882 name: UNDIGESTED.to_string(),
883 populated_size: 2048,
884 committed_size: 512,
885 vmos: Some(vec![NamedVmo {
886 name: ZXName::from_string_lossy("resource"),
887 populated_size: 2048,
888 committed_size: 512,
889 principals: vec![],
890 }]),
891 },
892 Bucket {
894 name: ORPHANED.to_string(),
895 populated_size: 8976,
896 committed_size: 8976,
897 vmos: None,
898 },
899 Bucket { name: KERNEL.to_string(), populated_size: 54, committed_size: 54, vmos: None },
901 Bucket { name: FREE.to_string(), populated_size: 2, committed_size: 2, vmos: None },
902 Bucket {
903 name: PAGER_TOTAL.to_string(),
904 populated_size: 14,
905 committed_size: 14,
906 vmos: None,
907 },
908 Bucket {
909 name: PAGER_NEWEST.to_string(),
910 populated_size: 15,
911 committed_size: 15,
912 vmos: None,
913 },
914 Bucket {
915 name: PAGER_OLDEST.to_string(),
916 populated_size: 16,
917 committed_size: 16,
918 vmos: None,
919 },
920 Bucket {
921 name: DISCARDABLE_LOCKED.to_string(),
922 populated_size: 18,
923 committed_size: 18,
924 vmos: None,
925 },
926 Bucket {
927 name: DISCARDABLE_UNLOCKED.to_string(),
928 populated_size: 19,
929 committed_size: 19,
930 vmos: None,
931 },
932 Bucket {
933 name: ZRAM_COMPRESSED_BYTES.to_string(),
934 populated_size: 21,
935 committed_size: 21,
936 vmos: None,
937 },
938 Bucket {
939 name: POPULATED_ANONYMOUS_BYTES.to_string(),
940 populated_size: 6,
941 committed_size: 6,
942 vmos: None,
943 },
944 ];
945
946 assert_eq!(digest.buckets, expected_buckets);
947 Ok(())
948 }
949
950 #[test]
951 fn test_digest_with_matching_principal_process_and_vmo() -> Result<(), anyhow::Error> {
952 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
953 let digest = {
954 let mut digest = Digest::compute(
955 &get_attribution_data(),
956 &kernel_stats,
957 &kernel_stats_compression,
958 &vec![BucketDefinition {
959 name: "matched".to_string(),
960 process: Some(Regex::new("matched")?),
961 vmo: Some(Regex::new("matched")?),
962 principal: Some(Regex::new("principal")?),
963 event_code: Default::default(),
964 }],
965 true,
966 )
967 .unwrap();
968 sort_buckets_for_assert(&mut digest);
969 digest
970 };
971 let expected_buckets = vec![
972 Bucket {
974 name: "matched".to_string(),
975 populated_size: 2048,
976 committed_size: 512,
977 vmos: Some(vec![NamedVmo {
978 name: ZXName::from_string_lossy("matched"),
979 populated_size: 2048,
980 committed_size: 512,
981 principals: vec!["principal".to_owned()],
982 }]),
983 },
984 Bucket {
986 name: UNDIGESTED.to_string(),
987 populated_size: 2048,
988 committed_size: 512,
989 vmos: Some(vec![NamedVmo {
990 name: ZXName::from_string_lossy("resource"),
991 populated_size: 2048,
992 committed_size: 512,
993 principals: vec![],
994 }]),
995 },
996 Bucket {
998 name: ORPHANED.to_string(),
999 populated_size: 8976,
1000 committed_size: 8976,
1001 vmos: None,
1002 },
1003 Bucket { name: KERNEL.to_string(), populated_size: 54, committed_size: 54, vmos: None },
1005 Bucket { name: FREE.to_string(), populated_size: 2, committed_size: 2, vmos: None },
1006 Bucket {
1007 name: PAGER_TOTAL.to_string(),
1008 populated_size: 14,
1009 committed_size: 14,
1010 vmos: None,
1011 },
1012 Bucket {
1013 name: PAGER_NEWEST.to_string(),
1014 populated_size: 15,
1015 committed_size: 15,
1016 vmos: None,
1017 },
1018 Bucket {
1019 name: PAGER_OLDEST.to_string(),
1020 populated_size: 16,
1021 committed_size: 16,
1022 vmos: None,
1023 },
1024 Bucket {
1025 name: DISCARDABLE_LOCKED.to_string(),
1026 populated_size: 18,
1027 committed_size: 18,
1028 vmos: None,
1029 },
1030 Bucket {
1031 name: DISCARDABLE_UNLOCKED.to_string(),
1032 populated_size: 19,
1033 committed_size: 19,
1034 vmos: None,
1035 },
1036 Bucket {
1037 name: ZRAM_COMPRESSED_BYTES.to_string(),
1038 populated_size: 21,
1039 committed_size: 21,
1040 vmos: None,
1041 },
1042 Bucket {
1043 name: POPULATED_ANONYMOUS_BYTES.to_string(),
1044 populated_size: 6,
1045 committed_size: 6,
1046 vmos: None,
1047 },
1048 ];
1049
1050 assert_eq!(digest.buckets, expected_buckets);
1051 Ok(())
1052 }
1053}