blob: b660375a833721fcf359f83acfd74676898bfaa6 [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.
//! `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 {
anyhow::{bail, Error},
fuchsia_async as fasync,
fuchsia_component::server::{ServiceFs, ServiceObj},
fuchsia_inspect::{component, health::Reporter},
fuchsia_zircon::{Duration, Time},
futures::{future::join, FutureExt, StreamExt},
persistence_component_config::Config as ComponentConfig,
/// 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();
let startup_delay_duration = Duration::from_seconds(component_config.startup_delay_seconds);
info!("Rotating directories");
let mut fs = ServiceFs::new();
// Add a persistence fidl service for each service defined in the config files.
spawn_persist_services(config, &mut fs);
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.
"Diagnostics Persistence Service delaying startup for {} 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| {
"Serving persisted data experienced critical failure. No data available: {:?}",
info!("Diagnostics Persistence Service ready");
inspector.root().record_int(PUBLISHED_TIME_KEY, Time::get_monotonic().into_nanos());
join(fs.collect::<()>(), publish_fut).await;
// 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) {
"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);