use std::collections::HashSet;
use std::rc::Rc;
use fuchsia_async as fasync;
use fuchsia_inspect::{self as inspect, component, Property, StringProperty};
use fuchsia_inspect_derive::{Inspect, WithInspect};
use futures::StreamExt;
use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
use crate::agent::{Context, Lifespan, Payload};
use crate::base::{SettingInfo, SettingType};
use crate::handler::base::{Payload as SettingPayload, Request};
use crate::handler::setting_handler::{Event, Payload as HandlerPayload};
use crate::message::base::{Audience, MessageEvent, MessengerType};
use crate::service::message::Messenger;
use crate::service::TryFromWithClient;
use crate::{clock, service};
const INSPECT_NODE_NAME: &str = "setting_values";
const SETTING_TYPE_INSPECT_NODE_NAME: &str = "setting_types";
pub(crate) struct SettingValuesInspectAgent {
messenger_client: Messenger,
setting_values: ManagedInspectMap<SettingValuesInspectInfo>,
setting_types: HashSet<SettingType>,
_setting_types_inspect_info: SettingTypesInspectInfo,
}
#[derive(Debug, Default, Inspect)]
struct SettingTypesInspectInfo {
inspect_node: inspect::Node,
value: inspect::StringProperty,
}
impl SettingTypesInspectInfo {
fn new(value: String, node: &inspect::Node, key: &str) -> Self {
let info = Self::default()
.with_inspect(node, key)
.expect("Failed to create SettingTypesInspectInfo node");
info.value.set(&value);
info
}
}
#[derive(Default, Inspect)]
struct SettingValuesInspectInfo {
inspect_node: inspect::Node,
value: StringProperty,
timestamp: StringProperty,
}
impl SettingValuesInspectAgent {
pub(crate) async fn create(context: Context) {
Self::create_with_node(
context,
component::inspector().root().create_child(INSPECT_NODE_NAME),
None,
)
.await;
}
async fn create_with_node(
context: Context,
inspect_node: inspect::Node,
custom_inspector: Option<&inspect::Inspector>,
) {
let inspector = custom_inspector.unwrap_or_else(|| component::inspector());
let (messenger_client, receptor) = match context
.delegate
.create(MessengerType::Broker(Rc::new(move |message| {
matches!(
message.payload(),
service::Payload::Controller(HandlerPayload::Event(Event::Changed(_)))
)
})))
.await
{
Ok(messenger) => messenger,
Err(err) => {
tracing::error!("could not create inspect: {:?}", err);
return;
}
};
let mut setting_types_list: Vec<String> = context
.available_components
.clone()
.iter()
.map(|component| format!("{component:?}"))
.collect();
setting_types_list.sort();
let setting_types_value = format!("{setting_types_list:?}");
let setting_types_inspect_info = SettingTypesInspectInfo::new(
setting_types_value,
inspector.root(),
SETTING_TYPE_INSPECT_NODE_NAME,
);
let mut agent = Self {
messenger_client,
setting_values: ManagedInspectMap::<SettingValuesInspectInfo>::with_node(inspect_node),
setting_types: context.available_components.clone(),
_setting_types_inspect_info: setting_types_inspect_info,
};
fasync::Task::local(async move {
let _ = &context;
let event = receptor.fuse();
let agent_event = context.receptor.fuse();
futures::pin_mut!(agent_event, event);
loop {
futures::select! {
message_event = event.select_next_some() => {
agent.process_message_event(message_event).await;
},
agent_message = agent_event.select_next_some() => {
if let MessageEvent::Message(
service::Payload::Agent(Payload::Invocation(invocation)), client)
= agent_message {
if invocation.lifespan == Lifespan::Service {
agent.fetch_initial_values().await;
}
let _ = client.reply(Payload::Complete(Ok(())).into());
}
},
}
}
})
.detach();
}
async fn fetch_initial_values(&mut self) {
for setting_type in self.setting_types.clone() {
let mut receptor = self.messenger_client.message(
SettingPayload::Request(Request::Get).into(),
Audience::Address(service::Address::Handler(setting_type)),
);
if let Ok((SettingPayload::Response(Ok(Some(setting_info))), _)) =
receptor.next_of::<SettingPayload>().await
{
self.write_setting_to_inspect(setting_info).await;
} else {
tracing::error!(
"Could not fetch initial value for setting type:{:?}",
setting_type
);
}
}
}
async fn process_message_event(&mut self, event: service::message::MessageEvent) {
if let Ok((HandlerPayload::Event(Event::Changed(setting_info)), _)) =
HandlerPayload::try_from_with_client(event)
{
self.write_setting_to_inspect(setting_info).await;
}
}
async fn write_setting_to_inspect(&mut self, setting: SettingInfo) {
let (key, value) = setting.for_inspect();
let timestamp = clock::inspect_format_now();
let key_str = key.to_string();
let setting_values = self.setting_values.get_mut(&key_str);
if let Some(setting_values_info) = setting_values {
setting_values_info.timestamp.set(×tamp);
setting_values_info.value.set(&value);
} else {
let inspect_info =
self.setting_values.get_or_insert_with(key_str, SettingValuesInspectInfo::default);
inspect_info.timestamp.set(×tamp);
inspect_info.value.set(&value);
}
}
}
#[cfg(test)]
mod tests {
use diagnostics_assertions::assert_data_tree;
use zx::MonotonicInstant;
use crate::agent::Invocation;
use crate::base::UnknownInfo;
use crate::message::base::Status;
use crate::service::MessageHub;
use crate::service_context::ServiceContext;
use super::*;
async fn create_context() -> Context {
let hub = MessageHub::create_hub();
Context::new(
hub.create(MessengerType::Unbound).await.expect("should be present").1,
hub,
vec![SettingType::Unknown].into_iter().collect(),
)
.await
}
#[fuchsia::test(allow_stalls = false)]
async fn test_write_inspect_on_service_lifespan() {
clock::mock::set(MonotonicInstant::from_nanos(0));
let inspector = inspect::Inspector::default();
let inspect_node = inspector.root().create_child(INSPECT_NODE_NAME);
let context = create_context().await;
let delegate = context.delegate.clone();
let agent_signature = context.receptor.get_signature();
let (_, mut setting_proxy_receptor) = context
.delegate
.create(MessengerType::Addressable(service::Address::Handler(SettingType::Unknown)))
.await
.expect("should create proxy");
SettingValuesInspectAgent::create_with_node(context, inspect_node, Some(&inspector)).await;
assert_data_tree!(inspector, root: {
setting_types: {
"value": "[\"Unknown\"]",
},
setting_values: {
}
});
let _ = delegate
.create(MessengerType::Unbound)
.await
.expect("should create messenger")
.0
.message(
Payload::Invocation(Invocation {
lifespan: Lifespan::Service,
service_context: Rc::new(ServiceContext::new(None, None)),
})
.into(),
Audience::Messenger(agent_signature),
);
let (_, reply_client) =
setting_proxy_receptor.next_payload().await.expect("payload should be present");
let mut reply_receptor =
reply_client.reply(SettingPayload::Response(Ok(Some(UnknownInfo(true).into()))).into());
while let Some(message_event) = reply_receptor.next().await {
if matches!(message_event, service::message::MessageEvent::Status(Status::Acknowledged))
{
break;
}
}
assert_data_tree!(inspector, root: {
setting_types: {
"value": "[\"Unknown\"]",
},
setting_values: {
"Unknown": {
value: "UnknownInfo(true)",
timestamp: "0.000000000",
}
}
});
}
#[fuchsia::test(allow_stalls = false)]
async fn test_write_inspect_on_changed() {
clock::mock::set(MonotonicInstant::from_nanos(0));
let inspector = inspect::Inspector::default();
let inspect_node = inspector.root().create_child(INSPECT_NODE_NAME);
let context = create_context().await;
let delegate = context.delegate.clone();
let mut proxy_receptor =
delegate.create(MessengerType::Unbound).await.expect("should create proxy messenger").1;
SettingValuesInspectAgent::create_with_node(context, inspect_node, Some(&inspector)).await;
assert_data_tree!(inspector, root: {
setting_types: {
"value": "[\"Unknown\"]",
},
setting_values: {
}
});
let _ = delegate
.create(MessengerType::Unbound)
.await
.expect("setting handler should be created")
.0
.message(
HandlerPayload::Event(Event::Changed(SettingInfo::Unknown(UnknownInfo(false))))
.into(),
Audience::Messenger(proxy_receptor.get_signature()),
);
let _payload = proxy_receptor.next_payload().await;
assert_data_tree!(inspector, root: {
setting_types: {
"value": "[\"Unknown\"]",
},
setting_values: {
"Unknown": {
value: "UnknownInfo(false)",
timestamp: "0.000000000",
}
}
});
}
}