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