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    // Returns Self along with receiver.
84    #[allow(clippy::new_ret_no_self)]
85    pub(crate) fn new(
86        settings: Vec<String>,
87        rx: UnboundedReceiver<(&'static str, String)>,
88    ) -> Option<AgentSetup> {
89        Self::create_with_node(
90            settings,
91            component::inspector().root().create_child(INSPECT_NODE_NAME),
92            rx,
93            None,
94        )
95    }
96
97    /// Create an agent to listen in on all messages between Proxy and setting
98    /// handlers. Agent starts immediately without calling invocation, but
99    /// acknowledges the invocation payload to let the Authority know the agent
100    /// starts properly.
101    fn create_with_node(
102        mut settings: Vec<String>,
103        inspect_node: inspect::Node,
104        event_rx: UnboundedReceiver<(&'static str, String)>,
105        custom_inspector: Option<&inspect::Inspector>,
106    ) -> Option<AgentSetup> {
107        let inspector = custom_inspector.unwrap_or_else(|| component::inspector());
108
109        // Add inspect node for the setting types.
110        settings.sort();
111        let setting_types_value = format!("{settings:?}");
112        let setting_types_inspect_info = SettingTypesInspectInfo::new(
113            setting_types_value,
114            inspector.root(),
115            SETTING_TYPE_INSPECT_NODE_NAME,
116        );
117
118        let agent = Self {
119            setting_values: ManagedInspectMap::<SettingValuesInspectInfo>::with_node(inspect_node),
120            _setting_types_inspect_info: setting_types_inspect_info,
121        };
122
123        Some(AgentSetup { agent, event_rx })
124    }
125
126    /// Writes a setting value to inspect.
127    async fn write_raw_setting_to_inspect(&mut self, setting_name: &'static str, value: String) {
128        let timestamp = clock::inspect_format_now();
129
130        let key_str = setting_name.to_string();
131        let setting_values = self.setting_values.get_mut(&key_str);
132
133        if let Some(setting_values_info) = setting_values {
134            // Value already known, just update its fields.
135            setting_values_info.timestamp.set(&timestamp);
136            setting_values_info.value.set(&value);
137        } else {
138            // Setting value not recorded yet, create a new inspect node.
139            let inspect_info =
140                self.setting_values.get_or_insert_with(key_str, SettingValuesInspectInfo::default);
141            inspect_info.timestamp.set(&timestamp);
142            inspect_info.value.set(&value);
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use diagnostics_assertions::assert_data_tree;
151    use futures::channel::mpsc;
152    use zx::MonotonicInstant;
153
154    // Verifies that inspect agent intercepts setting change events and writes the setting value
155    // to inspect.
156    #[fuchsia::test(allow_stalls = false)]
157    async fn test_write_inspect_on_changed() {
158        // Set the clock so that timestamps will always be 0.
159        clock::mock::set(MonotonicInstant::from_nanos(0));
160
161        let inspector = inspect::Inspector::default();
162        let inspect_node = inspector.root().create_child(INSPECT_NODE_NAME);
163
164        let (mut tx, rx) = mpsc::unbounded();
165        let agent_setup = SettingValuesInspectAgent::create_with_node(
166            vec!["Unknown".to_string()],
167            inspect_node,
168            rx,
169            Some(&inspector),
170        )
171        .expect("agent missing inspect");
172
173        let (done_tx, mut done_rx) = mpsc::unbounded();
174        agent_setup.initialize(Some(done_tx));
175
176        // Inspect agent should not report any setting values.
177        assert_data_tree!(inspector, root: {
178            setting_types: {
179                "value": "[\"Unknown\"]",
180            },
181            setting_values: {
182            }
183        });
184
185        tx.start_send(("Unknown", "UnknownInfo(true)".to_string())).expect("sending via channel");
186        let () = done_rx.next().await.expect("should have processed event");
187
188        // Inspect agent writes value to inspect.
189        assert_data_tree!(inspector, root: {
190            setting_types: {
191                "value": "[\"Unknown\"]",
192            },
193            setting_values: {
194                "Unknown": {
195                    value: "UnknownInfo(true)",
196                    timestamp: "0.000000000",
197                }
198            }
199        });
200    }
201}