| // 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. |
| |
| //! `diagnostics-persistence` component persists Inspect VMOs and serves them at the next boot. |
| |
| mod config; |
| mod constants; |
| mod file_handler; |
| mod inspect_server; |
| mod persist_server; |
| |
| use { |
| crate::config::Config, |
| anyhow::{bail, Error}, |
| argh::FromArgs, |
| fuchsia_async as fasync, |
| fuchsia_component::server::{ServiceFs, ServiceObj}, |
| fuchsia_inspect::{component, health::Reporter}, |
| fuchsia_zircon::{Duration, Time}, |
| futures::{future::join, FutureExt, StreamExt}, |
| persist_server::PersistServer, |
| persistence_component_config::Config as ComponentConfig, |
| tracing::*, |
| }; |
| |
| /// The name of the subcommand and the logs-tag. |
| pub const PROGRAM_NAME: &str = "persistence"; |
| pub const PERSIST_NODE_NAME: &str = "persist"; |
| /// Added after persisted data is fully published |
| pub const PUBLISHED_TIME_KEY: &str = "published"; |
| |
| /// Command line args |
| #[derive(FromArgs, Debug, PartialEq)] |
| #[argh(subcommand, name = "persistence")] |
| pub struct CommandLine {} |
| |
| // on_error logs any errors from `value` and then returns a Result. |
| // value must return a Result; error_message must contain one {} to put the error in. |
| macro_rules! on_error { |
| ($value:expr, $error_message:expr) => { |
| $value.or_else(|e| { |
| let message = format!($error_message, e); |
| warn!("{}", message); |
| bail!("{}", message) |
| }) |
| }; |
| } |
| |
| pub async fn main(_args: CommandLine) -> Result<(), Error> { |
| info!("Starting Diagnostics Persistence Service service"); |
| let config = on_error!(config::load_configuration_files(), "Error loading configs: {}")?; |
| let inspector = component::inspector(); |
| let component_config = ComponentConfig::take_from_startup_handle(); |
| component_config.record_inspect(inspector.root()); |
| let startup_delay_duration = Duration::from_seconds(component_config.startup_delay_seconds); |
| |
| info!("Rotating directories"); |
| file_handler::shuffle_at_boot(); |
| |
| let mut fs = ServiceFs::new(); |
| |
| component::health().set_starting_up(); |
| |
| // Add a persistence fidl service for each service defined in the config files. |
| spawn_persist_services(config, &mut fs); |
| fs.take_and_serve_directory_handle()?; |
| on_error!(inspect_runtime::serve(&inspector, &mut fs), "Error initializing Inspect: {}")?; |
| |
| // Before serving previous data, wait the arg-provided seconds for the /cache directory to |
| // stabilize. Note: We're already accepting persist requess. If we receive a request, store |
| // some data, and then cache is cleared after data is persisted, that data will be lost. This |
| // is correct behavior - we don't want to remember anything from before the cache was cleared. |
| info!( |
| "Diagnostics Persistence Service delaying startup for {} seconds...", |
| component_config.startup_delay_seconds |
| ); |
| let publish_fut = fasync::Timer::new(fasync::Time::after(startup_delay_duration)).then(|_| { |
| async move { |
| // Start serving previous boot data |
| info!("...done delay, publishing previous boot data"); |
| inspector.root().record_child(PERSIST_NODE_NAME, |node| { |
| inspect_server::serve_persisted_data(node).unwrap_or_else(|e| { |
| error!( |
| "Serving persisted data experienced critical failure. No data available: {:?}", |
| e |
| ) |
| }); |
| component::health().set_ok(); |
| info!("Diagnostics Persistence Service ready"); |
| }); |
| inspector.root().record_int(PUBLISHED_TIME_KEY, Time::get_monotonic().into_nanos()); |
| } |
| }); |
| |
| join(fs.collect::<()>(), publish_fut).await; |
| Ok(()) |
| } |
| |
| // Takes a config and adds all the persist services defined in those configs to the servicefs of |
| // the component. |
| fn spawn_persist_services(config: Config, fs: &mut ServiceFs<ServiceObj<'static, ()>>) { |
| let mut started_persist_services = 0; |
| // We want fault tolerance if only a subset of the service configs fail to initialize. |
| for (service_name, tags) in config.into_iter() { |
| info!("Launching persist service for {}, tags {:?}", service_name, tags); |
| match PersistServer::create(service_name.clone(), tags) { |
| Ok(persist_service) => { |
| if let Err(e) = persist_service.launch_server(fs) { |
| warn!( |
| "Encountered error launching persist service for service name: {}, Error: {:?}", |
| service_name, e |
| ); |
| } else { |
| started_persist_services += 1; |
| } |
| } |
| Err(e) => warn!( |
| "Encountered error instantiating persist service for service name: {}, Error: {:?}", |
| service_name, e |
| ), |
| } |
| } |
| |
| info!("Started {} persist services", started_persist_services); |
| } |