settings/agent/inspect/
usage_counts.rs

1// Copyright 2022 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
5//! The usage_counts mod defines the [SettingTypeUsageInspectAgent], which is responsible for counting
6//! relevant API usages to Inspect. Since API usages might happen before agent lifecycle states are
7//! communicated (due to agent priority ordering), the [SettingTypeUsageInspectAgent] begins
8//! listening to requests immediately after creation.
9//!
10
11use fuchsia_async as fasync;
12use fuchsia_inspect::{self as inspect, component};
13use fuchsia_inspect_derive::Inspect;
14use futures::StreamExt;
15use futures::channel::mpsc::UnboundedReceiver;
16#[cfg(test)]
17use futures::channel::mpsc::UnboundedSender;
18use inspect::NumericProperty;
19use settings_common::inspect::event::{Direction, UsageEvent};
20use settings_common::trace;
21use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
22use std::collections::HashMap;
23
24/// Information about a setting type usage count to be written to inspect.
25struct SettingTypeUsageInspectInfo {
26    /// Map from the name of the Request variant to its calling counts.
27    requests_by_type: ManagedInspectMap<UsageInfo>,
28}
29
30impl SettingTypeUsageInspectInfo {
31    fn new(parent: &inspect::Node, setting_type_str: &str) -> Self {
32        Self {
33            requests_by_type: ManagedInspectMap::<UsageInfo>::with_node(
34                parent.create_child(setting_type_str),
35            ),
36        }
37    }
38}
39
40#[derive(Default, Inspect)]
41struct UsageInfo {
42    /// Node of this info.
43    inspect_node: inspect::Node,
44
45    /// Call counts of the current API.
46    count: inspect::IntProperty,
47}
48
49/// The SettingTypeUsageInspectAgent is responsible for listening to requests to the setting
50/// handlers and recording the related API usage counts to Inspect.
51pub(crate) struct SettingTypeUsageInspectAgent {
52    /// Node of this info.
53    inspect_node: inspect::Node,
54
55    /// Mapping from SettingType key to api usage counts.
56    api_call_counts: HashMap<String, SettingTypeUsageInspectInfo>,
57
58    usage_rx: Option<UnboundedReceiver<UsageEvent>>,
59
60    #[cfg(test)]
61    done_tx: Option<UnboundedSender<()>>,
62}
63
64impl SettingTypeUsageInspectAgent {
65    pub fn new(rx: UnboundedReceiver<UsageEvent>) -> Self {
66        Self::create_with_node(
67            rx,
68            component::inspector().root().create_child("api_usage_counts"),
69            #[cfg(test)]
70            None,
71        )
72    }
73
74    fn create_with_node(
75        rx: UnboundedReceiver<UsageEvent>,
76        node: inspect::Node,
77        #[cfg(test)] done_tx: Option<UnboundedSender<()>>,
78    ) -> Self {
79        SettingTypeUsageInspectAgent {
80            inspect_node: node,
81            api_call_counts: HashMap::new(),
82            usage_rx: Some(rx),
83            #[cfg(test)]
84            done_tx,
85        }
86    }
87
88    pub fn initialize(mut self) {
89        fasync::Task::local({
90            async move {
91                let id = fuchsia_trace::Id::new();
92                trace!(id, c"usage_counts_inspect_agent");
93                let mut usage_rx = self.usage_rx.take().unwrap();
94
95                while let Some(usage_event) = usage_rx.next().await {
96                    self.process_usage_event(usage_event);
97                    #[cfg(test)]
98                    if let Some(done_tx) = &self.done_tx {
99                        let _ = done_tx.unbounded_send(());
100                    }
101                }
102            }
103        })
104        .detach();
105    }
106
107    fn process_usage_event(&mut self, event: UsageEvent) {
108        // We only need to track incoming requests.
109        if let Direction::Response(..) = event.direction {
110            return;
111        }
112
113        let inspect_node = &self.inspect_node;
114        let setting_type_info = self
115            .api_call_counts
116            .entry(event.setting.to_string())
117            .or_insert_with(|| SettingTypeUsageInspectInfo::new(inspect_node, event.setting));
118
119        let key = event.request_type;
120        let usage = setting_type_info
121            .requests_by_type
122            .get_or_insert_with(format!("{key:?}"), UsageInfo::default);
123        let _ = usage.count.add(1);
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use diagnostics_assertions::assert_data_tree;
131    use futures::channel::mpsc;
132    use settings_common::inspect::event::{Direction, RequestType, ResponseType};
133
134    #[fuchsia::test(allow_stalls = false)]
135    async fn test_inspect() {
136        let inspector = inspect::Inspector::default();
137        let inspect_node = inspector.root().create_child("api_usage_counts");
138
139        let (tx, rx) = mpsc::unbounded();
140        let (done_tx, mut done_rx) = mpsc::unbounded();
141        let agent = SettingTypeUsageInspectAgent::create_with_node(rx, inspect_node, Some(done_tx));
142        agent.initialize();
143
144        // Send a few requests to make sure they get written to inspect properly.
145        let mut request_event = UsageEvent {
146            setting: "Display",
147            request_type: RequestType::Set,
148            direction: Direction::Request(
149                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
150            ),
151            id: 0,
152        };
153        let mut response_event = UsageEvent {
154            setting: "Display",
155            request_type: RequestType::Set,
156            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
157            id: 0,
158        };
159
160        let _ = tx.unbounded_send(request_event.clone());
161        let _ = done_rx.next().await;
162
163        let _ = tx.unbounded_send(response_event.clone());
164        let _ = done_rx.next().await;
165
166        request_event.id = 1;
167        response_event.id = 1;
168
169        let _ = tx.unbounded_send(request_event);
170        let _ = done_rx.next().await;
171
172        let _ = tx.unbounded_send(response_event);
173        let _ = done_rx.next().await;
174
175        for i in 0..100 {
176            let _ = tx.unbounded_send(UsageEvent {
177                setting: "Intl",
178                request_type: RequestType::Set,
179                direction: Direction::Request(
180                    "SetIntlInfo{ \
181                        locales: Some([LocaleId { id: \"en-US\" }]), \
182                        temperature_unit: Some(Celsius), \
183                        time_zone_id: Some(\"UTC\"), \
184                        hour_cycle: None \
185                    }"
186                    .to_string(),
187                ),
188                id: i + 2,
189            });
190            let _ = done_rx.next().await;
191
192            let _ = tx.unbounded_send(UsageEvent {
193                setting: "Intl",
194                request_type: RequestType::Set,
195                direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
196                id: i + 2,
197            });
198            let _ = done_rx.next().await;
199        }
200
201        assert_data_tree!(inspector, root: {
202            api_usage_counts: {
203                "Display": {
204                    "Set": {
205                        count: 2i64,
206                    },
207                },
208                "Intl": {
209                    "Set": {
210                       count: 100i64
211                    },
212                }
213            },
214        });
215    }
216
217    #[fuchsia::test(allow_stalls = false)]
218    async fn test_inspect_mixed_request_types() {
219        let inspector = inspect::Inspector::default();
220        let inspect_node = inspector.root().create_child("api_usage_counts");
221
222        let (tx, rx) = mpsc::unbounded();
223        let (done_tx, mut done_rx) = mpsc::unbounded();
224        let agent = SettingTypeUsageInspectAgent::create_with_node(rx, inspect_node, Some(done_tx));
225        agent.initialize();
226
227        // Interlace different request types to make sure the counter is correct.
228        let _ = tx.unbounded_send(UsageEvent {
229            setting: "Display",
230            request_type: RequestType::Set,
231            direction: Direction::Request(
232                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
233            ),
234            id: 0,
235        });
236        let _ = done_rx.next().await;
237        let _ = tx.unbounded_send(UsageEvent {
238            setting: "Display",
239            request_type: RequestType::Set,
240            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
241            id: 0,
242        });
243        let _ = done_rx.next().await;
244
245        let _ = tx.unbounded_send(UsageEvent {
246            setting: "Display",
247            request_type: RequestType::Get,
248            direction: Direction::Request("WatchDisplayInfo".to_string()),
249            id: 1,
250        });
251        let _ = done_rx.next().await;
252        let _ = tx.unbounded_send(UsageEvent {
253            setting: "Display",
254            request_type: RequestType::Get,
255            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
256            id: 1,
257        });
258        let _ = done_rx.next().await;
259
260        let _ = tx.unbounded_send(UsageEvent {
261            setting: "Display",
262            request_type: RequestType::Set,
263            direction: Direction::Request(
264                "SetDisplayInfo{auto_brightness: Some(true)}".to_string(),
265            ),
266            id: 2,
267        });
268        let _ = done_rx.next().await;
269        let _ = tx.unbounded_send(UsageEvent {
270            setting: "Display",
271            request_type: RequestType::Set,
272            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
273            id: 2,
274        });
275        let _ = done_rx.next().await;
276
277        let _ = tx.unbounded_send(UsageEvent {
278            setting: "Display",
279            request_type: RequestType::Get,
280            direction: Direction::Request("WatchDisplayInfo".to_string()),
281            id: 3,
282        });
283        let _ = done_rx.next().await;
284        let _ = tx.unbounded_send(UsageEvent {
285            setting: "Display",
286            request_type: RequestType::Get,
287            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
288            id: 3,
289        });
290        let _ = done_rx.next().await;
291
292        assert_data_tree!(inspector, root: {
293            api_usage_counts: {
294                "Display": {
295                    "Set": {
296                        count: 2i64,
297                    },
298                    "Get": {
299                        count: 2i64,
300                    },
301                },
302            }
303        });
304    }
305}