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 crate::agent::{AgentCreator, Context, CreationFunc, Payload};
12use crate::base::SettingType;
13use crate::handler::base::{Payload as HandlerPayload, Request};
14use crate::message::base::{MessageEvent, MessengerType};
15use crate::service::TryFromWithClient;
16use crate::{service, trace};
17use fuchsia_async as fasync;
18use fuchsia_inspect::{self as inspect, component};
19use fuchsia_inspect_derive::Inspect;
20use futures::channel::mpsc::UnboundedReceiver;
21use futures::StreamExt;
22use inspect::NumericProperty;
23use settings_common::inspect::event::{Direction, UsageEvent};
24use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
25use std::cell::RefCell;
26use std::collections::HashMap;
27use std::rc::Rc;
28
29pub(crate) fn create_registrar(rx: UnboundedReceiver<UsageEvent>) -> AgentCreator {
30    let rx = Rc::new(RefCell::new(rx));
31    AgentCreator {
32        debug_id: "UsageCountInspectAgent",
33        create: CreationFunc::Dynamic(Rc::new(move |context| {
34            let rx = Rc::clone(&rx);
35            Box::pin(async move {
36                SettingTypeUsageInspectAgent::create(context, rx).await;
37            })
38        })),
39    }
40}
41
42/// Information about a setting type usage count to be written to inspect.
43struct SettingTypeUsageInspectInfo {
44    /// Map from the name of the Request variant to its calling counts.
45    requests_by_type: ManagedInspectMap<UsageInfo>,
46}
47
48impl SettingTypeUsageInspectInfo {
49    fn new(parent: &inspect::Node, setting_type_str: &str) -> Self {
50        Self {
51            requests_by_type: ManagedInspectMap::<UsageInfo>::with_node(
52                parent.create_child(setting_type_str),
53            ),
54        }
55    }
56}
57
58#[derive(Default, Inspect)]
59struct UsageInfo {
60    /// Node of this info.
61    inspect_node: inspect::Node,
62
63    /// Call counts of the current API.
64    count: inspect::IntProperty,
65}
66
67/// The SettingTypeUsageInspectAgent is responsible for listening to requests to the setting
68/// handlers and recording the related API usage counts to Inspect.
69pub(crate) struct SettingTypeUsageInspectAgent {
70    /// Node of this info.
71    inspect_node: inspect::Node,
72
73    /// Mapping from SettingType key to api usage counts.
74    api_call_counts: HashMap<String, SettingTypeUsageInspectInfo>,
75}
76
77impl SettingTypeUsageInspectAgent {
78    pub(crate) async fn create(context: Context, rx: Rc<RefCell<UnboundedReceiver<UsageEvent>>>) {
79        Self::create_with_node(
80            context,
81            rx,
82            component::inspector().root().create_child("api_usage_counts"),
83        )
84        .await;
85    }
86
87    async fn create_with_node(
88        context: Context,
89        rx: Rc<RefCell<UnboundedReceiver<UsageEvent>>>,
90        node: inspect::Node,
91    ) {
92        let (_, message_rx) = context
93            .delegate
94            .create(MessengerType::Broker(Rc::new(move |message| {
95                // Only catch setting handler requests.
96                matches!(message.payload(), service::Payload::Setting(HandlerPayload::Request(_)))
97            })))
98            .await
99            .expect("should receive client");
100
101        let mut agent =
102            SettingTypeUsageInspectAgent { inspect_node: node, api_call_counts: HashMap::new() };
103
104        fasync::Task::local({
105            async move {
106            let _ = &context;
107            let id = fuchsia_trace::Id::new();
108            trace!(id, c"usage_counts_inspect_agent");
109            let event = message_rx.fuse();
110            let agent_event = context.receptor.fuse();
111            let mut usage_event = rx.borrow_mut();
112            futures::pin_mut!(agent_event, event);
113
114            loop {
115                futures::select! {
116                    message_event = event.select_next_some() => {
117                        trace!(
118                            id,
119                            c"message_event"
120                        );
121                        agent.process_message_event(message_event);
122                    },
123                    usage_event = usage_event.select_next_some() => {
124                        agent.process_usage_event(usage_event);
125                    },
126                    agent_message = agent_event.select_next_some() => {
127                        trace!(
128                            id,
129                            c"agent_event"
130                        );
131                        if let MessageEvent::Message(
132                                service::Payload::Agent(Payload::Invocation(_invocation)), client)
133                                = agent_message {
134                            // Since the agent runs at creation, there is no
135                            // need to handle state here.
136                            let _ = client.reply(Payload::Complete(Ok(())).into());
137                        }
138                    },
139                }
140            }
141        }})
142        .detach();
143    }
144
145    fn process_usage_event(&mut self, event: UsageEvent) {
146        // We only need to track incoming requests.
147        if let Direction::Response(..) = event.direction {
148            return;
149        }
150
151        let inspect_node = &self.inspect_node;
152        let setting_type_info = self
153            .api_call_counts
154            .entry(event.setting.to_string())
155            .or_insert_with(|| SettingTypeUsageInspectInfo::new(inspect_node, event.setting));
156
157        let key = event.request_type;
158        let usage = setting_type_info
159            .requests_by_type
160            .get_or_insert_with(format!("{key:?}"), UsageInfo::default);
161        let _ = usage.count.add(1);
162    }
163
164    /// Identifies [`service::message::MessageEvent`] that contains a [`Request`]
165    /// for setting handlers and counts [`Request`] to its API usage.
166    fn process_message_event(&mut self, event: service::message::MessageEvent) {
167        if let Ok((HandlerPayload::Request(request), client)) =
168            HandlerPayload::try_from_with_client(event)
169        {
170            if let service::message::Audience::Address(service::Address::Handler(setting_type)) =
171                client.get_audience()
172            {
173                self.record_usage(setting_type, &request);
174            }
175        }
176    }
177
178    /// Write a usage count to inspect.
179    fn record_usage(&mut self, setting_type: SettingType, request: &Request) {
180        let inspect_node = &self.inspect_node;
181        let setting_type_str = format!("{setting_type:?}");
182        let setting_type_info = self
183            .api_call_counts
184            .entry(setting_type_str.clone())
185            .or_insert_with(|| SettingTypeUsageInspectInfo::new(inspect_node, &setting_type_str));
186
187        let mut key = request.for_inspect();
188        if key.starts_with("Set") {
189            // Match all Set* requests to Set
190            key = "Set";
191        }
192        let usage = setting_type_info
193            .requests_by_type
194            .get_or_insert_with(key.to_string(), UsageInfo::default);
195        let _ = usage.count.add(1);
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::display::types::SetDisplayInfo;
203    use crate::intl::types::{IntlInfo, LocaleId, TemperatureUnit};
204
205    use diagnostics_assertions::assert_data_tree;
206    use futures::channel::mpsc;
207    use std::collections::HashSet;
208
209    /// The `RequestProcessor` handles sending a request through a MessageHub
210    /// From caller to recipient. This is useful when testing brokers in
211    /// between.
212    struct RequestProcessor {
213        delegate: service::message::Delegate,
214    }
215
216    impl RequestProcessor {
217        fn new(delegate: service::message::Delegate) -> Self {
218            RequestProcessor { delegate }
219        }
220
221        async fn send_and_receive(&self, setting_type: SettingType, setting_request: Request) {
222            let (messenger, _) =
223                self.delegate.create(MessengerType::Unbound).await.expect("should be created");
224
225            let (_, mut receptor) = self
226                .delegate
227                .create(MessengerType::Addressable(service::Address::Handler(setting_type)))
228                .await
229                .expect("should be created");
230
231            let _ = messenger.message(
232                HandlerPayload::Request(setting_request).into(),
233                service::message::Audience::Address(service::Address::Handler(setting_type)),
234            );
235
236            let _ = receptor.next_payload().await;
237        }
238    }
239
240    async fn create_context() -> Context {
241        Context::new(
242            service::MessageHub::create_hub()
243                .create(MessengerType::Unbound)
244                .await
245                .expect("should be present")
246                .1,
247            service::MessageHub::create_hub(),
248            HashSet::new(),
249        )
250        .await
251    }
252
253    #[fuchsia::test(allow_stalls = false)]
254    async fn test_inspect() {
255        let inspector = inspect::Inspector::default();
256        let inspect_node = inspector.root().create_child("api_usage_counts");
257        let context = create_context().await;
258
259        let request_processor = RequestProcessor::new(context.delegate.clone());
260
261        let (_tx, rx) = mpsc::unbounded();
262        SettingTypeUsageInspectAgent::create_with_node(
263            context,
264            Rc::new(RefCell::new(rx)),
265            inspect_node,
266        )
267        .await;
268
269        // Send a few requests to make sure they get written to inspect properly.
270        let turn_off_auto_brightness = Request::SetDisplayInfo(SetDisplayInfo {
271            auto_brightness: Some(false),
272            ..SetDisplayInfo::default()
273        });
274        request_processor
275            .send_and_receive(SettingType::Display, turn_off_auto_brightness.clone())
276            .await;
277
278        request_processor.send_and_receive(SettingType::Display, turn_off_auto_brightness).await;
279
280        for _ in 0..100 {
281            request_processor
282                .send_and_receive(
283                    SettingType::Intl,
284                    Request::SetIntlInfo(IntlInfo {
285                        locales: Some(vec![LocaleId { id: "en-US".to_string() }]),
286                        temperature_unit: Some(TemperatureUnit::Celsius),
287                        time_zone_id: Some("UTC".to_string()),
288                        hour_cycle: None,
289                    }),
290                )
291                .await;
292        }
293
294        assert_data_tree!(inspector, root: {
295            api_usage_counts: {
296                "Display": {
297                    "Set": {
298                        count: 2i64,
299                    },
300                },
301                "Intl": {
302                    "Set": {
303                       count: 100i64
304                    },
305                }
306            },
307        });
308    }
309
310    #[fuchsia::test(allow_stalls = false)]
311    async fn test_inspect_mixed_request_types() {
312        let inspector = inspect::Inspector::default();
313        let inspect_node = inspector.root().create_child("api_usage_counts");
314        let context = create_context().await;
315
316        let request_processor = RequestProcessor::new(context.delegate.clone());
317
318        let (_tx, rx) = mpsc::unbounded();
319        SettingTypeUsageInspectAgent::create_with_node(
320            context,
321            Rc::new(RefCell::new(rx)),
322            inspect_node,
323        )
324        .await;
325
326        // Interlace different request types to make sure the counter is correct.
327        request_processor
328            .send_and_receive(
329                SettingType::Display,
330                Request::SetDisplayInfo(SetDisplayInfo {
331                    auto_brightness: Some(false),
332                    ..SetDisplayInfo::default()
333                }),
334            )
335            .await;
336
337        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
338
339        request_processor
340            .send_and_receive(
341                SettingType::Display,
342                Request::SetDisplayInfo(SetDisplayInfo {
343                    auto_brightness: Some(true),
344                    ..SetDisplayInfo::default()
345                }),
346            )
347            .await;
348
349        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
350
351        assert_data_tree!(inspector, root: {
352            api_usage_counts: {
353                "Display": {
354                    "Set": {
355                        count: 2i64,
356                    },
357                    "Get": {
358                        count: 2i64,
359                    },
360                },
361            }
362        });
363    }
364}