blob: 5e2bba2aba1236d41bd8df0aafb806b548a72a50 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![cfg(test)]
use fidl_fuchsia_component as fcomponent;
use fidl_fuchsia_component_decl as fdecl;
use fidl_fuchsia_examples_colocated as fcolocated;
use fidl_fuchsia_memory_attribution as fattribution;
use fidl_fuchsia_process::HandleInfo;
use fuchsia_async as fasync;
use fuchsia_component_test::{
Capability, ChildOptions, RealmBuilder, RealmBuilderParams, Ref, Route,
};
use fuchsia_runtime::HandleType;
use fuchsia_zircon as zx;
use futures_util::{future::BoxFuture, FutureExt};
use std::{collections::HashMap, sync::Arc};
/// Attribute resource given the set of resources at the root of the system,
/// and a protocol to attribute resources under different principals.
fn attribute_memory<'a>(
name: String,
attribution_provider: fattribution::ProviderProxy,
introspector: &'a fcomponent::IntrospectorProxy,
) -> BoxFuture<'a, Node> {
async move {
// Otherwise, check if there are attribution information.
let attributions = attribution_provider
.get()
.await
.unwrap_or_else(|e| panic!("Failed to get AttributionResponse for {name}: {e}"))
.unwrap_or_else(|e| panic!("Failed call to AttributionResponse for {name}: {e:?}"))
.attributions
.unwrap_or_else(|| panic!("Failed memory attribution for {name}"));
let mut node = Node::new(name);
// If there are children, resources assigned to this node by its parent
// will be re-assigned to children if applicable.
let mut children = HashMap::<String, Node>::new();
for attribution in attributions {
// Recursively attribute memory in this child principal.
match attribution {
fattribution::AttributionUpdate::Add(new_principal) => {
let identifier =
get_identifier_string(new_principal.identifier, &node.name, introspector)
.await;
let child = if let Some(client) = new_principal.detailed_attribution {
attribute_memory(identifier, client.into_proxy().unwrap(), introspector)
.await
} else {
Node::new(identifier)
};
children.insert(child.name.clone(), child);
}
fattribution::AttributionUpdate::Update(updated_principal) => {
let identifier = get_identifier_string(
updated_principal.identifier,
&node.name,
introspector,
)
.await;
let child = children.get_mut(&identifier).unwrap();
match updated_principal.resources.unwrap() {
fattribution::Resources::Data(d) => {
child.resources = d
.into_iter()
.filter_map(|r| {
if let fattribution::Resource::KernelObject(koid) = r {
Some(koid)
} else {
None
}
})
.collect();
}
_ => todo!("unimplemented"),
};
}
fattribution::AttributionUpdate::Remove(_) => todo!(),
_ => panic!("Unimplemented"),
};
}
node.children.extend(children.into_values());
node
}
.boxed()
}
async fn get_identifier_string(
identifier: Option<fattribution::Identifier>,
name: &String,
introspector: &fcomponent::IntrospectorProxy,
) -> String {
match identifier.unwrap() {
fattribution::Identifier::Self_(_) => todo!("self attribution not supported"),
fattribution::Identifier::Component(c) => introspector
.get_moniker(c)
.await
.expect("Inspector call failed")
.expect("Inspector::GetMoniker call failed"),
fattribution::Identifier::Part(sc) => format!("{}/{}", name, sc).to_owned(),
_ => todo!(),
}
}
#[derive(Debug)]
struct Node {
name: String,
resources: Vec<u64>,
children: Vec<Node>,
}
impl Node {
pub fn new(identifier: String) -> Node {
Node { name: identifier, resources: vec![], children: vec![] }
}
}
#[fuchsia::test]
async fn test_attribute_memory() {
// Starts a component manager and obtain its root job, so that we can simulate
// traversing the root job of the system, the kind done in `memory_monitor`.
let builder = RealmBuilder::with_params(
RealmBuilderParams::new().from_relative_url("#meta/test_realm.cm"),
)
.await
.expect("Failed to create test realm builder");
// Add a child to receive these capabilities so that we can use them in this test.
// - fuchsia.memory.attribution.Provider
// - fuchsia.kernel.RootJobForInspect
// - fuchsia.component.Realm
struct Capabilities {
attribution_provider: fattribution::ProviderProxy,
introspector: fcomponent::IntrospectorProxy,
realm: fcomponent::RealmProxy,
}
let (capabilities_sender, capabilities_receiver) = async_channel::unbounded::<Capabilities>();
let capabilities_sender = Arc::new(capabilities_sender);
let receiver = builder
.add_local_child(
"receiver",
move |handles| {
let capabilities_sender = capabilities_sender.clone();
async move {
capabilities_sender
.send(Capabilities {
attribution_provider: handles
.connect_to_protocol::<fattribution::ProviderMarker>()
.unwrap(),
introspector: handles
.connect_to_protocol::<fcomponent::IntrospectorMarker>()
.unwrap(),
realm: handles
.connect_to_protocol::<fcomponent::RealmMarker>()
.unwrap(),
})
.await
.unwrap();
// TODO(https://fxbug.dev/303919602): Until the component framework reliably
// drains capability requests when a component is stopped, we need to
// keep running the component.
std::future::pending().await
}
.boxed()
},
ChildOptions::new().eager(),
)
.await
.expect("Failed to add child");
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.memory.attribution.Provider"))
.from(Ref::child("elf_runner"))
.to(&receiver),
)
.await
.unwrap();
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.component.Realm"))
.from(Ref::framework())
.to(&receiver),
)
.await
.unwrap();
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.component.Introspector"))
.from(Ref::framework())
.to(&receiver),
)
.await
.unwrap();
let _realm =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let capabilities = capabilities_receiver.recv().await.unwrap();
// Start a colocated component.
let collection = fdecl::CollectionRef { name: "collection".to_string() };
let decl = fdecl::Child {
name: Some("colocated-component".to_string()),
url: Some("#meta/colocated-component.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
..Default::default()
};
let (user0, user0_peer) = zx::Channel::create();
let args = fcomponent::CreateChildArgs {
numbered_handles: Some(vec![HandleInfo {
handle: user0_peer.into(),
id: fuchsia_runtime::HandleInfo::new(HandleType::User0, 0).as_raw(),
}]),
..Default::default()
};
capabilities.realm.create_child(&collection, &decl, args).await.unwrap().unwrap();
let colocated_component_vmos =
fcolocated::ColocatedProxy::new(fasync::Channel::from_channel(user0))
.get_vmos()
.await
.unwrap();
assert!(!colocated_component_vmos.is_empty());
// Starting from the ELF runner, ask about the resource usage.
let elf_runner = attribute_memory(
"elf_runner.cm".to_owned(),
capabilities.attribution_provider,
&capabilities.introspector,
)
.await;
// We should get the following tree:
//
// - elf_runner.cm
// - colocated_runner.cm
// - colocated_component-64mb.cm
// - Some VMO
// - overhead
// - overhead
eprintln!("{:?}", elf_runner);
assert_eq!(elf_runner.children.len(), 1);
assert!(elf_runner.children[0].name.contains("colocated-runner"));
assert_eq!(elf_runner.children[0].children.len(), 1usize);
// Name of the colocated component
assert_eq!(&elf_runner.children[0].children[0].name, "collection:colocated-component");
let resource = &elf_runner.children[0].children[0].resources;
for vmo_koid in colocated_component_vmos {
assert!(resource.contains(&vmo_koid));
}
}