blob: 6c8507faf776a87d0329278dad755676e0300c42 [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 constants;
mod fetcher;
mod file_handler;
mod inspect_server;
mod persist_server;
mod scheduler;
use {
anyhow::{bail, Error},
argh::FromArgs,
fetcher::Fetcher,
fuchsia_async::{self as fasync, TaskGroup},
fuchsia_component::server::{ServiceFs, ServiceObj},
fuchsia_inspect::{component, health::Reporter},
fuchsia_sync::Mutex,
fuchsia_zircon::{Duration, Time},
futures::{future::join, FutureExt, StreamExt},
persist_server::PersistServer,
persistence_component_config::Config as ComponentConfig,
persistence_config::Config,
scheduler::Scheduler,
std::sync::Arc,
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!(persistence_config::load_configuration_files(), "Error loading configs: {}")?;
let inspector = component::inspector();
let _inspect_server_task =
inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default());
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();
// Create the Inspect fetcher
let (fetch_requester, _fetcher_task) =
on_error!(Fetcher::new(&config), "Error initializing fetcher: {}")?;
let scheduler = Scheduler::new(fetch_requester, &config);
// Add a persistence fidl service for each service defined in the config files.
let _server_tasks = spawn_persist_services(config, &mut fs, scheduler);
fs.take_and_serve_directory_handle()?;
// 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.
#[must_use]
fn spawn_persist_services(
config: Config,
fs: &mut ServiceFs<ServiceObj<'static, ()>>,
scheduler: Scheduler,
) -> Arc<Mutex<TaskGroup>> {
let mut started_persist_services = 0;
// We want fault tolerance if only a subset of the service configs fail to initialize.
let task_holder = Arc::new(Mutex::new(TaskGroup::new()));
for (service_name, tags) in config {
info!("Launching persist service for {service_name}");
PersistServer::create(
service_name.clone(),
tags.keys().map(|k| k.clone()).collect(),
scheduler.clone(),
)
.launch_server(task_holder.clone(), fs);
started_persist_services += 1;
}
info!("Started {} persist services", started_persist_services);
task_holder
}