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