blob: 2fccf964f5cf6223a8b42cacecde4dad1b574e61 [file] [log] [blame]
// Copyright 2020 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.
use {
crate::inspect::container::InspectHandle,
diagnostics_data::InspectHandleName,
fidl::endpoints::{DiscoverableProtocolMarker, Proxy},
fidl_fuchsia_inspect::{TreeMarker, TreeProxy},
fidl_fuchsia_inspect_deprecated::{InspectMarker, InspectProxy},
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
futures::stream::StreamExt,
std::{collections::HashMap, pin::pin},
tracing::error,
};
/// Mapping from a diagnostics filename to the underlying encoding of that
/// diagnostics data.
pub type DataMap = HashMap<Option<InspectHandleName>, InspectData>;
/// Data associated with a component.
/// This data is stored by data collectors and passed by the collectors to processors.
#[derive(Debug)]
pub enum InspectData {
/// A VMO containing data associated with the event.
Vmo(zx::Vmo),
/// A file containing data associated with the event.
///
/// Because we can't synchronously retrieve file contents like we can for VMOs, this holds
/// the full file contents. Future changes should make streaming ingestion feasible.
File(Vec<u8>),
/// A connection to a Tree service.
Tree(TreeProxy),
/// A connection to the deprecated Inspect service.
DeprecatedFidl(InspectProxy),
}
fn maybe_load_service<P: DiscoverableProtocolMarker>(
dir_proxy: &fio::DirectoryProxy,
entry: &fuchsia_fs::directory::DirEntry,
) -> Result<Option<P::Proxy>, anyhow::Error> {
if entry.name.ends_with(P::PROTOCOL_NAME) {
let (proxy, server) = fidl::endpoints::create_proxy::<P>()?;
fdio::service_connect_at(
dir_proxy.as_channel().as_ref(),
&entry.name,
server.into_channel(),
)?;
return Ok(Some(proxy));
}
Ok(None)
}
pub async fn populate_data_map(inspect_handles: &[InspectHandle]) -> DataMap {
let mut data_map = DataMap::new();
for inspect_handle in inspect_handles {
match inspect_handle {
InspectHandle::Directory(ref dir) => return populate_data_map_from_dir(dir).await,
InspectHandle::Tree(proxy, ref name) => {
data_map.insert(name.clone(), InspectData::Tree(proxy.clone()));
}
}
}
data_map
}
/// Searches the directory specified by inspect_directory_proxy for
/// .inspect files and populates the `inspect_data_map` with the found VMOs.
async fn populate_data_map_from_dir(inspect_proxy: &fio::DirectoryProxy) -> DataMap {
// TODO(https://fxbug.dev/42112326): Use a streaming and bounded readdir API when available to avoid
// being hung.
let mut entries =
pin!(fuchsia_fs::directory::readdir_recursive(inspect_proxy, /* timeout= */ None)
.filter_map(|result| {
async move {
// TODO(https://fxbug.dev/42126094): decide how to show directories that we failed to read.
result.ok()
}
}));
let mut data_map = DataMap::new();
// TODO(https://fxbug.dev/42138410) convert this async loop to a stream so we can carry backpressure
while let Some(entry) = entries.next().await {
// We are only currently interested in inspect VMO files (root.inspect) and
// inspect services.
if let Ok(Some(proxy)) = maybe_load_service::<TreeMarker>(inspect_proxy, &entry) {
data_map
.insert(Some(InspectHandleName::filename(entry.name)), InspectData::Tree(proxy));
continue;
}
if let Ok(Some(proxy)) = maybe_load_service::<InspectMarker>(inspect_proxy, &entry) {
data_map.insert(
Some(InspectHandleName::filename(entry.name)),
InspectData::DeprecatedFidl(proxy),
);
continue;
}
if !entry.name.ends_with(".inspect")
|| entry.kind != fuchsia_fs::directory::DirentKind::File
{
continue;
}
let file_proxy = match fuchsia_fs::directory::open_file_no_describe(
inspect_proxy,
&entry.name,
fuchsia_fs::OpenFlags::RIGHT_READABLE,
) {
Ok(proxy) => proxy,
Err(_) => {
// It should be ok to not be able to read a file. The file might be closed by the
// time we get here.
continue;
}
};
// Obtain the backing vmo.
let vmo = match file_proxy.get_backing_memory(fio::VmoFlags::READ).await {
Ok(vmo) => vmo,
Err(_) => {
// It should be ok to not be able to read a file. The file might be closed by the
// time we get here.
continue;
}
};
let data = match vmo.map_err(zx::Status::from_raw) {
Ok(vmo) => InspectData::Vmo(vmo),
Err(err) => {
match err {
zx::Status::NOT_SUPPORTED => {}
err => {
error!(
file = %entry.name, ?err,
"unexpected error from GetBackingMemory",
)
}
}
match fuchsia_fs::file::read(&file_proxy).await {
Ok(contents) => InspectData::File(contents),
Err(_) => {
// It should be ok to not be able to read a file. The file might be closed
// by the time we get here.
continue;
}
}
}
};
data_map.insert(Some(InspectHandleName::filename(entry.name)), data);
}
data_map
}
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
diagnostics_assertions::assert_data_tree,
fidl::endpoints::create_request_stream,
fuchsia_async as fasync,
fuchsia_component::server::ServiceFs,
fuchsia_inspect::{reader, Inspector},
fuchsia_zircon as zx,
fuchsia_zircon::Peered,
inspect_runtime::{service::spawn_tree_server_with_stream, TreeServerSendPreference},
};
fn get_vmo(text: &[u8]) -> zx::Vmo {
let vmo = zx::Vmo::create(4096).unwrap();
vmo.write(text, 0).unwrap();
vmo
}
#[fuchsia::test]
async fn populate_data_map_with_trees() {
let insp1 = Inspector::default();
let insp2 = Inspector::default();
let insp3 = Inspector::default();
insp1.root().record_int("one", 1);
insp2.root().record_int("two", 2);
insp3.root().record_int("three", 3);
let (tree1, request_stream) = create_request_stream::<TreeMarker>().unwrap();
spawn_tree_server_with_stream(insp1, TreeServerSendPreference::default(), request_stream)
.detach();
let (tree2, request_stream) = create_request_stream::<TreeMarker>().unwrap();
spawn_tree_server_with_stream(insp2, TreeServerSendPreference::default(), request_stream)
.detach();
let (tree3, request_stream) = create_request_stream::<TreeMarker>().unwrap();
spawn_tree_server_with_stream(insp3, TreeServerSendPreference::default(), request_stream)
.detach();
let name1 = Some(InspectHandleName::name("tree1"));
let name2 = Some(InspectHandleName::name("tree2"));
let name3 = None;
let data = populate_data_map(&[
InspectHandle::Tree(tree1.into_proxy().unwrap(), name1.clone()),
InspectHandle::Tree(tree2.into_proxy().unwrap(), name2.clone()),
InspectHandle::Tree(tree3.into_proxy().unwrap(), name3.clone()),
])
.await;
assert_eq!(data.len(), 3);
assert_matches!(data.get(&name1), Some(InspectData::Tree(t)) => {
let h = reader::read(t).await.unwrap();
assert_data_tree!(h, root: {
one: 1i64,
});
});
assert_matches!(data.get(&name2), Some(InspectData::Tree(t)) => {
let h = reader::read(t).await.unwrap();
assert_data_tree!(h, root: {
two: 2i64,
});
});
assert_matches!(data.get(&name3), Some(InspectData::Tree(t)) => {
let h = reader::read(t).await.unwrap();
assert_data_tree!(h, root: {
three: 3i64,
});
});
}
#[fuchsia::test]
async fn inspect_data_collector() {
let path = "/test-bindings/out";
// Make a ServiceFs containing two files.
// One is an inspect file, and one is not.
let mut fs = ServiceFs::new();
let vmo = get_vmo(b"test1");
let vmo2 = get_vmo(b"test2");
let vmo3 = get_vmo(b"test3");
let vmo4 = get_vmo(b"test4");
fs.dir("diagnostics").add_vmo_file_at("root.inspect", vmo);
fs.dir("diagnostics").add_vmo_file_at("root_not_inspect", vmo2);
fs.dir("diagnostics").dir("a").add_vmo_file_at("root.inspect", vmo3);
fs.dir("diagnostics").dir("b").add_vmo_file_at("root.inspect", vmo4);
// Create a connection to the ServiceFs.
let (h0, h1) = fidl::endpoints::create_endpoints();
fs.serve_connection(h1).unwrap();
let ns = fdio::Namespace::installed().unwrap();
ns.bind(path, h0).unwrap();
fasync::Task::spawn(fs.collect()).detach();
let (done0, done1) = zx::Channel::create();
// Run the actual test in a separate thread so that it does not block on FS operations.
// Use signalling on a zx::Channel to indicate that the test is done.
std::thread::spawn(move || {
let done = done1;
let mut executor = fasync::LocalExecutor::new();
executor.run_singlethreaded(async {
let extra_data = collect(&format!("{path}/diagnostics")).await;
assert_eq!(3, extra_data.len());
let assert_extra_data = |path: &str, content: &[u8]| {
let extra = extra_data.get(&Some(InspectHandleName::filename(path)));
assert!(extra.is_some());
match extra.unwrap() {
InspectData::Vmo(vmo) => {
let mut buf = [0u8; 5];
vmo.read(&mut buf, 0).expect("reading vmo");
assert_eq!(content, &buf);
}
v => {
panic!("Expected Vmo, got {v:?}");
}
}
};
assert_extra_data("root.inspect", b"test1");
assert_extra_data("a/root.inspect", b"test3");
assert_extra_data("b/root.inspect", b"test4");
done.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).expect("signalling peer");
});
});
fasync::OnSignals::new(&done0, zx::Signals::USER_0).await.unwrap();
ns.unbind(path).unwrap();
}
async fn collect(path: &str) -> DataMap {
let inspect_proxy =
fuchsia_fs::directory::open_in_namespace(path, fuchsia_fs::OpenFlags::RIGHT_READABLE)
.expect("Failed to open directory");
populate_data_map(&[inspect_proxy.into()]).await
}
}