attribution_processing/
lib.rs

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