blob: acfbb9e8c443727f8418213736a0f0e2f2e49c48 [file] [log] [blame]
// Copyright 2019 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::{
container::{
DiagnosticsArtifactsContainer, InspectArtifactsContainer, LifecycleArtifactsContainer,
LifecycleDataContainer, UnpopulatedInspectDataContainer,
},
events::types::ComponentIdentifier,
logs::LogManager,
},
anyhow::{format_err, Error},
fidl_fuchsia_diagnostics::{self, Selector},
fidl_fuchsia_io::{DirectoryProxy, CLONE_FLAG_SAME_RIGHTS},
fuchsia_inspect_node_hierarchy::trie,
fuchsia_zircon as zx, io_util, selectors,
std::convert::TryInto,
std::sync::Arc,
};
pub type DiagnosticsDataTrie = trie::Trie<String, DiagnosticsArtifactsContainer>;
/// DiagnosticsDataRepository manages storage of all state needed in order
/// for the inspect reader to retrieve inspect data when a read is requested.
pub struct DiagnosticsDataRepository {
pub data_directories: DiagnosticsDataTrie,
log_manager: LogManager,
/// Optional static selectors. For the all_access reader, there
/// are no provided selectors. For all other pipelines, a non-empty
/// vector is required.
pub static_selectors: Option<Vec<Arc<Selector>>>,
}
impl DiagnosticsDataRepository {
pub fn new(log_manager: LogManager, static_selectors: Option<Vec<Arc<Selector>>>) -> Self {
DiagnosticsDataRepository {
log_manager,
data_directories: DiagnosticsDataTrie::new(),
static_selectors: static_selectors,
}
}
pub fn log_manager(&self) -> LogManager {
self.log_manager.clone()
}
pub fn remove(&mut self, component_id: &ComponentIdentifier) {
self.data_directories.remove(component_id.unique_key());
}
pub fn add_new_component(
&mut self,
identifier: ComponentIdentifier,
component_url: impl Into<String>,
event_timestamp: zx::Time,
component_start_time: Option<zx::Time>,
) -> Result<(), Error> {
let relative_moniker = identifier.relative_moniker_for_selectors();
let lifecycle_artifact_container = LifecycleArtifactsContainer {
event_timestamp: event_timestamp,
component_start_time: component_start_time,
};
let key = identifier.unique_key();
let diag_repo_entry_opt = self.data_directories.get_mut(key.clone());
match diag_repo_entry_opt {
Some(diag_repo_entry) => {
let diag_repo_entry_values: &mut [DiagnosticsArtifactsContainer] =
diag_repo_entry.get_values_mut();
match &mut diag_repo_entry_values[..] {
[] => {
// An entry with no values implies that the somehow we observed the
// creation of a component lower in the topology before observing this
// one. If this is the case, just instantiate as though it's our first
// time encountering this moniker segment.
self.data_directories.insert(
key,
DiagnosticsArtifactsContainer {
relative_moniker: relative_moniker,
component_url: component_url.into(),
lifecycle_artifacts_container: Some(lifecycle_artifact_container),
inspect_artifacts_container: None,
},
)
}
[existing_diagnostics_artifact_container] => {
// Races may occur between seeing diagnostics ready and seeing
// creation lifecycle events. Handle this here.
// TODO(fxbug.dev/52047): Remove once caching handles ordering issues.
if existing_diagnostics_artifact_container
.lifecycle_artifacts_container
.is_none()
{
existing_diagnostics_artifact_container.lifecycle_artifacts_container =
Some(lifecycle_artifact_container);
}
}
_ => {
return Err(format_err!(
concat!(
"Encountered a diagnostics data repository node with more",
"than one artifact container, moniker: {:?}."
),
key
));
}
}
}
// This case is expected to be the most common case. We've seen a creation
// lifecycle event and it promotes the instantiation of a new data repository entry.
None => self.data_directories.insert(
key,
DiagnosticsArtifactsContainer {
relative_moniker: relative_moniker,
component_url: component_url.into(),
lifecycle_artifacts_container: Some(lifecycle_artifact_container),
inspect_artifacts_container: None,
},
),
}
Ok(())
}
pub fn add_inspect_artifacts(
&mut self,
identifier: ComponentIdentifier,
component_url: impl Into<String>,
directory_proxy: DirectoryProxy,
event_timestamp: zx::Time,
) -> Result<(), Error> {
let relative_moniker = identifier.relative_moniker_for_selectors();
let key = identifier.unique_key();
// Create an optional inspect artifact container. If the option is None, this implies
// that there existed static selectors, and none of them matched the relative moniker
// of the component being inserted. So we can abort insertion.
let inspect_artifact_container = match &self.static_selectors {
Some(selectors) => {
let matched_selectors = selectors::match_component_moniker_against_selectors(
&relative_moniker,
&selectors,
)?;
match &matched_selectors[..] {
[] => None,
populated_vec => Some(InspectArtifactsContainer {
component_diagnostics_proxy: Arc::new(directory_proxy),
inspect_matcher: Some((populated_vec).try_into()?),
event_timestamp,
}),
}
}
None => Some(InspectArtifactsContainer {
component_diagnostics_proxy: Arc::new(directory_proxy),
inspect_matcher: None,
event_timestamp,
}),
};
match inspect_artifact_container {
Some(inspect_container) => self.insert_inspect_artifact_container(
inspect_container,
key,
relative_moniker,
component_url.into(),
),
// The Inspect artifact container being None here implies that
// there were valid static selectors and none of them applied to
// the component currently being processed.
None => {
return Ok(());
}
}
}
// Inserts an InspectArtifactsContainer into the data repository.
fn insert_inspect_artifact_container(
&mut self,
inspect_container: InspectArtifactsContainer,
key: Vec<String>,
relative_moniker: Vec<String>,
component_url: String,
) -> Result<(), Error> {
let diag_repo_entry_opt = self.data_directories.get_mut(key.clone());
match diag_repo_entry_opt {
Some(diag_repo_entry) => {
let diag_repo_entry_values: &mut [DiagnosticsArtifactsContainer] =
diag_repo_entry.get_values_mut();
match &mut diag_repo_entry_values[..] {
[] => {
// An entry with no values implies that the somehow we observed the
// creation of a component lower in the topology before observing this
// one. If this is the case, just instantiate as though it's our first
// time encountering this moniker segment.
self.data_directories.insert(
key,
DiagnosticsArtifactsContainer {
relative_moniker: relative_moniker,
component_url,
lifecycle_artifacts_container: None,
inspect_artifacts_container: Some(inspect_container),
},
)
}
[existing_diagnostics_artifact_container] => {
// Races may occur between synthesized and real diagnostics_ready
// events, so we must handle de-duplication here.
// TODO(fxbug.dev/52047): Remove once caching handles ordering issues.
if existing_diagnostics_artifact_container
.inspect_artifacts_container
.is_none()
{
// This is expected to be the most common case. We've encountered the
// diagnostics_ready event for a component that has already been
// observed to be started/existing. We now must update the diagnostics
// artifact container with the inspect artifacts that accompanied the
// diagnostics_ready event.
existing_diagnostics_artifact_container.inspect_artifacts_container =
Some(inspect_container);
}
}
_ => {
return Err(format_err!(
concat!(
"Encountered a diagnostics data repository node with more",
"than one artifact container, moniker: {:?}."
),
key
));
}
}
}
// This case is expected to be uncommon; we've encountered a diagnostics_ready
// event before a start or existing event!
None => self.data_directories.insert(
key,
DiagnosticsArtifactsContainer {
relative_moniker: relative_moniker,
component_url,
lifecycle_artifacts_container: None,
inspect_artifacts_container: Some(inspect_container),
},
),
}
Ok(())
}
pub fn fetch_lifecycle_event_data(&self) -> Vec<LifecycleDataContainer> {
self.data_directories.iter().fold(
Vec::new(),
|mut acc, (_, diagnostics_artifacts_container_opt)| {
match diagnostics_artifacts_container_opt {
None => acc,
Some(diagnostics_artifacts_container) => {
if let Some(lifecycle_artifacts) =
&diagnostics_artifacts_container.lifecycle_artifacts_container
{
acc.push(LifecycleDataContainer::from_lifecycle_artifact(
lifecycle_artifacts,
diagnostics_artifacts_container.relative_moniker.clone(),
diagnostics_artifacts_container.component_url.clone(),
));
}
if let Some(inspect_artifacts) =
&diagnostics_artifacts_container.inspect_artifacts_container
{
acc.push(LifecycleDataContainer::from_inspect_artifact(
inspect_artifacts,
diagnostics_artifacts_container.relative_moniker.clone(),
diagnostics_artifacts_container.component_url.clone(),
));
}
acc
}
}
},
)
}
/// Return all of the DirectoryProxies that contain Inspect hierarchies
/// which contain data that should be selected from.
pub fn fetch_inspect_data(
&self,
component_selectors: &Option<Vec<Arc<Selector>>>,
) -> Vec<UnpopulatedInspectDataContainer> {
return self
.data_directories
.iter()
.filter_map(|(_, diagnostics_artifacts_container_opt)| {
let (diagnostics_artifacts_container, inspect_artifacts) =
match &diagnostics_artifacts_container_opt {
Some(diagnostics_artifacts_container) => {
match &diagnostics_artifacts_container.inspect_artifacts_container {
Some(inspect_artifacts) => {
(diagnostics_artifacts_container, inspect_artifacts)
}
None => return None,
}
}
None => return None,
};
if !match component_selectors {
Some(component_selectors) => component_selectors.iter().any(|s| {
selectors::match_component_moniker_against_selector(
&diagnostics_artifacts_container.relative_moniker,
s,
)
.ok()
.unwrap_or(false)
}),
None => true,
} {
return None;
}
// This artifact contains inspect and matches a passed selector.
io_util::clone_directory(
&inspect_artifacts.component_diagnostics_proxy,
CLONE_FLAG_SAME_RIGHTS,
)
.ok()
.map(|directory| UnpopulatedInspectDataContainer {
relative_moniker: diagnostics_artifacts_container.relative_moniker.clone(),
component_url: diagnostics_artifacts_container.component_url.clone(),
component_diagnostics_proxy: directory,
inspect_matcher: inspect_artifacts.inspect_matcher.clone(),
})
})
.collect();
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::events::types::{ComponentIdentifier, LegacyIdentifier, RealmPath},
fidl_fuchsia_io::DirectoryMarker,
fuchsia_async as fasync,
fuchsia_inspect_node_hierarchy::trie::TrieIterableNode,
fuchsia_zircon as zx,
};
const TEST_URL: &'static str = "fuchsia-pkg://test";
#[fasync::run_singlethreaded(test)]
async fn inspect_repo_disallows_duplicated_dirs() {
let mut inspect_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
let (proxy, _) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("create directory proxy");
inspect_repo
.add_inspect_artifacts(component_id.clone(), TEST_URL, proxy, zx::Time::from_nanos(0))
.expect("add to repo");
let (proxy, _) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("create directory proxy");
inspect_repo
.add_inspect_artifacts(component_id.clone(), TEST_URL, proxy, zx::Time::from_nanos(0))
.expect("add to repo");
let key = component_id.unique_key();
assert_eq!(inspect_repo.data_directories.get(key).unwrap().get_values().len(), 1);
}
#[fasync::run_singlethreaded(test)]
async fn data_repo_updates_existing_entry_to_hold_inspect_data() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
data_repo
.add_new_component(component_id.clone(), TEST_URL, zx::Time::from_nanos(0), None)
.expect("instantiated new component.");
let (proxy, _) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("create directory proxy");
data_repo
.add_inspect_artifacts(component_id.clone(), TEST_URL, proxy, zx::Time::from_nanos(0))
.expect("add to repo");
let key = component_id.unique_key();
assert_eq!(data_repo.data_directories.get(key.clone()).unwrap().get_values().len(), 1);
let entry = &data_repo.data_directories.get(key.clone()).unwrap().get_values()[0];
assert!(entry.inspect_artifacts_container.is_some());
assert_eq!(entry.component_url, TEST_URL);
}
#[fasync::run_singlethreaded(test)]
async fn data_repo_tolerates_duplicate_new_component_insertions() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
data_repo
.add_new_component(component_id.clone(), TEST_URL, zx::Time::from_nanos(0), None)
.expect("instantiated new component.");
let duplicate_new_component_insertion = data_repo.add_new_component(
component_id.clone(),
TEST_URL,
zx::Time::from_nanos(1),
Some(zx::Time::from_nanos(0)),
);
assert!(duplicate_new_component_insertion.is_ok());
let key = component_id.unique_key();
let repo_values = data_repo.data_directories.get(key.clone()).unwrap().get_values();
assert_eq!(repo_values.len(), 1);
let entry = &repo_values[0];
assert!(entry.lifecycle_artifacts_container.is_some());
let lifecycle_container = entry.lifecycle_artifacts_container.as_ref().unwrap();
assert!(lifecycle_container.component_start_time.is_none());
assert_eq!(entry.relative_moniker, component_id.relative_moniker_for_selectors());
assert_eq!(entry.component_url, TEST_URL);
}
#[fasync::run_singlethreaded(test)]
async fn running_components_provide_start_time() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
let component_insertion = data_repo.add_new_component(
component_id.clone(),
TEST_URL,
zx::Time::from_nanos(1),
Some(zx::Time::from_nanos(0)),
);
assert!(component_insertion.is_ok());
let key = component_id.unique_key();
let repo_values = data_repo.data_directories.get(key.clone()).unwrap().get_values();
assert_eq!(repo_values.len(), 1);
let entry = &repo_values[0];
assert!(entry.lifecycle_artifacts_container.is_some());
let lifecycle_container = entry.lifecycle_artifacts_container.as_ref().unwrap();
assert!(lifecycle_container.component_start_time.is_some());
assert_eq!(lifecycle_container.component_start_time.unwrap().into_nanos(), 0);
assert_eq!(entry.relative_moniker, component_id.relative_moniker_for_selectors());
assert_eq!(entry.component_url, TEST_URL);
}
#[fasync::run_singlethreaded(test)]
async fn data_repo_tolerant_of_new_component_calls_if_diagnostics_ready_already_processed() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
let (proxy, _) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("create directory proxy");
data_repo
.add_inspect_artifacts(component_id.clone(), TEST_URL, proxy, zx::Time::from_nanos(0))
.expect("add to repo");
let false_new_component_result = data_repo.add_new_component(
component_id.clone(),
TEST_URL,
zx::Time::from_nanos(0),
None,
);
assert!(false_new_component_result.is_ok());
// We shouldn't have overwritten the entry. There should still be an inspect
// artifacts container.
let key = component_id.unique_key();
assert_eq!(data_repo.data_directories.get(key.clone()).unwrap().get_values().len(), 1);
let entry = &data_repo.data_directories.get(key.clone()).unwrap().get_values()[0];
assert_eq!(entry.component_url, TEST_URL);
assert!(entry.inspect_artifacts_container.is_some());
assert!(entry.lifecycle_artifacts_container.is_some());
}
#[fasync::run_singlethreaded(test)]
async fn diagnostics_repo_cant_have_more_than_one_diagnostics_data_container_per_component() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path,
component_name: "foo.cmx".into(),
});
data_repo
.add_new_component(component_id.clone(), TEST_URL, zx::Time::from_nanos(0), None)
.expect("insertion will succeed.");
let key = component_id.unique_key();
assert_eq!(data_repo.data_directories.get(key.clone()).unwrap().get_values().len(), 1);
let mutable_values =
data_repo.data_directories.get_mut(key.clone()).unwrap().get_values_mut();
mutable_values.push(DiagnosticsArtifactsContainer {
relative_moniker: component_id.relative_moniker_for_selectors(),
component_url: TEST_URL.to_string(),
inspect_artifacts_container: None,
lifecycle_artifacts_container: None,
});
let (proxy, _) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("create directory proxy");
assert!(data_repo
.add_inspect_artifacts(component_id.clone(), TEST_URL, proxy, zx::Time::from_nanos(0))
.is_err());
}
#[fasync::run_singlethreaded(test)]
async fn data_repo_filters_inspect_by_selectors() {
let mut data_repo = DiagnosticsDataRepository::new(LogManager::new(), None);
let realm_path = RealmPath(vec!["a".to_string(), "b".to_string()]);
let instance_id = "1234".to_string();
let component_id = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id,
realm_path: realm_path.clone(),
component_name: "foo.cmx".into(),
});
data_repo
.add_new_component(component_id.clone(), TEST_URL, zx::Time::from_nanos(0), None)
.expect("insertion will succeed.");
data_repo
.add_inspect_artifacts(
component_id.clone(),
TEST_URL,
io_util::open_directory_in_namespace("/tmp", io_util::OPEN_RIGHT_READABLE)
.expect("open root"),
zx::Time::from_nanos(0),
)
.expect("add inspect artifacts");
let component_id2 = ComponentIdentifier::Legacy(LegacyIdentifier {
instance_id: "12345".to_string(),
realm_path,
component_name: "foo2.cmx".into(),
});
data_repo
.add_new_component(component_id2.clone(), TEST_URL, zx::Time::from_nanos(0), None)
.expect("insertion will succeed.");
data_repo
.add_inspect_artifacts(
component_id2.clone(),
TEST_URL,
io_util::open_directory_in_namespace("/tmp", io_util::OPEN_RIGHT_READABLE)
.expect("open root"),
zx::Time::from_nanos(0),
)
.expect("add inspect artifacts");
assert_eq!(2, data_repo.fetch_inspect_data(&None).len());
let selectors = Some(vec![Arc::new(
selectors::parse_selector("a/b/foo.cmx:root").expect("parse selector"),
)]);
assert_eq!(1, data_repo.fetch_inspect_data(&selectors).len());
let selectors = Some(vec![Arc::new(
selectors::parse_selector("a/b/f*.cmx:root").expect("parse selector"),
)]);
assert_eq!(2, data_repo.fetch_inspect_data(&selectors).len());
let selectors = Some(vec![Arc::new(
selectors::parse_selector("foo.cmx:root").expect("parse selector"),
)]);
assert_eq!(0, data_repo.fetch_inspect_data(&selectors).len());
}
}