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