1use 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
24struct SettingTypeUsageInspectInfo {
26 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 inspect_node: inspect::Node,
44
45 count: inspect::IntProperty,
47}
48
49pub(crate) struct SettingTypeUsageInspectAgent {
52 inspect_node: inspect::Node,
54
55 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 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 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 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}