attribution_processing/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::cell::RefCell;
6use core::convert::Into;
7use fidl_fuchsia_memory_attribution_plugin as fplugin;
8use futures::future::BoxFuture;
9use std::collections::{HashMap, HashSet};
10use summary::MemorySummary;
11
12pub mod digest;
13pub mod kernel_statistics;
14mod name;
15pub mod summary;
16pub use name::ZXName;
17
18#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
19pub struct PrincipalIdentifier(pub u64);
20
21impl From<fplugin::PrincipalIdentifier> for PrincipalIdentifier {
22    fn from(value: fplugin::PrincipalIdentifier) -> Self {
23        PrincipalIdentifier(value.id)
24    }
25}
26
27impl Into<fplugin::PrincipalIdentifier> for PrincipalIdentifier {
28    fn into(self) -> fplugin::PrincipalIdentifier {
29        fplugin::PrincipalIdentifier { id: self.0 }
30    }
31}
32
33/// User-understandable description of a Principal
34#[derive(PartialEq, Eq, Clone, Debug, Hash)]
35pub enum PrincipalDescription {
36    Component(String),
37    Part(String),
38}
39
40impl From<fplugin::Description> for PrincipalDescription {
41    fn from(value: fplugin::Description) -> Self {
42        match value {
43            fplugin::Description::Component(s) => PrincipalDescription::Component(s),
44            fplugin::Description::Part(s) => PrincipalDescription::Part(s),
45            _ => unreachable!(),
46        }
47    }
48}
49
50impl Into<fplugin::Description> for PrincipalDescription {
51    fn into(self) -> fplugin::Description {
52        match self {
53            PrincipalDescription::Component(s) => fplugin::Description::Component(s),
54            PrincipalDescription::Part(s) => fplugin::Description::Part(s),
55        }
56    }
57}
58
59/// Type of a principal.
60#[derive(PartialEq, Eq, Clone, Debug, Hash)]
61pub enum PrincipalType {
62    Runnable,
63    Part,
64}
65
66impl From<fplugin::PrincipalType> for PrincipalType {
67    fn from(value: fplugin::PrincipalType) -> Self {
68        match value {
69            fplugin::PrincipalType::Runnable => PrincipalType::Runnable,
70            fplugin::PrincipalType::Part => PrincipalType::Part,
71            _ => unreachable!(),
72        }
73    }
74}
75
76impl Into<fplugin::PrincipalType> for PrincipalType {
77    fn into(self) -> fplugin::PrincipalType {
78        match self {
79            PrincipalType::Runnable => fplugin::PrincipalType::Runnable,
80            PrincipalType::Part => fplugin::PrincipalType::Part,
81        }
82    }
83}
84
85#[derive(Clone, Debug, Eq, Hash, PartialEq)]
86/// A Principal, that can use and claim memory.
87pub struct Principal {
88    // These fields are initialized from [fplugin::Principal].
89    pub identifier: PrincipalIdentifier,
90    pub description: PrincipalDescription,
91    pub principal_type: PrincipalType,
92
93    /// Principal that declared this Principal. None if this Principal is at the root of the system
94    /// hierarchy (the root principal is a statically defined Principal encompassing all resources
95    /// on the system). The Principal hierarchy forms a tree (no cycles).
96    pub parent: Option<PrincipalIdentifier>,
97}
98
99/// Creates a new [Principal] from a [fplugin::Principal] object. The [Principal] object will
100/// contain all the data from [fplugin::Principal] and have its other fields initialized empty.
101impl From<fplugin::Principal> for Principal {
102    fn from(value: fplugin::Principal) -> Self {
103        Principal {
104            identifier: value.identifier.unwrap().into(),
105            description: value.description.unwrap().into(),
106            principal_type: value.principal_type.unwrap().into(),
107            parent: value.parent.map(Into::into),
108        }
109    }
110}
111
112impl Into<fplugin::Principal> for Principal {
113    fn into(self) -> fplugin::Principal {
114        fplugin::Principal {
115            identifier: Some(self.identifier.into()),
116            description: Some(self.description.into()),
117            principal_type: Some(self.principal_type.into()),
118            parent: self.parent.map(Into::into),
119            ..Default::default()
120        }
121    }
122}
123
124/// A Principal, with its attribution claims and resources.
125pub struct InflatedPrincipal {
126    /// The principal definition.
127    principal: Principal,
128
129    // These fields are computed from the rest of the [fplugin::Snapshot] data.
130    /// Map of attribution claims made about this Principal (this Principal is the subject of the
131    /// claim). This map goes from the source Principal to the attribution claim.
132    attribution_claims: HashMap<PrincipalIdentifier, Attribution>,
133    /// KOIDs of resources attributed to this principal, after resolution of sharing and
134    /// reattributions.
135    resources: HashSet<u64>,
136}
137
138impl InflatedPrincipal {
139    fn new(principal: Principal) -> InflatedPrincipal {
140        InflatedPrincipal {
141            principal,
142            attribution_claims: Default::default(),
143            resources: Default::default(),
144        }
145    }
146}
147
148impl InflatedPrincipal {
149    fn name(&self) -> &str {
150        match &self.principal.description {
151            PrincipalDescription::Component(component_name) => component_name,
152            PrincipalDescription::Part(part_name) => part_name,
153        }
154    }
155}
156
157/// Type of the claim, that changes depending on how the claim was created.
158#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
159pub enum ClaimType {
160    /// A principal claimed this resource directly.
161    Direct,
162    /// A principal claimed a resource that contains this resource (e.g. a process containing a
163    /// VMO).
164    Indirect,
165    /// A principal claimed a child of this resource (e.g. a copy-on-write VMO child of this VMO).
166    Child,
167}
168
169#[derive(Clone, Copy, PartialEq, Eq, Hash)]
170pub struct Koid(u64);
171
172impl From<u64> for Koid {
173    fn from(value: u64) -> Self {
174        Koid(value)
175    }
176}
177
178/// Attribution claim of a Principal on a Resource.
179///
180/// Note that this object is slightly different from the [fplugin::Attribution] object: it goes from
181/// a Resource to a Principal, and covers also indirect attribution claims of sub- and parent
182/// resources.
183#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
184pub struct Claim {
185    /// Principal to which the resources are attributed.
186    subject: PrincipalIdentifier,
187    /// Principal making the attribution claim.
188    source: PrincipalIdentifier,
189    claim_type: ClaimType,
190}
191
192#[derive(Clone, Debug, PartialEq)]
193pub struct Resource {
194    pub koid: u64,
195    pub name_index: usize,
196    pub resource_type: fplugin::ResourceType,
197}
198
199impl From<fplugin::Resource> for Resource {
200    fn from(value: fplugin::Resource) -> Self {
201        Resource {
202            koid: value.koid.unwrap(),
203            name_index: value.name_index.unwrap() as usize,
204            resource_type: value.resource_type.unwrap(),
205        }
206    }
207}
208
209impl Into<fplugin::Resource> for Resource {
210    fn into(self) -> fplugin::Resource {
211        fplugin::Resource {
212            koid: Some(self.koid),
213            name_index: Some(self.name_index as u64),
214            resource_type: Some(self.resource_type),
215            ..Default::default()
216        }
217    }
218}
219
220// Claim with a boolean tag, to help find leaves in the claim assignment graph.
221pub struct TaggedClaim(Claim, bool);
222
223#[derive(Debug)]
224pub struct InflatedResource {
225    resource: Resource,
226    claims: HashSet<Claim>,
227}
228
229impl InflatedResource {
230    fn new(resource: Resource) -> InflatedResource {
231        InflatedResource { resource, claims: Default::default() }
232    }
233
234    fn children(&self) -> Vec<u64> {
235        match &self.resource.resource_type {
236            fplugin::ResourceType::Job(job) => {
237                let mut r: Vec<u64> = job.child_jobs.iter().flatten().map(|k| *k).collect();
238                r.extend(job.processes.iter().flatten().map(|k| *k));
239                if r.len() == 0 {
240                    eprintln!("{} has no processes", self.resource.koid);
241                }
242                r
243            }
244            fplugin::ResourceType::Process(process) => {
245                process.vmos.iter().flatten().map(|k| *k).collect()
246            }
247            fplugin::ResourceType::Vmo(_) => Vec::new(),
248            _ => todo!(),
249        }
250    }
251
252    /// Process the claims made on this resource to disambiguate between reassignment and sharing.
253    ///
254    /// [process_claims] looks at each claim made on this resource, and removes claims that are
255    /// reassigned by another claim. This happens if a principal A gives a resource to principal B,
256    /// and B then gives it to principal C. However, if two independent principals claim this
257    /// resource, then both their claims are kept.
258    /// This is done by:
259    /// (i)  preserving all self claims, and
260    /// (ii) preserving only leaves in the DAG following claim.source to claim.subject edges.
261    fn process_claims(&mut self) {
262        let mut claims_by_source: HashMap<PrincipalIdentifier, RefCell<Vec<TaggedClaim>>> =
263            Default::default();
264        let mut self_claims = Vec::new();
265
266        for claim in self.claims.iter().cloned() {
267            if claim.source == claim.subject {
268                // Self claims are taken out of the graph because they are never transferred. This
269                // is to implement sharing.
270                self_claims.push(claim);
271            } else {
272                claims_by_source
273                    .entry(claim.source)
274                    .or_default()
275                    .borrow_mut()
276                    .push(TaggedClaim(claim, false));
277            }
278        }
279
280        self.claims = self_claims.into_iter().collect();
281        for (_, claimlist_refcell) in claims_by_source.iter() {
282            let mut claimlist = claimlist_refcell.borrow_mut();
283            for tagged_claim in claimlist.iter_mut() {
284                self.claims.extend(
285                    InflatedResource::process_claims_recursive(tagged_claim, &claims_by_source)
286                        .into_iter(),
287                );
288            }
289        }
290    }
291
292    /// Recursively look at claims to find the ones that are not reassigned.
293    fn process_claims_recursive(
294        tagged_claim: &mut TaggedClaim,
295        claims: &HashMap<PrincipalIdentifier, RefCell<Vec<TaggedClaim>>>,
296    ) -> Vec<Claim> {
297        let claim = match tagged_claim.1 {
298            true => {
299                // We have visited this claim already, we can skip.
300                return vec![];
301            }
302            false => {
303                // We tag visited claims, so we don't visit them again.
304                tagged_claim.1 = true;
305                tagged_claim.0
306            }
307        };
308        let subject = &claim.subject;
309        // We find if this claim has been reassigned.
310        let mut subject_claims = match claims.get(subject) {
311            Some(value_ref) => {
312                // [subject_claims] mutable borrow is held when recursing below, and
313                // [RefCell::try_borrow_mut] returns an error if called when a mutable borrow is
314                // already held. This ensures an error will be thrown at runtime if there is a
315                // cycle.
316                value_ref.try_borrow_mut().expect("Claims form a cycle, this is not supported")
317            }
318            None => {
319                // The claim is not reassigned, we keep the claim.
320                return vec![claim];
321            }
322        };
323        let mut leaves = vec![];
324        for subject_claim in subject_claims.iter_mut() {
325            leaves.append(&mut InflatedResource::process_claims_recursive(subject_claim, claims));
326        }
327        leaves
328    }
329}
330
331#[derive(Clone)]
332/// Holds the list of resources attributed to a Principal (subject) by another Principal (source).
333pub struct Attribution {
334    /// Principal making the attribution claim.
335    pub source: PrincipalIdentifier,
336    /// Principal to which the resources are attributed.
337    pub subject: PrincipalIdentifier,
338    /// List of resources attributed to `subject` by `source`.
339    pub resources: Vec<ResourceReference>,
340}
341
342impl From<fplugin::Attribution> for Attribution {
343    fn from(value: fplugin::Attribution) -> Attribution {
344        Attribution {
345            source: value.source.unwrap().into(),
346            subject: value.subject.unwrap().into(),
347            resources: value.resources.unwrap().into_iter().map(|r| r.into()).collect(),
348        }
349    }
350}
351
352impl Into<fplugin::Attribution> for Attribution {
353    fn into(self) -> fplugin::Attribution {
354        fplugin::Attribution {
355            source: Some(self.source.into()),
356            subject: Some(self.subject.into()),
357            resources: Some(self.resources.into_iter().map(|r| r.into()).collect()),
358            ..Default::default()
359        }
360    }
361}
362
363#[derive(Clone, Copy)]
364/// References a kernel [`Resource`], or some subset of a [`Resource`] (such as a part of a process
365/// address space).
366pub enum ResourceReference {
367    /// Identifies a kernel object whose memory is being attributed.
368    ///
369    /// Refers to all memory held by VMOs reachable from the object
370    /// (currently a Job, Process or VMO).
371    KernelObject(u64),
372
373    /// Identifies a part of a process address space.
374    ProcessMapped {
375        /// The KOID of the process that this VMAR lives in.
376        process: u64,
377
378        /// Base address of the VMAR.
379        base: u64,
380
381        /// Length of the VMAR.
382        len: u64,
383    },
384}
385
386impl From<fplugin::ResourceReference> for ResourceReference {
387    fn from(value: fplugin::ResourceReference) -> ResourceReference {
388        match value {
389            fidl_fuchsia_memory_attribution_plugin::ResourceReference::KernelObject(ko) => {
390                ResourceReference::KernelObject(ko)
391            }
392            fidl_fuchsia_memory_attribution_plugin::ResourceReference::ProcessMapped(
393                fplugin::ProcessMapped { process, base, len },
394            ) => ResourceReference::ProcessMapped { process, base, len },
395            _ => unimplemented!(),
396        }
397    }
398}
399
400impl Into<fplugin::ResourceReference> for ResourceReference {
401    fn into(self) -> fplugin::ResourceReference {
402        match self {
403            ResourceReference::KernelObject(ko) => {
404                fidl_fuchsia_memory_attribution_plugin::ResourceReference::KernelObject(ko)
405            }
406            ResourceReference::ProcessMapped { process, base, len } => {
407                fidl_fuchsia_memory_attribution_plugin::ResourceReference::ProcessMapped(
408                    fplugin::ProcessMapped { process, base, len },
409                )
410            }
411        }
412    }
413}
414
415/// Capture of the current memory usage of a device, as retrieved through the memory attribution
416/// protocol. In this object, memory attribution is not resolved.
417pub struct AttributionData {
418    pub principals_vec: Vec<Principal>,
419    pub resources_vec: Vec<Resource>,
420    pub resource_names: Vec<ZXName>,
421    pub attributions: Vec<Attribution>,
422}
423
424// TODO(b/411121120): Use zx::Koid when available on host code.
425/// The post-order traversal of the Jobs->Process->VMOs tree guarantees that when
426/// a visitor processes a Process, it has already seen its associated VMOs.
427pub trait ResourcesVisitor {
428    fn on_job(
429        &mut self,
430        job_koid: zx_types::zx_koid_t,
431        job_name: &ZXName,
432        job: fplugin::Job,
433    ) -> Result<(), zx_status::Status>;
434    fn on_process(
435        &mut self,
436        process_koid: zx_types::zx_koid_t,
437        process_name: &ZXName,
438        process: fplugin::Process,
439    ) -> Result<(), zx_status::Status>;
440    fn on_vmo(
441        &mut self,
442        vmo_koid: zx_types::zx_koid_t,
443        vmo_name: &ZXName,
444        vmo: fplugin::Vmo,
445    ) -> Result<(), zx_status::Status>;
446}
447
448pub trait AttributionDataProvider: Send + Sync + 'static {
449    /// Collects and returns a structure with all memory resources and attribution specifications.
450    fn get_attribution_data(&self) -> BoxFuture<'_, Result<AttributionData, anyhow::Error>>;
451    /// Enumerates Jobs, Processes and VMOs and call back the visitor.
452    fn for_each_resource(&self, visitor: &mut impl ResourcesVisitor) -> Result<(), anyhow::Error>;
453}
454
455/// Processed snapshot of the memory usage of a device, with attribution of memory resources to
456/// Principals resolved.
457pub struct ProcessedAttributionData {
458    principals: HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
459    resources: HashMap<u64, RefCell<InflatedResource>>,
460    resource_names: Vec<ZXName>,
461}
462
463impl ProcessedAttributionData {
464    fn new(
465        principals: HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>>,
466        resources: HashMap<u64, RefCell<InflatedResource>>,
467        resource_names: Vec<ZXName>,
468    ) -> Self {
469        Self { principals, resources, resource_names }
470    }
471
472    /// Create a summary view of the memory attribution_data. See [MemorySummary] for details.
473    pub fn summary(&self) -> MemorySummary {
474        MemorySummary::build(&self.principals, &self.resources, &self.resource_names)
475    }
476}
477
478/// Process data from a [AttributionData] to resolve attribution claims.
479pub fn attribute_vmos(attribution_data: AttributionData) -> ProcessedAttributionData {
480    // Map from moniker token ID to Principal struct.
481    let principals: HashMap<PrincipalIdentifier, RefCell<InflatedPrincipal>> = attribution_data
482        .principals_vec
483        .into_iter()
484        .map(|p| (p.identifier.clone(), RefCell::new(InflatedPrincipal::new(p))))
485        .collect();
486
487    // Map from kernel resource koid to Resource struct.
488    let mut resources: HashMap<u64, RefCell<InflatedResource>> = attribution_data
489        .resources_vec
490        .into_iter()
491        .map(|r| (r.koid, RefCell::new(InflatedResource::new(r))))
492        .collect();
493
494    // Add direct claims to resources.
495    for attribution in attribution_data.attributions {
496        principals.get(&attribution.subject.clone().into()).map(|p| {
497            p.borrow_mut().attribution_claims.insert(attribution.source.into(), attribution.clone())
498        });
499        for resource in attribution.resources {
500            match resource {
501                ResourceReference::KernelObject(koid) => {
502                    if !resources.contains_key(&koid) {
503                        continue;
504                    }
505                    resources.get_mut(&koid).unwrap().get_mut().claims.insert(Claim {
506                        source: attribution.source.into(),
507                        subject: attribution.subject.into(),
508                        claim_type: ClaimType::Direct,
509                    });
510                }
511                ResourceReference::ProcessMapped { process, base, len } => {
512                    if !resources.contains_key(&process) {
513                        continue;
514                    }
515                    let mut matched_vmos = Vec::new();
516                    if let fplugin::ResourceType::Process(process_data) =
517                        &resources.get(&process).unwrap().borrow().resource.resource_type
518                    {
519                        for mapping in process_data.mappings.iter().flatten() {
520                            // We consider an entire VMO to be matched if it has a mapping
521                            // within the claimed region.
522                            if mapping.address_base.unwrap() >= base
523                                && mapping.address_base.unwrap() + mapping.size.unwrap()
524                                    <= base + len
525                            {
526                                matched_vmos.push(mapping.vmo.unwrap());
527                            }
528                        }
529                    }
530                    for vmo_koid in matched_vmos {
531                        match resources.get_mut(&vmo_koid) {
532                            Some(resource) => {
533                                resource.get_mut().claims.insert(Claim {
534                                    source: attribution.source.into(),
535                                    subject: attribution.subject.into(),
536                                    claim_type: ClaimType::Direct,
537                                });
538                            }
539                            None => {
540                                // The VMO is unknown. This can happen when a VMO is created between
541                                // the collection of the list of VMOs and the collection of the
542                                // process mappings.
543                            }
544                        }
545                    }
546                }
547            }
548        }
549    }
550
551    // Propagate claims. We propagate direct claims to child resources recursively until we hit a
552    // resource that is directly claimed: this is because we consider that attributors deeper in the
553    // principal hierarchy will not attribute resources higher in the resource hierarchy than the
554    // ones attributed by their ancestors (ie. attribution is always more precise as we go deeper).
555    for (_, resource_refcell) in &resources {
556        let resource = resource_refcell.borrow_mut();
557        // Extract the list of direct claims to propagate.
558        let direct_claims: Vec<&Claim> = resource
559            .claims
560            .iter()
561            .filter(|claim| match claim.claim_type {
562                ClaimType::Direct => true,
563                _ => false,
564            })
565            .collect();
566
567        if direct_claims.is_empty() {
568            // There is no direct claim to propagate, we can skip this resource.
569            continue;
570        }
571
572        let propagated_claims: Vec<Claim> = direct_claims
573            .into_iter()
574            .map(|claim| Claim {
575                source: claim.source,
576                subject: claim.subject,
577                claim_type: ClaimType::Indirect,
578            })
579            .collect();
580        let mut frontier = Vec::new();
581        frontier.extend(resource.children());
582        while !frontier.is_empty() {
583            let child = frontier.pop().unwrap();
584            let mut child_resource = match resources.get(&child) {
585                Some(resource) => resource.borrow_mut(),
586                None => {
587                    // This can happen if a resource is created or disappears while we were
588                    // collecting information about all the resources in the system. This should
589                    // remain a rare event.
590                    println!("Resource {} not found", child);
591                    continue;
592                }
593            };
594            if child_resource.claims.iter().any(|c| c.claim_type == ClaimType::Direct) {
595                // If there is a direct claim on the resource, don't propagate.
596                continue;
597            }
598            child_resource.claims.extend(propagated_claims.clone().iter());
599            frontier.extend(child_resource.children().iter());
600        }
601    }
602
603    for (_, resource_refcell) in &resources {
604        let mut resource = resource_refcell.borrow_mut();
605        resource.process_claims();
606    }
607
608    // Push claimed resources to principals. We are interested in VMOs as the VMOs are the resources
609    // actually holding memory. We also keep track of the process to display its name in the output.
610    for (resource_id, resource_refcell) in &resources {
611        let resource = resource_refcell.borrow();
612        if let fplugin::ResourceType::Vmo(vmo) = &resource.resource.resource_type {
613            let mut ancestors = vec![*resource_id];
614            // VMOs created by reference don't behave like COW-clones; their byte count is always
615            // zero, and all pages are attributed to their parent. This is not what we want here,
616            // as we prefer to acknowledge that the pages are shared between the parent and its
617            // children. We currently don't have a way to formally distinguish between VMOs created
618            // by reference and the other VMOs, so we use the heuristic of checking that they
619            // report they are always empty.
620            if vmo.total_populated_bytes.unwrap_or_default() == 0 {
621                let mut current_parent = vmo.parent;
622                // Add the parents of a VMO as "Child" claims. This is done so that slices of VMOs,
623                // with possibly no memory of their own, get attributed the resources of their
624                // parent.
625                while let Some(parent_koid) = current_parent {
626                    if parent_koid == 0 {
627                        panic!("Parent is not None but 0.");
628                    }
629                    ancestors.push(parent_koid);
630                    let mut current_resource = match resources.get(&parent_koid) {
631                        Some(res) => res.borrow_mut(),
632                        None => break,
633                    };
634                    current_resource.claims.extend(resource.claims.iter().map(|c| Claim {
635                        subject: c.subject,
636                        source: c.source,
637                        claim_type: ClaimType::Child,
638                    }));
639                    current_parent = match &current_resource.resource.resource_type {
640                        fplugin::ResourceType::Job(_) => panic!("This should not happen"),
641                        fplugin::ResourceType::Process(_) => panic!("This should not happen"),
642                        fplugin::ResourceType::Vmo(current_vmo) => current_vmo.parent,
643                        _ => unimplemented!(),
644                    };
645                }
646            }
647
648            for claim in &resource.claims {
649                principals
650                    .get(&claim.subject)
651                    .unwrap()
652                    .borrow_mut()
653                    .resources
654                    .extend(ancestors.iter());
655            }
656        } else if let fplugin::ResourceType::Process(_) = &resource.resource.resource_type {
657            for claim in &resource.claims {
658                principals
659                    .get(&claim.subject)
660                    .unwrap()
661                    .borrow_mut()
662                    .resources
663                    .insert(resource.resource.koid);
664            }
665        }
666    }
667
668    ProcessedAttributionData::new(principals, resources, attribution_data.resource_names)
669}
670
671pub mod testing {
672    use crate::{AttributionData, AttributionDataProvider, Resource, ResourcesVisitor};
673    use fidl_fuchsia_memory_attribution_plugin::ResourceType;
674    use futures::future::{ready, BoxFuture};
675
676    pub struct FakeAttributionDataProvider {
677        pub attribution_data: AttributionData,
678    }
679
680    impl AttributionDataProvider for FakeAttributionDataProvider {
681        fn get_attribution_data(&self) -> BoxFuture<'_, Result<AttributionData, anyhow::Error>> {
682            Box::pin(ready(Ok(AttributionData {
683                principals_vec: self.attribution_data.principals_vec.clone(),
684                resources_vec: self.attribution_data.resources_vec.clone(),
685                resource_names: self.attribution_data.resource_names.clone(),
686                attributions: self.attribution_data.attributions.clone(),
687            })))
688        }
689
690        fn for_each_resource(
691            &self,
692            visitor: &mut impl ResourcesVisitor,
693        ) -> Result<(), anyhow::Error> {
694            for resource in &self.attribution_data.resources_vec {
695                if let Resource {
696                    koid, name_index, resource_type: ResourceType::Vmo(vmo), ..
697                } = resource
698                {
699                    visitor.on_vmo(
700                        *koid,
701                        &self.attribution_data.resource_names[*name_index],
702                        vmo.clone(),
703                    )?;
704                }
705            }
706            for resource in &self.attribution_data.resources_vec {
707                if let Resource {
708                    koid,
709                    name_index,
710                    resource_type: ResourceType::Process(process),
711                    ..
712                } = resource
713                {
714                    visitor.on_process(
715                        *koid,
716                        &self.attribution_data.resource_names[*name_index],
717                        process.clone(),
718                    )?;
719                }
720            }
721            for resource in &self.attribution_data.resources_vec {
722                if let Resource {
723                    koid, name_index, resource_type: ResourceType::Job(job), ..
724                } = resource
725                {
726                    visitor.on_job(
727                        *koid,
728                        &self.attribution_data.resource_names[*name_index],
729                        job.clone(),
730                    )?;
731                }
732            }
733            Ok(())
734        }
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741    use std::collections::HashMap;
742    use summary::{PrincipalSummary, VmoSummary};
743
744    #[test]
745    fn test_gather_resources() {
746        // Create a fake snapshot with 4 principals:
747        // root (0)
748        //  - runner (1)
749        //    - component 3 (3)
750        //  - component 2 (2)
751        //
752        // and the following job/process/vmo hierarchy:
753        // root_job (1000)
754        //  * root_process (1001)
755        //    . root_vmo (1002)
756        //    . shared_vmo (1003)
757        //  - runner_job (1004)
758        //    * runner_process (1005)
759        //      . runner_vmo (1006)
760        //      . component_vmo (1007)
761        //      . component_vmo2 (1012)
762        //      . component_vmo3 (1013)
763        //  - component_2_job (1008)
764        //    * 2_process (1009)
765        //      . 2_vmo (1010)
766        //      . shared_vmo (1003)
767        // And an additional parent VMO for 2_vmo, 2_vmo_parent (1011).
768
769        let resource_names = vec![
770            ZXName::from_string_lossy("root_job"),
771            ZXName::from_string_lossy("root_process"),
772            ZXName::from_string_lossy("root_vmo"),
773            ZXName::from_string_lossy("shared_vmo"),
774            ZXName::from_string_lossy("runner_job"),
775            ZXName::from_string_lossy("runner_process"),
776            ZXName::from_string_lossy("runner_vmo"),
777            ZXName::from_string_lossy("component_vmo"),
778            ZXName::from_string_lossy("component_2_job"),
779            ZXName::from_string_lossy("2_process"),
780            ZXName::from_string_lossy("2_vmo"),
781            ZXName::from_string_lossy("2_vmo_parent"),
782            ZXName::from_string_lossy("component_vmo_mapped"),
783            ZXName::from_string_lossy("component_vmo_mapped2"),
784        ];
785
786        let attributions = vec![
787            fplugin::Attribution {
788                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
789                subject: Some(fplugin::PrincipalIdentifier { id: 0 }),
790                resources: Some(vec![fplugin::ResourceReference::KernelObject(1000)]),
791                ..Default::default()
792            },
793            fplugin::Attribution {
794                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
795                subject: Some(fplugin::PrincipalIdentifier { id: 1 }),
796                resources: Some(vec![fplugin::ResourceReference::KernelObject(1004)]),
797                ..Default::default()
798            },
799            fplugin::Attribution {
800                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
801                subject: Some(fplugin::PrincipalIdentifier { id: 2 }),
802                resources: Some(vec![fplugin::ResourceReference::KernelObject(1008)]),
803                ..Default::default()
804            },
805            fplugin::Attribution {
806                source: Some(fplugin::PrincipalIdentifier { id: 1 }),
807                subject: Some(fplugin::PrincipalIdentifier { id: 3 }),
808                resources: Some(vec![
809                    fplugin::ResourceReference::KernelObject(1007),
810                    fplugin::ResourceReference::ProcessMapped(fplugin::ProcessMapped {
811                        process: 1005,
812                        base: 1024,
813                        len: 1024,
814                    }),
815                ]),
816                ..Default::default()
817            },
818        ]
819        .into_iter()
820        .map(|a| a.into())
821        .collect();
822
823        let principals = vec![
824            fplugin::Principal {
825                identifier: Some(fplugin::PrincipalIdentifier { id: 0 }),
826                description: Some(fplugin::Description::Component("root".to_owned())),
827                principal_type: Some(fplugin::PrincipalType::Runnable),
828                parent: None,
829                ..Default::default()
830            },
831            fplugin::Principal {
832                identifier: Some(fplugin::PrincipalIdentifier { id: 1 }),
833                description: Some(fplugin::Description::Component("runner".to_owned())),
834                principal_type: Some(fplugin::PrincipalType::Runnable),
835                parent: Some(fplugin::PrincipalIdentifier { id: 0 }),
836                ..Default::default()
837            },
838            fplugin::Principal {
839                identifier: Some(fplugin::PrincipalIdentifier { id: 2 }),
840                description: Some(fplugin::Description::Component("component 2".to_owned())),
841                principal_type: Some(fplugin::PrincipalType::Runnable),
842                parent: Some(fplugin::PrincipalIdentifier { id: 0 }),
843                ..Default::default()
844            },
845            fplugin::Principal {
846                identifier: Some(fplugin::PrincipalIdentifier { id: 3 }),
847                description: Some(fplugin::Description::Component("component 3".to_owned())),
848                principal_type: Some(fplugin::PrincipalType::Runnable),
849                parent: Some(fplugin::PrincipalIdentifier { id: 1 }),
850                ..Default::default()
851            },
852        ]
853        .into_iter()
854        .map(|p| p.into())
855        .collect();
856
857        let resources = vec![
858            fplugin::Resource {
859                koid: Some(1000),
860                name_index: Some(0),
861                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
862                    child_jobs: Some(vec![1004, 1008]),
863                    processes: Some(vec![1001]),
864                    ..Default::default()
865                })),
866                ..Default::default()
867            },
868            fplugin::Resource {
869                koid: Some(1001),
870                name_index: Some(1),
871                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
872                    vmos: Some(vec![1002, 1003]),
873                    mappings: None,
874                    ..Default::default()
875                })),
876                ..Default::default()
877            },
878            fplugin::Resource {
879                koid: Some(1002),
880                name_index: Some(2),
881                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
882                    private_committed_bytes: Some(1024),
883                    private_populated_bytes: Some(2048),
884                    scaled_committed_bytes: Some(1024),
885                    scaled_populated_bytes: Some(2048),
886                    total_committed_bytes: Some(1024),
887                    total_populated_bytes: Some(2048),
888                    ..Default::default()
889                })),
890                ..Default::default()
891            },
892            fplugin::Resource {
893                koid: Some(1003),
894                name_index: Some(3),
895                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
896                    private_committed_bytes: Some(1024),
897                    private_populated_bytes: Some(2048),
898                    scaled_committed_bytes: Some(1024),
899                    scaled_populated_bytes: Some(2048),
900                    total_committed_bytes: Some(1024),
901                    total_populated_bytes: Some(2048),
902                    ..Default::default()
903                })),
904                ..Default::default()
905            },
906            fplugin::Resource {
907                koid: Some(1004),
908                name_index: Some(4),
909                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
910                    child_jobs: Some(vec![]),
911                    processes: Some(vec![1005]),
912                    ..Default::default()
913                })),
914                ..Default::default()
915            },
916            fplugin::Resource {
917                koid: Some(1005),
918                name_index: Some(5),
919                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
920                    vmos: Some(vec![1006, 1007, 1012]),
921                    mappings: Some(vec![
922                        fplugin::Mapping {
923                            vmo: Some(1006),
924                            address_base: Some(0),
925                            size: Some(512),
926                            ..Default::default()
927                        },
928                        fplugin::Mapping {
929                            vmo: Some(1012),
930                            address_base: Some(1024),
931                            size: Some(512),
932                            ..Default::default()
933                        },
934                        fplugin::Mapping {
935                            vmo: Some(1013),
936                            address_base: Some(1536),
937                            size: Some(512),
938                            ..Default::default()
939                        },
940                        fplugin::Mapping {
941                            vmo: Some(1006),
942                            address_base: Some(2048),
943                            size: Some(512),
944                            ..Default::default()
945                        },
946                    ]),
947                    ..Default::default()
948                })),
949                ..Default::default()
950            },
951            fplugin::Resource {
952                koid: Some(1006),
953                name_index: Some(6),
954                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
955                    private_committed_bytes: Some(1024),
956                    private_populated_bytes: Some(2048),
957                    scaled_committed_bytes: Some(1024),
958                    scaled_populated_bytes: Some(2048),
959                    total_committed_bytes: Some(1024),
960                    total_populated_bytes: Some(2048),
961                    ..Default::default()
962                })),
963                ..Default::default()
964            },
965            fplugin::Resource {
966                koid: Some(1007),
967                name_index: Some(7),
968                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
969                    private_committed_bytes: Some(128),
970                    private_populated_bytes: Some(256),
971                    scaled_committed_bytes: Some(128),
972                    scaled_populated_bytes: Some(256),
973                    total_committed_bytes: Some(128),
974                    total_populated_bytes: Some(256),
975                    ..Default::default()
976                })),
977                ..Default::default()
978            },
979            fplugin::Resource {
980                koid: Some(1008),
981                name_index: Some(8),
982                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
983                    child_jobs: Some(vec![]),
984                    processes: Some(vec![1009]),
985                    ..Default::default()
986                })),
987                ..Default::default()
988            },
989            fplugin::Resource {
990                koid: Some(1009),
991                name_index: Some(9),
992                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
993                    vmos: Some(vec![1010, 1003]),
994                    mappings: None,
995                    ..Default::default()
996                })),
997                ..Default::default()
998            },
999            fplugin::Resource {
1000                koid: Some(1010),
1001                name_index: Some(10),
1002                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1003                    parent: Some(1011),
1004                    private_committed_bytes: Some(1024),
1005                    private_populated_bytes: Some(2048),
1006                    scaled_committed_bytes: Some(1024),
1007                    scaled_populated_bytes: Some(2048),
1008                    total_committed_bytes: Some(1024),
1009                    total_populated_bytes: Some(2048),
1010                    ..Default::default()
1011                })),
1012                ..Default::default()
1013            },
1014            fplugin::Resource {
1015                koid: Some(1011),
1016                name_index: Some(11),
1017                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1018                    private_committed_bytes: Some(1024),
1019                    private_populated_bytes: Some(2048),
1020                    scaled_committed_bytes: Some(1024),
1021                    scaled_populated_bytes: Some(2048),
1022                    total_committed_bytes: Some(1024),
1023                    total_populated_bytes: Some(2048),
1024                    ..Default::default()
1025                })),
1026                ..Default::default()
1027            },
1028            fplugin::Resource {
1029                koid: Some(1012),
1030                name_index: Some(12),
1031                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1032                    private_committed_bytes: Some(1024),
1033                    private_populated_bytes: Some(2048),
1034                    scaled_committed_bytes: Some(1024),
1035                    scaled_populated_bytes: Some(2048),
1036                    total_committed_bytes: Some(1024),
1037                    total_populated_bytes: Some(2048),
1038                    ..Default::default()
1039                })),
1040                ..Default::default()
1041            },
1042            fplugin::Resource {
1043                koid: Some(1013),
1044                name_index: Some(13),
1045                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1046                    private_committed_bytes: Some(1024),
1047                    private_populated_bytes: Some(2048),
1048                    scaled_committed_bytes: Some(1024),
1049                    scaled_populated_bytes: Some(2048),
1050                    total_committed_bytes: Some(1024),
1051                    total_populated_bytes: Some(2048),
1052                    ..Default::default()
1053                })),
1054                ..Default::default()
1055            },
1056        ]
1057        .into_iter()
1058        .map(|r| r.into())
1059        .collect();
1060
1061        let output = attribute_vmos(AttributionData {
1062            principals_vec: principals,
1063            resources_vec: resources,
1064            resource_names,
1065            attributions,
1066        })
1067        .summary();
1068
1069        assert_eq!(output.undigested, 2048);
1070        assert_eq!(output.principals.len(), 4);
1071
1072        let principals: HashMap<u64, PrincipalSummary> =
1073            output.principals.into_iter().map(|p| (p.id, p)).collect();
1074
1075        assert_eq!(
1076            principals.get(&0).unwrap(),
1077            &PrincipalSummary {
1078                id: 0,
1079                name: "root".to_owned(),
1080                principal_type: "R".to_owned(),
1081                committed_private: 1024,
1082                committed_scaled: 1536.0,
1083                committed_total: 2048,
1084                populated_private: 2048,
1085                populated_scaled: 3072.0,
1086                populated_total: 4096,
1087                attributor: None,
1088                processes: vec!["root_process (1001)".to_owned()],
1089                vmos: vec![
1090                    (
1091                        ZXName::from_string_lossy("root_vmo"),
1092                        VmoSummary {
1093                            count: 1,
1094                            committed_private: 1024,
1095                            committed_scaled: 1024.0,
1096                            committed_total: 1024,
1097                            populated_private: 2048,
1098                            populated_scaled: 2048.0,
1099                            populated_total: 2048,
1100                            ..Default::default()
1101                        }
1102                    ),
1103                    (
1104                        ZXName::from_string_lossy("shared_vmo"),
1105                        VmoSummary {
1106                            count: 1,
1107                            committed_private: 0,
1108                            committed_scaled: 512.0,
1109                            committed_total: 1024,
1110                            populated_private: 0,
1111                            populated_scaled: 1024.0,
1112                            populated_total: 2048,
1113                            ..Default::default()
1114                        }
1115                    )
1116                ]
1117                .into_iter()
1118                .collect(),
1119            }
1120        );
1121
1122        assert_eq!(
1123            principals.get(&1).unwrap(),
1124            &PrincipalSummary {
1125                id: 1,
1126                name: "runner".to_owned(),
1127                principal_type: "R".to_owned(),
1128                committed_private: 1024,
1129                committed_scaled: 1024.0,
1130                committed_total: 1024,
1131                populated_private: 2048,
1132                populated_scaled: 2048.0,
1133                populated_total: 2048,
1134                attributor: Some("root".to_owned()),
1135                processes: vec!["runner_process (1005)".to_owned()],
1136                vmos: vec![(
1137                    ZXName::from_string_lossy("runner_vmo"),
1138                    VmoSummary {
1139                        count: 1,
1140                        committed_private: 1024,
1141                        committed_scaled: 1024.0,
1142                        committed_total: 1024,
1143                        populated_private: 2048,
1144                        populated_scaled: 2048.0,
1145                        populated_total: 2048,
1146                        ..Default::default()
1147                    }
1148                )]
1149                .into_iter()
1150                .collect(),
1151            }
1152        );
1153
1154        assert_eq!(
1155            principals.get(&2).unwrap(),
1156            &PrincipalSummary {
1157                id: 2,
1158                name: "component 2".to_owned(),
1159                principal_type: "R".to_owned(),
1160                committed_private: 1024,
1161                committed_scaled: 1536.0,
1162                committed_total: 2048,
1163                populated_private: 2048,
1164                populated_scaled: 3072.0,
1165                populated_total: 4096,
1166                attributor: Some("root".to_owned()),
1167                processes: vec!["2_process (1009)".to_owned()],
1168                vmos: vec![
1169                    (
1170                        ZXName::from_string_lossy("shared_vmo"),
1171                        VmoSummary {
1172                            count: 1,
1173                            committed_private: 0,
1174                            committed_scaled: 512.0,
1175                            committed_total: 1024,
1176                            populated_private: 0,
1177                            populated_scaled: 1024.0,
1178                            populated_total: 2048,
1179                            ..Default::default()
1180                        }
1181                    ),
1182                    (
1183                        ZXName::from_string_lossy("2_vmo"),
1184                        VmoSummary {
1185                            count: 1,
1186                            committed_private: 1024,
1187                            committed_scaled: 1024.0,
1188                            committed_total: 1024,
1189                            populated_private: 2048,
1190                            populated_scaled: 2048.0,
1191                            populated_total: 2048,
1192                            ..Default::default()
1193                        }
1194                    )
1195                ]
1196                .into_iter()
1197                .collect(),
1198            }
1199        );
1200
1201        assert_eq!(
1202            principals.get(&3).unwrap(),
1203            &PrincipalSummary {
1204                id: 3,
1205                name: "component 3".to_owned(),
1206                principal_type: "R".to_owned(),
1207                committed_private: 2176,
1208                committed_scaled: 2176.0,
1209                committed_total: 2176,
1210                populated_private: 4352,
1211                populated_scaled: 4352.0,
1212                populated_total: 4352,
1213                attributor: Some("runner".to_owned()),
1214                processes: vec!["runner_process (1005)".to_owned()],
1215                vmos: vec![
1216                    (
1217                        ZXName::from_string_lossy("component_vmo"),
1218                        VmoSummary {
1219                            count: 1,
1220                            committed_private: 128,
1221                            committed_scaled: 128.0,
1222                            committed_total: 128,
1223                            populated_private: 256,
1224                            populated_scaled: 256.0,
1225                            populated_total: 256,
1226                            ..Default::default()
1227                        }
1228                    ),
1229                    (
1230                        ZXName::from_string_lossy("component_vmo_mapped"),
1231                        VmoSummary {
1232                            count: 1,
1233                            committed_private: 1024,
1234                            committed_scaled: 1024.0,
1235                            committed_total: 1024,
1236                            populated_private: 2048,
1237                            populated_scaled: 2048.0,
1238                            populated_total: 2048,
1239                            ..Default::default()
1240                        }
1241                    ),
1242                    (
1243                        ZXName::from_string_lossy("component_vmo_mapped2"),
1244                        VmoSummary {
1245                            count: 1,
1246                            committed_private: 1024,
1247                            committed_scaled: 1024.0,
1248                            committed_total: 1024,
1249                            populated_private: 2048,
1250                            populated_scaled: 2048.0,
1251                            populated_total: 2048,
1252                            ..Default::default()
1253                        }
1254                    )
1255                ]
1256                .into_iter()
1257                .collect(),
1258            }
1259        );
1260    }
1261
1262    #[test]
1263    fn test_reshare_resources() {
1264        // Create a fake snapshot with 3 principals:
1265        // root (0)
1266        //  - component 1 (1)
1267        //    - component 2 (2)
1268        //
1269        // and the following job/process/vmo hierarchy:
1270        // root_job (1000)
1271        //  - component_job (1001)
1272        //    * component_process (1002)
1273        //      . component_vmo (1003)
1274        //
1275        // In this scenario, component 1 reattributes component_job to component 2 entirely.
1276
1277        let resource_names = vec![
1278            ZXName::from_string_lossy("root_job"),
1279            ZXName::from_string_lossy("component_job"),
1280            ZXName::from_string_lossy("component_process"),
1281            ZXName::from_string_lossy("component_vmo"),
1282        ];
1283        let attributions = vec![
1284            fplugin::Attribution {
1285                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
1286                subject: Some(fplugin::PrincipalIdentifier { id: 0 }),
1287                resources: Some(vec![fplugin::ResourceReference::KernelObject(1000)]),
1288                ..Default::default()
1289            },
1290            fplugin::Attribution {
1291                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
1292                subject: Some(fplugin::PrincipalIdentifier { id: 1 }),
1293                resources: Some(vec![fplugin::ResourceReference::KernelObject(1001)]),
1294                ..Default::default()
1295            },
1296            fplugin::Attribution {
1297                source: Some(fplugin::PrincipalIdentifier { id: 1 }),
1298                subject: Some(fplugin::PrincipalIdentifier { id: 2 }),
1299                resources: Some(vec![fplugin::ResourceReference::KernelObject(1001)]),
1300                ..Default::default()
1301            },
1302        ]
1303        .into_iter()
1304        .map(|a| a.into())
1305        .collect();
1306        let principals = vec![
1307            fplugin::Principal {
1308                identifier: Some(fplugin::PrincipalIdentifier { id: 0 }),
1309                description: Some(fplugin::Description::Component("root".to_owned())),
1310                principal_type: Some(fplugin::PrincipalType::Runnable),
1311                parent: None,
1312                ..Default::default()
1313            },
1314            fplugin::Principal {
1315                identifier: Some(fplugin::PrincipalIdentifier { id: 1 }),
1316                description: Some(fplugin::Description::Component("component 1".to_owned())),
1317                principal_type: Some(fplugin::PrincipalType::Runnable),
1318                parent: Some(fplugin::PrincipalIdentifier { id: 0 }),
1319                ..Default::default()
1320            },
1321            fplugin::Principal {
1322                identifier: Some(fplugin::PrincipalIdentifier { id: 2 }),
1323                description: Some(fplugin::Description::Component("component 2".to_owned())),
1324                principal_type: Some(fplugin::PrincipalType::Runnable),
1325                parent: Some(fplugin::PrincipalIdentifier { id: 1 }),
1326                ..Default::default()
1327            },
1328        ]
1329        .into_iter()
1330        .map(|p| p.into())
1331        .collect();
1332
1333        let resources = vec![
1334            fplugin::Resource {
1335                koid: Some(1000),
1336                name_index: Some(0),
1337                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
1338                    child_jobs: Some(vec![1001]),
1339                    processes: Some(vec![]),
1340                    ..Default::default()
1341                })),
1342                ..Default::default()
1343            },
1344            fplugin::Resource {
1345                koid: Some(1001),
1346                name_index: Some(1),
1347                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
1348                    child_jobs: Some(vec![]),
1349                    processes: Some(vec![1002]),
1350                    ..Default::default()
1351                })),
1352                ..Default::default()
1353            },
1354            fplugin::Resource {
1355                koid: Some(1002),
1356                name_index: Some(2),
1357                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
1358                    vmos: Some(vec![1003]),
1359                    mappings: None,
1360                    ..Default::default()
1361                })),
1362                ..Default::default()
1363            },
1364            fplugin::Resource {
1365                koid: Some(1003),
1366                name_index: Some(3),
1367                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1368                    private_committed_bytes: Some(1024),
1369                    private_populated_bytes: Some(2048),
1370                    scaled_committed_bytes: Some(1024),
1371                    scaled_populated_bytes: Some(2048),
1372                    total_committed_bytes: Some(1024),
1373                    total_populated_bytes: Some(2048),
1374                    ..Default::default()
1375                })),
1376                ..Default::default()
1377            },
1378        ]
1379        .into_iter()
1380        .map(|r| r.into())
1381        .collect();
1382
1383        let output = attribute_vmos(AttributionData {
1384            principals_vec: principals,
1385            resources_vec: resources,
1386            resource_names,
1387            attributions,
1388        })
1389        .summary();
1390
1391        assert_eq!(output.undigested, 0);
1392        assert_eq!(output.principals.len(), 3);
1393
1394        let principals: HashMap<u64, PrincipalSummary> =
1395            output.principals.into_iter().map(|p| (p.id, p)).collect();
1396
1397        assert_eq!(
1398            principals.get(&0).unwrap(),
1399            &PrincipalSummary {
1400                id: 0,
1401                name: "root".to_owned(),
1402                principal_type: "R".to_owned(),
1403                committed_private: 0,
1404                committed_scaled: 0.0,
1405                committed_total: 0,
1406                populated_private: 0,
1407                populated_scaled: 0.0,
1408                populated_total: 0,
1409                attributor: None,
1410                processes: vec![],
1411                vmos: vec![].into_iter().collect(),
1412            }
1413        );
1414
1415        assert_eq!(
1416            principals.get(&1).unwrap(),
1417            &PrincipalSummary {
1418                id: 1,
1419                name: "component 1".to_owned(),
1420                principal_type: "R".to_owned(),
1421                committed_private: 0,
1422                committed_scaled: 0.0,
1423                committed_total: 0,
1424                populated_private: 0,
1425                populated_scaled: 0.0,
1426                populated_total: 0,
1427                attributor: Some("root".to_owned()),
1428                processes: vec![],
1429                vmos: vec![].into_iter().collect(),
1430            }
1431        );
1432
1433        assert_eq!(
1434            principals.get(&2).unwrap(),
1435            &PrincipalSummary {
1436                id: 2,
1437                name: "component 2".to_owned(),
1438                principal_type: "R".to_owned(),
1439                committed_private: 1024,
1440                committed_scaled: 1024.0,
1441                committed_total: 1024,
1442                populated_private: 2048,
1443                populated_scaled: 2048.0,
1444                populated_total: 2048,
1445                attributor: Some("component 1".to_owned()),
1446                processes: vec!["component_process (1002)".to_owned()],
1447                vmos: vec![(
1448                    ZXName::from_string_lossy("component_vmo"),
1449                    VmoSummary {
1450                        count: 1,
1451                        committed_private: 1024,
1452                        committed_scaled: 1024.0,
1453                        committed_total: 1024,
1454                        populated_private: 2048,
1455                        populated_scaled: 2048.0,
1456                        populated_total: 2048,
1457                        ..Default::default()
1458                    }
1459                ),]
1460                .into_iter()
1461                .collect(),
1462            }
1463        );
1464    }
1465
1466    #[test]
1467    fn test_conversions() {
1468        let plugin_principal = fplugin::Principal {
1469            identifier: Some(fplugin::PrincipalIdentifier { id: 0 }),
1470            description: Some(fplugin::Description::Component("root".to_owned())),
1471            principal_type: Some(fplugin::PrincipalType::Runnable),
1472            parent: None,
1473            ..Default::default()
1474        };
1475
1476        let data_principal: Principal = plugin_principal.clone().into();
1477
1478        assert_eq!(plugin_principal, data_principal.into());
1479
1480        let plugin_resources = vec![
1481            fplugin::Resource {
1482                koid: Some(1000),
1483                name_index: Some(0),
1484                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
1485                    child_jobs: Some(vec![1004, 1008]),
1486                    processes: Some(vec![1001]),
1487                    ..Default::default()
1488                })),
1489                ..Default::default()
1490            },
1491            fplugin::Resource {
1492                koid: Some(1001),
1493                name_index: Some(1),
1494                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
1495                    vmos: Some(vec![1002, 1003]),
1496                    mappings: None,
1497                    ..Default::default()
1498                })),
1499                ..Default::default()
1500            },
1501            fplugin::Resource {
1502                koid: Some(1002),
1503                name_index: Some(2),
1504                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1505                    private_committed_bytes: Some(1024),
1506                    private_populated_bytes: Some(2048),
1507                    scaled_committed_bytes: Some(1024),
1508                    scaled_populated_bytes: Some(2048),
1509                    total_committed_bytes: Some(1024),
1510                    total_populated_bytes: Some(2048),
1511                    ..Default::default()
1512                })),
1513                ..Default::default()
1514            },
1515            fplugin::Resource {
1516                koid: Some(1005),
1517                name_index: Some(5),
1518                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
1519                    vmos: Some(vec![1006, 1007, 1012]),
1520                    mappings: Some(vec![
1521                        fplugin::Mapping {
1522                            vmo: Some(1006),
1523                            address_base: Some(0),
1524                            size: Some(512),
1525                            ..Default::default()
1526                        },
1527                        fplugin::Mapping {
1528                            vmo: Some(1012),
1529                            address_base: Some(1024),
1530                            size: Some(512),
1531                            ..Default::default()
1532                        },
1533                    ]),
1534                    ..Default::default()
1535                })),
1536                ..Default::default()
1537            },
1538        ];
1539
1540        let data_resources: Vec<Resource> =
1541            plugin_resources.iter().cloned().map(|r| r.into()).collect();
1542
1543        let actual_resources: Vec<fplugin::Resource> =
1544            data_resources.into_iter().map(|r| r.into()).collect();
1545
1546        assert_eq!(plugin_resources, actual_resources);
1547    }
1548
1549    #[test]
1550    fn test_vmo_reference() {
1551        // Create a fake snapshot with 3 principals:
1552        // root (0)
1553        //  - component 1 (1)
1554        //
1555        // and the following job/process/vmo hierarchy:
1556        // root_job (1000)
1557        //  - component_job (1001)
1558        //    * component_process (1002)
1559        //      . component_vmo (1003)
1560        // component_vmo_parent (1004)
1561        //
1562        // In this scenario, component_vmo is a reference to component_vmo_parent and should get
1563        // shared attribution of its pages.
1564
1565        let resource_names = vec![
1566            name::ZXName::from_string_lossy("root_job"),
1567            name::ZXName::from_string_lossy("component_job"),
1568            name::ZXName::from_string_lossy("component_process"),
1569            name::ZXName::from_string_lossy("component_vmo"),
1570            name::ZXName::from_string_lossy("component_vmo_parent"),
1571        ];
1572        let attributions = vec![
1573            fplugin::Attribution {
1574                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
1575                subject: Some(fplugin::PrincipalIdentifier { id: 0 }),
1576                resources: Some(vec![fplugin::ResourceReference::KernelObject(1000)]),
1577                ..Default::default()
1578            },
1579            fplugin::Attribution {
1580                source: Some(fplugin::PrincipalIdentifier { id: 0 }),
1581                subject: Some(fplugin::PrincipalIdentifier { id: 1 }),
1582                resources: Some(vec![fplugin::ResourceReference::KernelObject(1001)]),
1583                ..Default::default()
1584            },
1585        ]
1586        .into_iter()
1587        .map(|a| a.into())
1588        .collect();
1589        let principals = vec![
1590            fplugin::Principal {
1591                identifier: Some(fplugin::PrincipalIdentifier { id: 0 }),
1592                description: Some(fplugin::Description::Component("root".to_owned())),
1593                principal_type: Some(fplugin::PrincipalType::Runnable),
1594                parent: None,
1595                ..Default::default()
1596            },
1597            fplugin::Principal {
1598                identifier: Some(fplugin::PrincipalIdentifier { id: 1 }),
1599                description: Some(fplugin::Description::Component("component 1".to_owned())),
1600                principal_type: Some(fplugin::PrincipalType::Runnable),
1601                parent: Some(fplugin::PrincipalIdentifier { id: 0 }),
1602                ..Default::default()
1603            },
1604        ]
1605        .into_iter()
1606        .map(|p| p.into())
1607        .collect();
1608
1609        let resources = vec![
1610            fplugin::Resource {
1611                koid: Some(1000),
1612                name_index: Some(0),
1613                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
1614                    child_jobs: Some(vec![1001]),
1615                    processes: Some(vec![]),
1616                    ..Default::default()
1617                })),
1618                ..Default::default()
1619            },
1620            fplugin::Resource {
1621                koid: Some(1001),
1622                name_index: Some(1),
1623                resource_type: Some(fplugin::ResourceType::Job(fplugin::Job {
1624                    child_jobs: Some(vec![]),
1625                    processes: Some(vec![1002]),
1626                    ..Default::default()
1627                })),
1628                ..Default::default()
1629            },
1630            fplugin::Resource {
1631                koid: Some(1002),
1632                name_index: Some(2),
1633                resource_type: Some(fplugin::ResourceType::Process(fplugin::Process {
1634                    vmos: Some(vec![1003]),
1635                    mappings: None,
1636                    ..Default::default()
1637                })),
1638                ..Default::default()
1639            },
1640            fplugin::Resource {
1641                koid: Some(1003),
1642                name_index: Some(3),
1643                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1644                    parent: Some(1004),
1645                    private_committed_bytes: Some(0),
1646                    private_populated_bytes: Some(0),
1647                    scaled_committed_bytes: Some(0),
1648                    scaled_populated_bytes: Some(0),
1649                    total_committed_bytes: Some(0),
1650                    total_populated_bytes: Some(0),
1651                    ..Default::default()
1652                })),
1653                ..Default::default()
1654            },
1655            fplugin::Resource {
1656                koid: Some(1004),
1657                name_index: Some(4),
1658                resource_type: Some(fplugin::ResourceType::Vmo(fplugin::Vmo {
1659                    private_committed_bytes: Some(1024),
1660                    private_populated_bytes: Some(2048),
1661                    scaled_committed_bytes: Some(1024),
1662                    scaled_populated_bytes: Some(2048),
1663                    total_committed_bytes: Some(1024),
1664                    total_populated_bytes: Some(2048),
1665                    ..Default::default()
1666                })),
1667                ..Default::default()
1668            },
1669        ]
1670        .into_iter()
1671        .map(|r| r.into())
1672        .collect();
1673
1674        let output = attribute_vmos(AttributionData {
1675            principals_vec: principals,
1676            resources_vec: resources,
1677            resource_names,
1678            attributions,
1679        })
1680        .summary();
1681
1682        assert_eq!(output.undigested, 0);
1683        assert_eq!(output.principals.len(), 2);
1684
1685        let principals: HashMap<u64, PrincipalSummary> =
1686            output.principals.into_iter().map(|p| (p.id, p)).collect();
1687
1688        assert_eq!(
1689            principals.get(&0).unwrap(),
1690            &PrincipalSummary {
1691                id: 0,
1692                name: "root".to_owned(),
1693                principal_type: "R".to_owned(),
1694                committed_private: 0,
1695                committed_scaled: 0.0,
1696                committed_total: 0,
1697                populated_private: 0,
1698                populated_scaled: 0.0,
1699                populated_total: 0,
1700                attributor: None,
1701                processes: vec![],
1702                vmos: vec![].into_iter().collect(),
1703            }
1704        );
1705
1706        assert_eq!(
1707            principals.get(&1).unwrap(),
1708            &PrincipalSummary {
1709                id: 1,
1710                name: "component 1".to_owned(),
1711                principal_type: "R".to_owned(),
1712                committed_private: 1024,
1713                committed_scaled: 1024.0,
1714                committed_total: 1024,
1715                populated_private: 2048,
1716                populated_scaled: 2048.0,
1717                populated_total: 2048,
1718                attributor: Some("root".to_owned()),
1719                processes: vec!["component_process (1002)".to_owned()],
1720                vmos: vec![
1721                    (
1722                        name::ZXName::from_string_lossy("component_vmo"),
1723                        VmoSummary {
1724                            count: 1,
1725                            committed_private: 0,
1726                            committed_scaled: 0.0,
1727                            committed_total: 0,
1728                            populated_private: 0,
1729                            populated_scaled: 0.0,
1730                            populated_total: 0,
1731                            ..Default::default()
1732                        }
1733                    ),
1734                    (
1735                        name::ZXName::from_string_lossy("component_vmo_parent"),
1736                        VmoSummary {
1737                            count: 1,
1738                            committed_private: 1024,
1739                            committed_scaled: 1024.0,
1740                            committed_total: 1024,
1741                            populated_private: 2048,
1742                            populated_scaled: 2048.0,
1743                            populated_total: 2048,
1744                            ..Default::default()
1745                        }
1746                    ),
1747                ]
1748                .into_iter()
1749                .collect(),
1750            }
1751        );
1752    }
1753}