blob: 408c0bb0e1a9d2a28bf2224ba3239fbf64e34922 [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::{
identity::ComponentIdentity,
inspect::container::InspectArtifactsContainer,
lifecycle::container::LifecycleArtifactsContainer,
logs::{
budget::BudgetManager, container::LogsArtifactsContainer, multiplex::PinStream,
stats::LogStreamStats,
},
repository::MultiplexerBroker,
},
diagnostics_data::{self, LogsData},
fidl_fuchsia_diagnostics::{LogInterestSelector, StreamMode},
fuchsia_inspect_derive::WithInspect,
std::sync::Arc,
tracing::debug,
};
/// Holds all diagnostics data artifacts for a given component in the topology.
///
/// While most members are public for convenience, other data types may be added in the future and
/// so this is marked with `#[non_exhaustive]`.
///
/// # Lifecycle
///
/// Each instance is held by the DataRepo as long as the corresponding component is running or this
/// struct has `logs` that are non-empty.
///
/// When we receive a stop event for the corresponding component, we set `inspect` and `lifecycle`
/// to `None`, causing `ArchiveAccessor` to ignore this component until it restarts. If `logs` is
/// empty at that point, this struct should be removed from the DataRepo.
///
/// See [`ComponentDiagnostics::mark_stopped`], [`ComponentDiagnostics::mark_started`], and
/// [`ComponentDiagnostics::should_retain`] for the implementations of this behavior.
#[non_exhaustive]
pub struct ComponentDiagnostics {
/// Container holding the artifacts needed to uniquely identify
/// a component on the system.
pub identity: Arc<ComponentIdentity>,
/// Container holding the artifacts needed to serve inspect data.
/// If absent, this is interpereted as a component existing, but not
/// hosting diagnostics data.
pub inspect: Option<InspectArtifactsContainer>,
/// Container holding the artifacts needed to serve lifecycle data.
pub lifecycle: Option<LifecycleArtifactsContainer>,
/// Container holding cached log messages and interest dispatchers.
pub logs: Option<Arc<LogsArtifactsContainer>>,
/// Holds the state for `root/sources/MONIKER/*` in Archivist's inspect.
pub source_node: fuchsia_inspect::Node,
/// Whether the component this represents is still running or not. Components which have
/// stopped have their containers retained for as long as they still have log messages we want.
is_live: bool,
}
impl ComponentDiagnostics {
pub fn empty(identity: Arc<ComponentIdentity>, parent: &fuchsia_inspect::Node) -> Self {
let source_node = parent.create_child(identity.relative_moniker.join("/"));
source_node.record_string("url", &identity.url);
Self { identity, inspect: None, lifecycle: None, logs: None, source_node, is_live: true }
}
pub fn new_with_lifecycle(
identity: Arc<ComponentIdentity>,
lifecycle: LifecycleArtifactsContainer,
parent: &fuchsia_inspect::Node,
) -> Self {
let mut new = Self::empty(identity, parent);
new.lifecycle = Some(lifecycle);
new
}
pub fn new_with_inspect(
identity: Arc<ComponentIdentity>,
inspect: InspectArtifactsContainer,
parent: &fuchsia_inspect::Node,
) -> Self {
let mut new = Self::empty(identity, parent);
new.inspect = Some(inspect);
new
}
pub fn logs(
&mut self,
budget: &BudgetManager,
interest_selectors: &[LogInterestSelector],
multiplexers: &mut MultiplexerBroker,
) -> Arc<LogsArtifactsContainer> {
if let Some(logs) = &self.logs {
logs.clone()
} else {
let stats = LogStreamStats::default()
.with_inspect(&self.source_node, "logs")
.expect("failed to attach component log stats");
let container = Arc::new(LogsArtifactsContainer::new(
self.identity.clone(),
interest_selectors,
stats,
budget.handle(),
));
budget.add_container(container.clone());
self.logs = Some(container.clone());
multiplexers.send(&container);
container
}
}
/// Return a cursor over messages from this component with the given `mode`.
pub fn logs_cursor(&self, mode: StreamMode) -> Option<PinStream<Arc<LogsData>>> {
self.logs.as_ref().map(|l| l.cursor(mode))
}
/// Ensure this container is marked as live even if the component it represents had previously
/// stopped.
pub fn mark_started(&mut self) {
debug!(%self.identity, "Marking component as started.");
self.is_live = true;
if let Some(logs) = &self.logs {
logs.mark_started();
}
}
/// Mark this container as stopped -- the component is no longer running and we should not
/// serve lifecycle or inspect results.
///
/// Sets `inspect` and `lifecycle` to `None` so that this component will be excluded from
/// accessors for those data types.
pub fn mark_stopped(&mut self) {
debug!(%self.identity, "Marking stopped.");
self.inspect = None;
self.lifecycle = None;
self.is_live = false;
if let Some(logs) = &self.logs {
logs.mark_stopped();
}
}
/// Returns `true` if the DataRepo should continue holding this container. This container should
/// be retained as long as we believe the corresponding component is still running or as long as
/// we still have logs from its execution.
pub fn should_retain(&self) -> bool {
let should_retain_logs = self.logs.as_ref().map(|l| l.should_retain()).unwrap_or(false);
self.is_live || should_retain_logs
}
/// Ensure that no new log messages can be consumed from the corresponding component, causing
/// all pending subscriptions to terminate. Used during Archivist shutdown to ensure a full
/// flush of available logs.
pub fn terminate_logs(&self) {
if let Some(logs) = &self.logs {
logs.terminate();
}
}
}