| // Copyright 2021 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. |
| |
| //! # Inspect Runtime |
| //! |
| //! This library contains the necessary functions to serve inspect from a component. |
| |
| use fidl::endpoints::DiscoverableProtocolMarker; |
| use fidl_fuchsia_inspect::TreeMarker; |
| use fidl_fuchsia_io as fio; |
| use fuchsia_component::server::{ServiceFs, ServiceObjTrait}; |
| use fuchsia_inspect::{Error, Inspector}; |
| use futures::prelude::*; |
| use std::sync::Arc; |
| use tracing::warn; |
| use vfs::{ |
| directory::entry::DirectoryEntry, execution_scope::ExecutionScope, path::Path, pseudo_directory, |
| }; |
| |
| pub mod service; |
| |
| /// Directory within the outgoing directory of a component where the diagnostics service should be |
| /// added. |
| pub const DIAGNOSTICS_DIR: &str = "diagnostics"; |
| |
| /// Spawns a server with options for handling `fuchsia.inspect.Tree` requests in |
| /// the outgoing diagnostics directory. |
| pub fn serve_with_options<'a, ServiceObjTy: ServiceObjTrait>( |
| inspector: &Inspector, |
| options: service::TreeServerSettings, |
| service_fs: &mut ServiceFs<ServiceObjTy>, |
| ) -> Result<(), Error> { |
| let (proxy, server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>() |
| .map_err(|e| Error::fidl(e.into()))?; |
| let dir = create_diagnostics_dir_with_options(inspector.clone(), options); |
| let server_end = server.into_channel().into(); |
| let scope = ExecutionScope::new(); |
| dir.open( |
| scope, |
| fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE, |
| 0, |
| Path::dot(), |
| server_end, |
| ); |
| service_fs.add_remote(DIAGNOSTICS_DIR, proxy); |
| |
| Ok(()) |
| } |
| |
| /// Spawns a server for handling `fuchsia.inspect.Tree` requests in the outgoing diagnostics |
| /// directory. |
| pub fn serve<'a, ServiceObjTy: ServiceObjTrait>( |
| inspector: &Inspector, |
| service_fs: &mut ServiceFs<ServiceObjTy>, |
| ) -> Result<(), Error> { |
| serve_with_options(inspector, service::TreeServerSettings::default(), service_fs) |
| } |
| |
| /// Creates the outgoing diagnostics directory with options. Should be added to the component's |
| /// outgoing directory at `DIAGNOSTICS_DIR`. Use `serve_with_options` if the component's outgoing |
| /// directory is served by `ServiceFs`. |
| pub fn create_diagnostics_dir_with_options( |
| inspector: fuchsia_inspect::Inspector, |
| options: service::TreeServerSettings, |
| ) -> Arc<dyn DirectoryEntry> { |
| pseudo_directory! { |
| TreeMarker::PROTOCOL_NAME => |
| vfs::service::host(move |stream| { |
| service::handle_request_stream( |
| inspector.clone(), |
| options.clone(), |
| stream |
| ).unwrap_or_else(|e| { |
| warn!( |
| "error handling fuchsia.inspect/Tree connection: {e:#}" |
| ); |
| }) |
| }), |
| } |
| } |
| |
| /// Creates the outgoing diagnostics directory. Should be added to the component's outgoing |
| /// directory at `DIAGNOSTICS_DIR`. Use `serve` if the component's outgoing directory is served by |
| /// `ServiceFs`. |
| pub fn create_diagnostics_dir(inspector: Inspector) -> Arc<dyn DirectoryEntry> { |
| create_diagnostics_dir_with_options(inspector, service::TreeServerSettings::default()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use component_events::{ |
| events::{Event, EventSource, EventSubscription, Started}, |
| matcher::EventMatcher, |
| }; |
| use fdio; |
| use fuchsia_component::server::ServiceObj; |
| use fuchsia_component_test::ScopedInstance; |
| use fuchsia_inspect::{reader, testing::assert_data_tree, Inspector}; |
| |
| const TEST_COMPONENT_URL: &str = |
| "fuchsia-pkg://fuchsia.com/inspect-runtime-tests#meta/inspect_test_component.cm"; |
| |
| #[fuchsia::test] |
| async fn new_no_op() -> Result<(), Error> { |
| let mut fs: ServiceFs<ServiceObj<'_, ()>> = ServiceFs::new(); |
| |
| let inspector = Inspector::new_no_op(); |
| assert!(!inspector.is_valid()); |
| |
| // Ensure serve doesn't crash on a No-Op inspector |
| serve(&inspector, &mut fs) |
| } |
| |
| #[allow(clippy::approx_constant)] // TODO(fxbug.dev/95023) |
| #[fuchsia::test] |
| async fn connect_to_service() -> Result<(), anyhow::Error> { |
| // TODO(https://fxbug.dev/81400): Change code below to |
| // app.start_with_binder_sync during post-migration cleanup. |
| let event_source = EventSource::new().expect("failed to create EventSource"); |
| let mut event_stream = event_source |
| .subscribe(vec![EventSubscription::new(vec![Started::NAME])]) |
| .await |
| .expect("failed to subscribe to EventSource"); |
| |
| let app = ScopedInstance::new("coll".to_string(), TEST_COMPONENT_URL.to_string()) |
| .await |
| .expect("failed to create test component"); |
| |
| app.connect_to_binder().expect("failed to connect to Binder protocol"); |
| |
| let _ = EventMatcher::ok() |
| .moniker_regex(app.child_name().to_owned()) |
| .wait::<Started>(&mut event_stream) |
| .await |
| .expect("failed to observe Started event"); |
| |
| let path = format!( |
| "/hub/children/coll:{}/exec/out/diagnostics/{}", |
| app.child_name(), |
| TreeMarker::PROTOCOL_NAME |
| ); |
| let (tree, server_end) = |
| fidl::endpoints::create_proxy::<TreeMarker>().expect("failed to create proxy"); |
| fdio::service_connect(&path, server_end.into_channel()) |
| .expect("failed to connect to service"); |
| |
| let hierarchy = reader::read(&tree).await?; |
| assert_data_tree!(hierarchy, root: { |
| int: 3i64, |
| "lazy-node": { |
| a: "test", |
| child: { |
| double: 3.14, |
| }, |
| } |
| }); |
| |
| Ok(()) |
| } |
| } |