blob: 7a283eac444784198e40c9b4b886f9515973b37d [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 {
fidl_fuchsia_diagnostics::{Interest, StringSelector},
fidl_fuchsia_logger::{LogInterestSelector, LogSinkControlHandle},
fidl_fuchsia_sys_internal::SourceIdentity,
std::collections::HashMap,
std::convert::TryFrom,
std::sync::{Arc, Weak},
tracing::warn,
};
/// Type used to identify the intended component target specified by an
/// interest selector.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct Component {
name: String,
}
impl<'s> TryFrom<&'s Arc<SourceIdentity>> for Component {
type Error = &'static str;
fn try_from(source: &'s Arc<SourceIdentity>) -> Result<Self, Self::Error> {
match &source.component_name {
Some(n) => Ok(Component { name: n.to_string() }),
None => Err("Failed to convert SourceIdentity to Component - missing name."),
}
}
}
/// Interest dispatcher to handle the communication of `Interest` changes
/// to their intended `LogSink` client listeners.
#[derive(Debug, Default)]
pub struct InterestDispatcher {
/// Map of LogSinkControlHandles associated with a given component name
/// (possibly multiple instances). Any provided selector specification
/// will apply universally to all matching instances.
interest_listeners: HashMap<Component, Vec<Weak<LogSinkControlHandle>>>,
/// List of LogInterestSelectors that couple the specified interest with
/// the intended target component.
selectors: Vec<LogInterestSelector>,
}
impl InterestDispatcher {
/// Add a LogSinkControlHandle corresponding to the given component
/// (source) as an interest listener. If one or more control handles
/// are already associated with this component (i.e. in the case of
/// multiple instances) this handle will be apended to the list.
pub fn add_interest_listener(
&mut self,
source: &Arc<SourceIdentity>,
handle: Weak<LogSinkControlHandle>,
) {
let component = match Component::try_from(source) {
Ok(c) => c,
_ => {
warn!("Failed to add interest listener - no component for source");
return;
}
};
let component_listeners =
self.interest_listeners.entry(component.clone()).or_insert(vec![]);
component_listeners.push(handle);
// check to see if we have a selector specified for this component and
// if so, send the interest notification.
let mut interest: Option<Interest> = None;
self.selectors.iter().for_each(|s| {
if let Some(segments) = &s.selector.moniker_segments {
segments.iter().for_each(|segment| {
match segment {
StringSelector::StringPattern(name) => {
// TODO(fxbug.dev/54198): Interest listener matching based
// on strict name comparison look at using moniker
// heuristics via selectors API.
if name == &component.name {
interest = Some(Interest {
min_severity: s.interest.min_severity,
..Interest::EMPTY
});
}
}
_ => warn!(?segment, "Unexpected component selector moniker segment"),
};
});
};
});
if let Some(i) = interest {
self.notify_listeners_for_component(component, |l| {
let _ = l.send_on_register_interest(Interest {
min_severity: i.min_severity,
..Interest::EMPTY
});
});
}
}
/// Update the set of selectors that archivist uses to control the log
/// levels associated with any active LogSink clients.
pub fn update_selectors<'a>(&mut self, selectors: Vec<LogInterestSelector>) {
if !self.selectors.is_empty() {
warn!(existing = ?self.selectors, new = ?selectors, "Overriding selectors");
}
selectors.iter().for_each(|s| {
if let Some(segments) = &s.selector.moniker_segments {
segments.iter().for_each(|segment| {
match segment {
// string_pattern results from selectors created with
// selectors::parse_component_selector. Potentially
// handle additional cases (exact_match?) if needs be.
StringSelector::StringPattern(name) => {
self.notify_listeners_for_component(
Component { name: name.to_string() },
|l| {
let _ = l.send_on_register_interest(Interest {
min_severity: s.interest.min_severity,
..Interest::EMPTY
});
},
);
}
_ => warn!(?segment, "Unexpected component selector moniker segment"),
};
});
};
});
self.selectors = selectors;
}
fn notify_listeners_for_component<F>(&mut self, component: Component, mut f: F)
where
F: FnMut(&LogSinkControlHandle) -> (),
{
if let Some(component_listeners) = self.interest_listeners.get_mut(&component) {
component_listeners.retain(|listener| match listener.upgrade() {
Some(listener_) => {
f(&listener_);
true
}
None => false,
});
} else {
warn!(
?component,
"Failed to notify interest listener - unable to find LogSinkControlHandle"
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::{create_request_stream, RequestStream};
use std::sync::Arc;
/// This test does not await any futures but it creates FIDL types which expect to have
/// an executor available.
#[fuchsia_async::run_singlethreaded(test)]
async fn interest_listeners() {
let mut source_id = SourceIdentity::EMPTY;
source_id.component_name = Some("foo.cmx".to_string());
let source_arc = Arc::new(source_id);
let component = Component::try_from(&source_arc);
let mut dispatcher = InterestDispatcher::default();
let (_logsink_client, log_request_stream) =
create_request_stream::<fidl_fuchsia_logger::LogSinkMarker>()
.expect("failed to create LogSink proxy");
let handle = log_request_stream.control_handle();
// add the interest listener (component_id + weak handle)
dispatcher.add_interest_listener(&source_arc, Arc::downgrade(&Arc::new(handle)));
// check listener addition successful
assert_eq!(component.is_ok(), true);
if let Ok(c) = component {
let preclose_listeners = &dispatcher.interest_listeners.get(&c);
assert_eq!(preclose_listeners.is_some(), true);
if let Some(l) = preclose_listeners {
assert_eq!(l.len(), 1);
if let Some(handle) = l[0].upgrade() {
// close the channel
handle.shutdown();
}
// notify checks the channel/retains
dispatcher.notify_listeners_for_component(c.clone(), |listener| {
let _ = listener.send_on_register_interest(Interest {
min_severity: None,
..Interest::EMPTY
});
});
// check that the listener is no longer active.
let postclose_listeners = &dispatcher.interest_listeners.get(&c);
assert_eq!(postclose_listeners.is_some(), true);
match postclose_listeners {
Some(l) => assert_eq!(l.len(), 0),
None => {}
}
}
}
}
}