settings/agent/inspect/
setting_values.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::clock;
6use fuchsia_async as fasync;
7use fuchsia_inspect::{self as inspect, Property, StringProperty, component};
8use fuchsia_inspect_derive::{Inspect, WithInspect};
9use futures::StreamExt;
10use futures::channel::mpsc::UnboundedReceiver;
11#[cfg(test)]
12use futures::channel::mpsc::UnboundedSender;
13use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
14
15const INSPECT_NODE_NAME: &str = "setting_values";
16const SETTING_TYPE_INSPECT_NODE_NAME: &str = "setting_types";
17
18/// Information about the setting types available in the settings service.
19///
20/// Inspect nodes are not used, but need to be held as they're deleted from inspect once they go
21/// out of scope.
22#[derive(Debug, Default, Inspect)]
23struct SettingTypesInspectInfo {
24    inspect_node: inspect::Node,
25    value: inspect::StringProperty,
26}
27
28impl SettingTypesInspectInfo {
29    fn new(value: String, node: &inspect::Node, key: &str) -> Self {
30        let info = Self::default()
31            .with_inspect(node, key)
32            .expect("Failed to create SettingTypesInspectInfo node");
33        info.value.set(&value);
34        info
35    }
36}
37
38/// Information about a setting to be written to inspect.
39///
40/// Inspect nodes are not used, but need to be held as they're deleted from inspect once they go
41/// out of scope.
42#[derive(Default, Inspect)]
43struct SettingValuesInspectInfo {
44    /// Node of this info.
45    inspect_node: inspect::Node,
46
47    /// Debug string representation of the value of this setting.
48    value: StringProperty,
49
50    /// Milliseconds since Unix epoch that this setting's value was changed.
51    timestamp: StringProperty,
52}
53
54pub struct AgentSetup {
55    agent: SettingValuesInspectAgent,
56    event_rx: UnboundedReceiver<(&'static str, String)>,
57}
58
59impl AgentSetup {
60    pub fn initialize(self, #[cfg(test)] mut channel_done_tx: Option<UnboundedSender<()>>) {
61        let AgentSetup { mut agent, mut event_rx } = self;
62        fasync::Task::local(async move {
63            while let Some((setting_name, setting_str)) = event_rx.next().await {
64                agent.write_raw_setting_to_inspect(setting_name, setting_str).await;
65                #[cfg(test)]
66                {
67                    let _ = channel_done_tx.as_mut().map(|tx| tx.start_send(()));
68                }
69            }
70        })
71        .detach();
72    }
73}
74
75/// An agent that listens in on messages between the proxy and setting handlers to record the
76/// values of all settings to inspect.
77pub(crate) struct SettingValuesInspectAgent {
78    setting_values: ManagedInspectMap<SettingValuesInspectInfo>,
79    _setting_types_inspect_info: SettingTypesInspectInfo,
80}
81
82impl SettingValuesInspectAgent {
83    pub(crate) fn new(
84        settings: Vec<String>,
85        rx: UnboundedReceiver<(&'static str, String)>,
86    ) -> Option<AgentSetup> {
87        Self::create_with_node(
88            settings,
89            component::inspector().root().create_child(INSPECT_NODE_NAME),
90            rx,
91            None,
92        )
93    }
94
95    /// Create an agent to listen in on all messages between Proxy and setting
96    /// handlers. Agent starts immediately without calling invocation, but
97    /// acknowledges the invocation payload to let the Authority know the agent
98    /// starts properly.
99    fn create_with_node(
100        mut settings: Vec<String>,
101        inspect_node: inspect::Node,
102        event_rx: UnboundedReceiver<(&'static str, String)>,
103        custom_inspector: Option<&inspect::Inspector>,
104    ) -> Option<AgentSetup> {
105        let inspector = custom_inspector.unwrap_or_else(|| component::inspector());
106
107        // Add inspect node for the setting types.
108        settings.sort();
109        let setting_types_value = format!("{settings:?}");
110        let setting_types_inspect_info = SettingTypesInspectInfo::new(
111            setting_types_value,
112            inspector.root(),
113            SETTING_TYPE_INSPECT_NODE_NAME,
114        );
115
116        let agent = Self {
117            setting_values: ManagedInspectMap::<SettingValuesInspectInfo>::with_node(inspect_node),
118            _setting_types_inspect_info: setting_types_inspect_info,
119        };
120
121        Some(AgentSetup { agent, event_rx })
122    }
123
124    /// Writes a setting value to inspect.
125    async fn write_raw_setting_to_inspect(&mut self, setting_name: &'static str, value: String) {
126        let timestamp = clock::inspect_format_now();
127
128        let key_str = setting_name.to_string();
129        let setting_values = self.setting_values.get_mut(&key_str);
130
131        if let Some(setting_values_info) = setting_values {
132            // Value already known, just update its fields.
133            setting_values_info.timestamp.set(&timestamp);
134            setting_values_info.value.set(&value);
135        } else {
136            // Setting value not recorded yet, create a new inspect node.
137            let inspect_info =
138                self.setting_values.get_or_insert_with(key_str, SettingValuesInspectInfo::default);
139            inspect_info.timestamp.set(&timestamp);
140            inspect_info.value.set(&value);
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use diagnostics_assertions::assert_data_tree;
149    use futures::channel::mpsc;
150    use zx::MonotonicInstant;
151
152    // Verifies that inspect agent intercepts setting change events and writes the setting value
153    // to inspect.
154    #[fuchsia::test(allow_stalls = false)]
155    async fn test_write_inspect_on_changed() {
156        // Set the clock so that timestamps will always be 0.
157        clock::mock::set(MonotonicInstant::from_nanos(0));
158
159        let inspector = inspect::Inspector::default();
160        let inspect_node = inspector.root().create_child(INSPECT_NODE_NAME);
161
162        let (mut tx, rx) = mpsc::unbounded();
163        let agent_setup = SettingValuesInspectAgent::create_with_node(
164            vec!["Unknown".to_string()],
165            inspect_node,
166            rx,
167            Some(&inspector),
168        )
169        .expect("agent missing inspect");
170
171        let (done_tx, mut done_rx) = mpsc::unbounded();
172        agent_setup.initialize(Some(done_tx));
173
174        // Inspect agent should not report any setting values.
175        assert_data_tree!(inspector, root: {
176            setting_types: {
177                "value": "[\"Unknown\"]",
178            },
179            setting_values: {
180            }
181        });
182
183        tx.start_send(("Unknown", "UnknownInfo(true)".to_string())).expect("sending via channel");
184        let () = done_rx.next().await.expect("should have processed event");
185
186        // Inspect agent writes value to inspect.
187        assert_data_tree!(inspector, root: {
188            setting_types: {
189                "value": "[\"Unknown\"]",
190            },
191            setting_values: {
192                "Unknown": {
193                    value: "UnknownInfo(true)",
194                    timestamp: "0.000000000",
195                }
196            }
197        });
198    }
199}