settings/agent/inspect/
setting_proxy.rs

1// Copyright 2021 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 inspect mod defines the [SettingProxyInspectAgent], which is responsible for logging
6//! the contents of requests and responses, as well as timestamps and counts to Inspect. Since this
7//! activity might happen before agent lifecycle states are communicated (due to agent priority
8//! ordering), the [SettingProxyInspectAgent] begins listening to requests immediately after
9//! creation.
10//!
11//! [SettingProxyInspectAgent]: inspect::SettingProxyInspectAgent
12
13use crate::agent::{AgentCreator, Context, CreationFunc, Payload};
14use crate::base::{SettingInfo, SettingType};
15use crate::handler::base::{Error, Payload as HandlerPayload, Request};
16use crate::message::base::{MessageEvent, MessengerType};
17use crate::message::receptor::Receptor;
18use crate::service::TryFromWithClient;
19use crate::{clock, service, trace};
20use futures::channel::mpsc::UnboundedReceiver;
21use settings_common::inspect::event::{Direction, ResponseType, UsageEvent};
22use settings_inspect_utils::joinable_inspect_vecdeque::JoinableInspectVecDeque;
23use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
24use settings_inspect_utils::managed_inspect_queue::ManagedInspectQueue;
25
26use fuchsia_async as fasync;
27use fuchsia_inspect::{self as inspect, component, NumericProperty};
28use fuchsia_inspect_derive::{IValue, Inspect};
29use futures::stream::FuturesUnordered;
30use futures::StreamExt;
31use std::cell::RefCell;
32use std::rc::Rc;
33
34/// The maximum number of pending requests to store in inspect per setting. There should generally
35/// be fairly few of these unless a setting is changing rapidly, so a slightly larger size allows us
36/// to avoid dropping requests.
37const MAX_PENDING_REQUESTS: usize = 20;
38
39/// The maximum number of unique request + response pairs to store per request type in each setting.
40const MAX_REQUEST_RESPONSE_PAIRS: usize = 10;
41
42/// The maximum number of request or response timestamps to store per request + response pair.
43const MAX_REQUEST_RESPONSE_TIMESTAMPS: usize = 10;
44
45/// Name of the top-level node under root used to store the contents of requests and responses.
46const REQUEST_RESPONSE_NODE_NAME: &str = "requests_and_responses";
47
48/// Name of the top-level node under root used to store request counts.
49const RESPONSE_COUNTS_NODE_NAME: &str = "response_counts";
50
51pub(crate) fn create_registrar(rx: UnboundedReceiver<UsageEvent>) -> AgentCreator {
52    let rx = Rc::new(RefCell::new(rx));
53    AgentCreator {
54        debug_id: "SettingProxyInspectAgent",
55        create: CreationFunc::Dynamic(Rc::new(move |context| {
56            let rx = Rc::clone(&rx);
57            Box::pin(async move {
58                SettingProxyInspectAgent::create(context, rx).await;
59            })
60        })),
61    }
62}
63#[derive(Default, Inspect)]
64/// Information about response counts to be written to inspect.
65struct SettingTypeResponseCountInfo {
66    /// Map from the name of the ResponseType variant to a ResponseCountInfo that holds the number
67    /// of occurrences of that response.
68    #[inspect(forward)]
69    response_counts_by_type: ManagedInspectMap<ResponseTypeCount>,
70}
71
72#[derive(Default, Inspect)]
73/// Information about the number of responses of a given response type.
74struct ResponseTypeCount {
75    count: inspect::UintProperty,
76    inspect_node: inspect::Node,
77}
78
79/// Inspect information on the requests and responses of one setting.
80#[derive(Inspect)]
81struct SettingTypeRequestResponseInfo {
82    /// Map from request type to a map containing [RequestResponsePairInfo].
83    ///
84    /// The first-level map's keys are the enum variant names from [Request::to_inspect], to allow
85    /// different request types to be recorded separately. The second-level map's keys are the
86    /// concatenation of the debug representation of the request and response and holds up to
87    /// [REQUEST_RESPONSE_COUNT] of the most recently seen unique request + response pairs.
88    #[inspect(rename = "requests_and_responses")]
89    requests_and_responses_by_type: ManagedInspectMap<ManagedInspectMap<RequestResponsePairInfo>>,
90
91    /// Queue of pending requests that have been sent but have not received a response yet.
92    pending_requests: ManagedInspectQueue<PendingRequestInspectInfo>,
93
94    /// Inspect node to which this setting's data is written.
95    inspect_node: inspect::Node,
96
97    /// Incrementing count for all requests of this setting type.
98    ///
99    /// The same counter is used across all request types to easily see the order that requests
100    /// occurred in.
101    #[inspect(skip)]
102    count: u64,
103}
104
105impl SettingTypeRequestResponseInfo {
106    fn new() -> Self {
107        Self {
108            requests_and_responses_by_type: Default::default(),
109            pending_requests: ManagedInspectQueue::<PendingRequestInspectInfo>::new(
110                MAX_PENDING_REQUESTS,
111            ),
112            inspect_node: Default::default(),
113            count: 0,
114        }
115    }
116}
117
118/// Information to be written to inspect about a request that has not yet received a response.
119#[derive(Debug, Default, Inspect)]
120struct PendingRequestInspectInfo {
121    /// Debug string representation of the request.
122    request: IValue<String>,
123
124    /// The request type of the request, from [Request::to_inspect]. Used to bucket by request type
125    /// when recording responses.
126    #[inspect(skip)]
127    request_type: String,
128
129    /// Time this request was sent, in milliseconds of uptime. Uses the system monotonic clock
130    /// (zx_clock_get_monotonic).
131    timestamp: IValue<String>,
132
133    /// Request count within the setting for this request.
134    #[inspect(skip)]
135    count: u64,
136
137    /// Inspect node this request will be recorded at.
138    inspect_node: inspect::Node,
139}
140
141/// Information about a request and response pair to be written to inspect.
142///
143/// Timestamps are recorded upon receiving a response, so [request_timestamp] and
144/// [response_timestamp] will always be the same length and the timestamps at index N of each array
145/// belong to the same request + response round trip.
146#[derive(Default, Inspect)]
147struct RequestResponsePairInfo {
148    /// Debug string representation of the request.
149    request: IValue<String>,
150
151    /// Debug string representation of the response.
152    response: IValue<String>,
153
154    /// Request count of the most recently received request + response.
155    #[inspect(skip)]
156    request_count: u64,
157
158    /// List of timestamps at which this request was seen.
159    ///
160    /// Timestamps are recorded as milliseconds of system uptime. Uses the system monotonic clock
161    /// (zx_clock_get_monotonic).
162    request_timestamps: IValue<JoinableInspectVecDeque>,
163
164    /// List of timestamps at which this response was seen.
165    ///
166    /// Timestamps are recorded as milliseconds of system uptime. Uses the system monotonic clock
167    /// (zx_clock_get_monotonic).
168    response_timestamps: IValue<JoinableInspectVecDeque>,
169
170    /// Inspect node at which this info is stored.
171    inspect_node: inspect::Node,
172}
173
174impl RequestResponsePairInfo {
175    fn new(request: String, response: String, count: u64) -> Self {
176        Self {
177            request: IValue::new(request),
178            response: IValue::new(response),
179            request_count: count,
180            request_timestamps: Default::default(),
181            response_timestamps: Default::default(),
182            inspect_node: Default::default(),
183        }
184    }
185}
186
187/// The SettingProxyInspectAgent is responsible for listening to requests to the setting
188/// handlers and recording the requests and responses to Inspect.
189pub(crate) struct SettingProxyInspectAgent {
190    /// Response type accumulation info.
191    response_counts: ManagedInspectMap<SettingTypeResponseCountInfo>,
192
193    /// Information for each setting on requests and responses.
194    setting_request_response_info: ManagedInspectMap<SettingTypeRequestResponseInfo>,
195}
196
197impl SettingProxyInspectAgent {
198    pub(crate) async fn create(context: Context, rx: Rc<RefCell<UnboundedReceiver<UsageEvent>>>) {
199        Self::create_with_node(
200            context,
201            rx,
202            component::inspector().root().create_child(REQUEST_RESPONSE_NODE_NAME),
203            component::inspector().root().create_child(RESPONSE_COUNTS_NODE_NAME),
204        )
205        .await;
206    }
207
208    async fn create_with_node(
209        context: Context,
210        rx: Rc<RefCell<UnboundedReceiver<UsageEvent>>>,
211        request_response_inspect_node: inspect::Node,
212        response_counts_node: inspect::Node,
213    ) {
214        let (_, message_rx) = context
215            .delegate
216            .create(MessengerType::Broker(Rc::new(move |message| {
217                // Only catch setting handler requests.
218                matches!(message.payload(), service::Payload::Setting(HandlerPayload::Request(_)))
219            })))
220            .await
221            .expect("should receive client");
222
223        let mut agent = SettingProxyInspectAgent {
224            response_counts: ManagedInspectMap::<SettingTypeResponseCountInfo>::with_node(
225                response_counts_node,
226            ),
227            setting_request_response_info:
228                ManagedInspectMap::<SettingTypeRequestResponseInfo>::with_node(
229                    request_response_inspect_node,
230                ),
231        };
232
233        fasync::Task::local({
234            async move {
235            let _ = &context;
236            let id = fuchsia_trace::Id::new();
237            trace!(id, c"setting_proxy_inspect_agent");
238            let event = message_rx.fuse();
239            let agent_event = context.receptor.fuse();
240            let mut usage_event = rx.borrow_mut();
241            futures::pin_mut!(agent_event, event);
242
243            // Push reply_receptor to the FutureUnordered to avoid blocking codes when there are no
244            // responses replied back.
245            let mut unordered = FuturesUnordered::new();
246            loop {
247                futures::select! {
248                    message_event = event.select_next_some() => {
249                        trace!(
250                            id,
251                            c"message_event"
252                        );
253                        if let Some((setting_type, count, mut reply_receptor)) =
254                            agent.process_message_event(message_event) {
255                                unordered.push(async move {
256                                    let payload = reply_receptor.next_payload().await;
257                                    (setting_type, count, payload)
258                                });
259                        };
260                    },
261                    usage_event = usage_event.select_next_some() => {
262                        if let Direction::Request(_) = usage_event.direction {
263                            agent.process_usage_event(usage_event);
264                        } else {
265                            agent.process_usage_response_event(usage_event);
266                        }
267                    }
268                    reply = unordered.select_next_some() => {
269                        let (setting_type, count, payload) = reply;
270                        if let Ok((
271                            service::Payload::Setting(
272                                HandlerPayload::Response(response)),
273                            _,
274                        )) = payload
275                        {
276                            agent.record_response(setting_type, count, response);
277                        }
278                    },
279                    agent_message = agent_event.select_next_some() => {
280                        trace!(
281                            id,
282                            c"agent_event"
283                        );
284                        if let MessageEvent::Message(
285                                service::Payload::Agent(Payload::Invocation(_invocation)), client)
286                                = agent_message {
287                            // Since the agent runs at creation, there is no
288                            // need to handle state here.
289                            let _ = client.reply(Payload::Complete(Ok(())).into());
290                        }
291                    },
292                }
293            }
294        }})
295        .detach();
296    }
297
298    fn process_usage_event(&mut self, event: UsageEvent) {
299        let timestamp = clock::inspect_format_now();
300
301        // Get or create the info for this setting type.
302        let request_response_info = self
303            .setting_request_response_info
304            .get_or_insert_with(event.setting.to_string(), SettingTypeRequestResponseInfo::new);
305
306        request_response_info.count += 1;
307
308        let Direction::Request(request) = event.direction else {
309            panic!("Call process_usage_event with response!");
310        };
311        let pending_request_info = PendingRequestInspectInfo {
312            request: request.into(),
313            request_type: format!("{:?}", event.request_type),
314            timestamp: timestamp.into(),
315            count: event.id,
316            inspect_node: inspect::Node::default(),
317        };
318
319        let count_key = format!("{:020}", event.id);
320        request_response_info.pending_requests.push(&count_key, pending_request_info);
321    }
322
323    fn process_usage_response_event(&mut self, event: UsageEvent) {
324        let setting_type_str = event.setting.to_string();
325        let timestamp = clock::inspect_format_now();
326        let Direction::Response(response, response_type) = event.direction else {
327            panic!("Called process_usage_response_event with a request!");
328        };
329
330        // Update the response counter.
331        self.increment_response_count(setting_type_str.clone(), response_type);
332
333        // Find the inspect data for this setting. This should always be present as it's created
334        // upon receiving a request, which should happen before the response is recorded.
335        let condensed_setting_type_info = self
336            .setting_request_response_info
337            .map_mut()
338            .get_mut(&setting_type_str)
339            .expect("Missing info for request");
340
341        let pending_requests = &mut condensed_setting_type_info.pending_requests;
342
343        // Find the position of the pending request with the same request count and remove it. This
344        // should generally be the first pending request in the queue if requests are being answered
345        // in order.
346        let position = match pending_requests.iter_mut().position(|info| info.count == event.id) {
347            Some(position) => position,
348            None => {
349                // We may be unable to find a matching request if requests are piling up faster than
350                // responses, as the number of pending requests is limited.
351                return;
352            }
353        };
354        let pending =
355            pending_requests.items_mut().remove(position).expect("Failed to find pending item");
356
357        // Find the info for this particular request type.
358        let request_type_info_map = condensed_setting_type_info
359            .requests_and_responses_by_type
360            .get_or_insert_with(pending.request_type, || {
361                ManagedInspectMap::<RequestResponsePairInfo>::default()
362            });
363
364        // Request and response pairs are keyed by the concatenation of the request and response,
365        // which uniquely identifies them within a setting.
366        let map_key = format!("{:?}{:?}", pending.request, response);
367
368        // Find this request + response pair in the map and remove it, if it's present. While the
369        // map key is the request + response concatenated, the key displayed in inspect is the
370        // newest request count for that pair. We remove the map entry if it exists so that we can
371        // re-insert to update the key displayed in inspect.
372        let removed_info = request_type_info_map.map_mut().remove(&map_key);
373
374        let mut info = removed_info.unwrap_or_else(|| {
375            RequestResponsePairInfo::new(pending.request.into_inner(), response, pending.count)
376        });
377        {
378            // Update the request and response timestamps. We have borrow from the IValues with
379            // as_mut and drop the variables after this scope ends so that the IValues will know to
380            // update the values in inspect.
381            let mut_requests = &mut info.request_timestamps.as_mut().0;
382            let mut_responses = &mut info.response_timestamps.as_mut().0;
383
384            mut_requests.push_back(pending.timestamp.into_inner());
385            mut_responses.push_back(timestamp);
386
387            // If there are too many timestamps, remove earlier ones.
388            if mut_requests.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
389                let _ = mut_requests.pop_front();
390            }
391            if mut_responses.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
392                let _ = mut_responses.pop_front();
393            }
394        }
395
396        // Insert into the map, but display the key in inspect as the request count.
397        let count_key = format!("{:020}", pending.count);
398        let _ = request_type_info_map.insert_with_property_name(map_key, count_key, info);
399
400        // If there are too many entries, find and remove the oldest.
401        let num_request_response_pairs = request_type_info_map.map().len();
402        if num_request_response_pairs > MAX_REQUEST_RESPONSE_PAIRS {
403            // Find the item with the lowest request count, which means it was the oldest request
404            // received.
405            let mut lowest_count: u64 = u64::MAX;
406            let mut lowest_key: Option<String> = None;
407            for (key, inspect_info) in request_type_info_map.map() {
408                if inspect_info.request_count < lowest_count {
409                    lowest_count = inspect_info.request_count;
410                    lowest_key = Some(key.clone());
411                }
412            }
413
414            if let Some(key_to_remove) = lowest_key {
415                let _ = request_type_info_map.map_mut().remove(&key_to_remove);
416            }
417        }
418    }
419
420    /// Identfies [`service::message::MessageEvent`] that contains a [`Request`]
421    /// for setting handlers and records the [`Request`].
422    fn process_message_event(
423        &mut self,
424        event: service::message::MessageEvent,
425    ) -> Option<(SettingType, u64, Receptor)> {
426        if let Ok((HandlerPayload::Request(request), mut client)) =
427            HandlerPayload::try_from_with_client(event)
428        {
429            if let service::message::Audience::Address(service::Address::Handler(setting_type)) =
430                client.get_audience()
431            {
432                // A Listen request will always send a Get request. We can always get the Get's
433                // response. However, Listen will return the Get's response only when it is
434                // considered updated. Therefore, we ignore Listen response.
435                if request != Request::Listen {
436                    let count = self.record_request(setting_type, request);
437                    return Some((setting_type, count, client.spawn_observer()));
438                }
439            }
440        }
441        None
442    }
443
444    /// Writes a pending request to inspect.
445    ///
446    /// Returns the request count of this request.
447    fn record_request(&mut self, setting_type: SettingType, request: Request) -> u64 {
448        let setting_type_str = format!("{setting_type:?}");
449        let timestamp = clock::inspect_format_now();
450
451        // Get or create the info for this setting type.
452        let request_response_info = self
453            .setting_request_response_info
454            .get_or_insert_with(setting_type_str, SettingTypeRequestResponseInfo::new);
455
456        let request_count = request_response_info.count;
457        request_response_info.count += 1;
458
459        let pending_request_info = PendingRequestInspectInfo {
460            request: format!("{request:?}").into(),
461            request_type: request.for_inspect().to_string(),
462            timestamp: timestamp.into(),
463            count: request_count,
464            inspect_node: inspect::Node::default(),
465        };
466
467        let count_key = format!("{request_count:020}");
468        request_response_info.pending_requests.push(&count_key, pending_request_info);
469
470        request_count
471    }
472
473    /// Writes a response to inspect, matching it up with an already recorded request + response
474    /// pair if possible.
475    fn record_response(
476        &mut self,
477        setting_type: SettingType,
478        count: u64,
479        response: Result<Option<SettingInfo>, Error>,
480    ) {
481        let setting_type_str = format!("{setting_type:?}");
482        let timestamp = clock::inspect_format_now();
483
484        // Update the response counter.
485        self.increment_response_count(
486            setting_type_str.clone(),
487            match &response {
488                Ok(Some(_)) => ResponseType::OkSome,
489                Ok(None) => ResponseType::OkNone,
490                Err(e) => ResponseType::from(e.clone()),
491            },
492        );
493
494        // Find the inspect data for this setting. This should always be present as it's created
495        // upon receiving a request, which should happen before the response is recorded.
496        let condensed_setting_type_info = self
497            .setting_request_response_info
498            .map_mut()
499            .get_mut(&setting_type_str)
500            .expect("Missing info for request");
501
502        let pending_requests = &mut condensed_setting_type_info.pending_requests;
503
504        // Find the position of the pending request with the same request count and remove it. This
505        // should generally be the first pending request in the queue if requests are being answered
506        // in order.
507        let position = match pending_requests.iter_mut().position(|info| info.count == count) {
508            Some(position) => position,
509            None => {
510                // We may be unable to find a matching request if requests are piling up faster than
511                // responses, as the number of pending requests is limited.
512                return;
513            }
514        };
515        let pending =
516            pending_requests.items_mut().remove(position).expect("Failed to find pending item");
517
518        // Find the info for this particular request type.
519        let request_type_info_map = condensed_setting_type_info
520            .requests_and_responses_by_type
521            .get_or_insert_with(pending.request_type, || {
522                ManagedInspectMap::<RequestResponsePairInfo>::default()
523            });
524
525        // Request and response pairs are keyed by the concatenation of the request and response,
526        // which uniquely identifies them within a setting.
527        let map_key = format!("{:?}{:?}", pending.request, response);
528
529        // Find this request + response pair in the map and remove it, if it's present. While the
530        // map key is the request + response concatenated, the key displayed in inspect is the
531        // newest request count for that pair. We remove the map entry if it exists so that we can
532        // re-insert to update the key displayed in inspect.
533        let removed_info = request_type_info_map.map_mut().remove(&map_key);
534
535        let response_str = format!("{response:?}");
536        let mut info = removed_info.unwrap_or_else(|| {
537            RequestResponsePairInfo::new(pending.request.into_inner(), response_str, pending.count)
538        });
539        {
540            // Update the request and response timestamps. We have borrow from the IValues with
541            // as_mut and drop the variables after this scope ends so that the IValues will know to
542            // update the values in inspect.
543            let mut_requests = &mut info.request_timestamps.as_mut().0;
544            let mut_responses = &mut info.response_timestamps.as_mut().0;
545
546            mut_requests.push_back(pending.timestamp.into_inner());
547            mut_responses.push_back(timestamp);
548
549            // If there are too many timestamps, remove earlier ones.
550            if mut_requests.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
551                let _ = mut_requests.pop_front();
552            }
553            if mut_responses.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
554                let _ = mut_responses.pop_front();
555            }
556        }
557
558        // Insert into the map, but display the key in inspect as the request count.
559        let count_key = format!("{:020}", pending.count);
560        let _ = request_type_info_map.insert_with_property_name(map_key, count_key, info);
561
562        // If there are too many entries, find and remove the oldest.
563        let num_request_response_pairs = request_type_info_map.map().len();
564        if num_request_response_pairs > MAX_REQUEST_RESPONSE_PAIRS {
565            // Find the item with the lowest request count, which means it was the oldest request
566            // received.
567            let mut lowest_count: u64 = u64::MAX;
568            let mut lowest_key: Option<String> = None;
569            for (key, inspect_info) in request_type_info_map.map() {
570                if inspect_info.request_count < lowest_count {
571                    lowest_count = inspect_info.request_count;
572                    lowest_key = Some(key.clone());
573                }
574            }
575
576            if let Some(key_to_remove) = lowest_key {
577                let _ = request_type_info_map.map_mut().remove(&key_to_remove);
578            }
579        }
580    }
581
582    fn increment_response_count(&mut self, setting_type_str: String, response_type: ResponseType) {
583        // Get the response count info for the setting type, creating a new info object
584        // if it doesn't exist in the map yet.
585        let response_count_info = self
586            .response_counts
587            .get_or_insert_with(setting_type_str, SettingTypeResponseCountInfo::default);
588
589        // Get the count for the response type, creating a new count if it doesn't exist
590        // in the map yet, then increment the response count
591        let response_count = response_count_info
592            .response_counts_by_type
593            .get_or_insert_with(format!("{response_type:?}"), ResponseTypeCount::default);
594        let _ = response_count.count.add(1u64);
595    }
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601    use crate::display::types::SetDisplayInfo;
602    use crate::intl::types::{IntlInfo, LocaleId, TemperatureUnit};
603    use diagnostics_assertions::{assert_data_tree, TreeAssertion};
604    use futures::channel::mpsc;
605    use std::collections::HashSet;
606    use zx::MonotonicInstant;
607
608    /// The `RequestProcessor` handles sending a request through a MessageHub
609    /// From caller to recipient. This is useful when testing brokers in
610    /// between.
611    struct RequestProcessor {
612        delegate: service::message::Delegate,
613    }
614
615    impl RequestProcessor {
616        fn new(delegate: service::message::Delegate) -> Self {
617            RequestProcessor { delegate }
618        }
619
620        async fn send_request(
621            &self,
622            setting_type: SettingType,
623            setting_request: Request,
624            should_reply: bool,
625        ) {
626            let (messenger, _) =
627                self.delegate.create(MessengerType::Unbound).await.expect("should be created");
628
629            let (_, mut receptor) = self
630                .delegate
631                .create(MessengerType::Addressable(service::Address::Handler(setting_type)))
632                .await
633                .expect("should be created");
634
635            let mut reply_receptor = messenger.message(
636                HandlerPayload::Request(setting_request).into(),
637                service::message::Audience::Address(service::Address::Handler(setting_type)),
638            );
639
640            if let Some(message_event) = futures::StreamExt::next(&mut receptor).await {
641                if let Ok((_, reply_client)) = HandlerPayload::try_from_with_client(message_event) {
642                    if should_reply {
643                        let _ = reply_client.reply(HandlerPayload::Response(Ok(None)).into());
644                    }
645                }
646            }
647            let _ = reply_receptor.next_payload().await;
648        }
649
650        async fn send_and_receive(&self, setting_type: SettingType, setting_request: Request) {
651            self.send_request(setting_type, setting_request, true).await;
652        }
653    }
654
655    async fn create_context() -> Context {
656        Context::new(
657            service::MessageHub::create_hub()
658                .create(MessengerType::Unbound)
659                .await
660                .expect("should be present")
661                .1,
662            service::MessageHub::create_hub(),
663            HashSet::new(),
664        )
665        .await
666    }
667
668    // Verifies that request + response pairs with the same value and request type are grouped
669    // together.
670    #[fuchsia::test(allow_stalls = false)]
671    async fn test_inspect_grouped_responses() {
672        // Set the clock so that timestamps can be controlled.
673        clock::mock::set(MonotonicInstant::from_nanos(0));
674
675        let inspector = inspect::Inspector::default();
676        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
677        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
678        let context = create_context().await;
679
680        let request_processor = RequestProcessor::new(context.delegate.clone());
681
682        let (_tx, rx) = mpsc::unbounded();
683        SettingProxyInspectAgent::create_with_node(
684            context,
685            Rc::new(RefCell::new(rx)),
686            condense_node,
687            response_counts_node,
688        )
689        .await;
690
691        // Send a request to turn off auto brightness.
692        let turn_off_auto_brightness = Request::SetDisplayInfo(SetDisplayInfo {
693            auto_brightness: Some(false),
694            ..SetDisplayInfo::default()
695        });
696        request_processor
697            .send_and_receive(SettingType::Display, turn_off_auto_brightness.clone())
698            .await;
699
700        // Increment clock and send a request to turn on auto brightness.
701        clock::mock::set(MonotonicInstant::from_nanos(100));
702        request_processor
703            .send_and_receive(
704                SettingType::Display,
705                Request::SetDisplayInfo(SetDisplayInfo {
706                    auto_brightness: Some(true),
707                    ..SetDisplayInfo::default()
708                }),
709            )
710            .await;
711
712        // Increment clock and send the same request as the first one. The two should be grouped
713        // together.
714        clock::mock::set(MonotonicInstant::from_nanos(200));
715        request_processor.send_and_receive(SettingType::Display, turn_off_auto_brightness).await;
716
717        assert_data_tree!(inspector, root: contains {
718            requests_and_responses: {
719                "Display": {
720                    "pending_requests": {},
721                    "requests_and_responses": {
722                        "SetDisplayInfo": {
723                            "00000000000000000001": {
724                                "request": "SetDisplayInfo(SetDisplayInfo { \
725                                    manual_brightness_value: None, \
726                                    auto_brightness_value: None, \
727                                    auto_brightness: Some(true), \
728                                    screen_enabled: None, \
729                                    low_light_mode: None, \
730                                    theme: None \
731                                })",
732                                "request_timestamps": "0.000000100",
733                                "response": "Ok(None)",
734                                "response_timestamps": "0.000000100"
735                            },
736                            "00000000000000000002": {
737                                "request": "SetDisplayInfo(SetDisplayInfo { \
738                                    manual_brightness_value: None, \
739                                    auto_brightness_value: None, \
740                                    auto_brightness: Some(false), \
741                                    screen_enabled: None, \
742                                    low_light_mode: None, \
743                                    theme: None \
744                                })",
745                                "request_timestamps": "0.000000000,0.000000200",
746                                "response": "Ok(None)",
747                                "response_timestamps": "0.000000000,0.000000200"
748                            }
749                        }
750                    }
751                }
752            },
753        });
754    }
755
756    // Test that multiple requests of different request types for the same setting records the
757    // correct inspect data.
758    #[fuchsia::test(allow_stalls = false)]
759    async fn test_inspect_mixed_request_types() {
760        // Set the clock so that timestamps can be controlled.
761        clock::mock::set(MonotonicInstant::from_nanos(0));
762
763        let inspector = inspect::Inspector::default();
764        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
765        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
766        let context = create_context().await;
767
768        let request_processor = RequestProcessor::new(context.delegate.clone());
769
770        let (_tx, rx) = mpsc::unbounded();
771        SettingProxyInspectAgent::create_with_node(
772            context,
773            Rc::new(RefCell::new(rx)),
774            condense_node,
775            response_counts_node,
776        )
777        .await;
778
779        // Interlace different request types to make sure the counter is correct.
780        request_processor
781            .send_and_receive(
782                SettingType::Display,
783                Request::SetDisplayInfo(SetDisplayInfo {
784                    auto_brightness: Some(false),
785                    ..SetDisplayInfo::default()
786                }),
787            )
788            .await;
789
790        // Set to a different time so that a response can correctly link to its request.
791        clock::mock::set(MonotonicInstant::from_nanos(100));
792        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
793
794        // Set to a different time so that a response can correctly link to its request.
795        clock::mock::set(MonotonicInstant::from_nanos(200));
796        request_processor
797            .send_and_receive(
798                SettingType::Display,
799                Request::SetDisplayInfo(SetDisplayInfo {
800                    auto_brightness: Some(true),
801                    ..SetDisplayInfo::default()
802                }),
803            )
804            .await;
805
806        clock::mock::set(MonotonicInstant::from_nanos(300));
807        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
808
809        assert_data_tree!(inspector, root: contains {
810            requests_and_responses: {
811                "Display": {
812                    "pending_requests": {},
813                    "requests_and_responses": {
814                        "Get": {
815                            "00000000000000000003": {
816                                "request": "Get",
817                                "request_timestamps": "0.000000100,0.000000300",
818                                "response": "Ok(None)",
819                                "response_timestamps": "0.000000100,0.000000300"
820                            }
821                        },
822                        "SetDisplayInfo": {
823                            "00000000000000000000": {
824                                "request": "SetDisplayInfo(SetDisplayInfo { \
825                                    manual_brightness_value: None, \
826                                    auto_brightness_value: None, \
827                                    auto_brightness: Some(false), \
828                                    screen_enabled: None, \
829                                    low_light_mode: None, \
830                                    theme: None \
831                                })",
832                                  "request_timestamps": "0.000000000",
833                                  "response": "Ok(None)",
834                                  "response_timestamps": "0.000000000"
835                            },
836                            "00000000000000000002": {
837                                "request": "SetDisplayInfo(SetDisplayInfo { \
838                                    manual_brightness_value: None, \
839                                    auto_brightness_value: None, \
840                                    auto_brightness: Some(true), \
841                                    screen_enabled: None, \
842                                    low_light_mode: None, \
843                                    theme: None \
844                                })",
845                                "request_timestamps": "0.000000200",
846                                "response": "Ok(None)",
847                                "response_timestamps": "0.000000200"
848                            }
849                        }
850                    }
851                }
852            },
853            response_counts: {
854                "Display": {
855                    "OkNone": {
856                        count: 4u64,
857                    }
858                },
859            },
860        });
861    }
862
863    #[fuchsia::test(allow_stalls = false)]
864    async fn test_pending_request() {
865        // Set the clock so that timestamps can be controlled.
866        clock::mock::set(MonotonicInstant::from_nanos(0));
867
868        let inspector = inspect::Inspector::default();
869        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
870        let request_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
871        let context = create_context().await;
872
873        let request_processor = RequestProcessor::new(context.delegate.clone());
874
875        let (_tx, rx) = mpsc::unbounded();
876        SettingProxyInspectAgent::create_with_node(
877            context,
878            Rc::new(RefCell::new(rx)),
879            condense_node,
880            request_counts_node,
881        )
882        .await;
883
884        request_processor
885            .send_request(
886                SettingType::Display,
887                Request::SetDisplayInfo(SetDisplayInfo {
888                    auto_brightness: Some(false),
889                    ..SetDisplayInfo::default()
890                }),
891                false,
892            )
893            .await;
894
895        assert_data_tree!(inspector, root: contains {
896            requests_and_responses: {
897                "Display": {
898                    "pending_requests": {
899                        "00000000000000000000": {
900                            "request": "SetDisplayInfo(SetDisplayInfo { \
901                                manual_brightness_value: None, \
902                                auto_brightness_value: None, \
903                                auto_brightness: Some(false), \
904                                screen_enabled: None, \
905                                low_light_mode: None, \
906                                theme: None \
907                            })",
908                            "timestamp": "0.000000000",
909                        }
910                    },
911                    "requests_and_responses": {}
912                }
913            },
914        });
915    }
916
917    #[fuchsia::test(allow_stalls = false)]
918    async fn test_response_counts_inspect() {
919        // Set the clock so that timestamps can be controlled.
920        clock::mock::set(MonotonicInstant::from_nanos(0));
921
922        let inspector = inspect::Inspector::default();
923        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
924        let request_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
925        let context = create_context().await;
926
927        let request_processor = RequestProcessor::new(context.delegate.clone());
928
929        let (_tx, rx) = mpsc::unbounded();
930        SettingProxyInspectAgent::create_with_node(
931            context,
932            Rc::new(RefCell::new(rx)),
933            condense_node,
934            request_counts_node,
935        )
936        .await;
937
938        request_processor
939            .send_and_receive(
940                SettingType::Display,
941                Request::SetDisplayInfo(SetDisplayInfo {
942                    auto_brightness: Some(false),
943                    ..SetDisplayInfo::default()
944                }),
945            )
946            .await;
947
948        clock::mock::set(MonotonicInstant::from_nanos(100));
949        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
950
951        clock::mock::set(MonotonicInstant::from_nanos(200));
952        request_processor
953            .send_and_receive(
954                SettingType::Display,
955                Request::SetDisplayInfo(SetDisplayInfo {
956                    auto_brightness: None,
957                    ..SetDisplayInfo::default()
958                }),
959            )
960            .await;
961
962        clock::mock::set(MonotonicInstant::from_nanos(300));
963        request_processor.send_and_receive(SettingType::Display, Request::Get).await;
964
965        assert_data_tree!(inspector, root: contains {
966            response_counts: {
967                "Display": {
968                    "OkNone": {
969                        count: 4u64,
970                    },
971                },
972            },
973        });
974    }
975
976    // Verifies that old requests are dropped after MAX_REQUEST_RESPONSE_PAIRS are received for a
977    // given request + response pair.
978    #[fuchsia::test(allow_stalls = false)]
979    async fn inspect_queue_test() {
980        // Set the clock so that timestamps will always be 0.
981        clock::mock::set(MonotonicInstant::from_nanos(0));
982        let inspector = inspect::Inspector::default();
983        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
984        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
985        let context = create_context().await;
986        let request_processor = RequestProcessor::new(context.delegate.clone());
987
988        let (_tx, rx) = mpsc::unbounded();
989        SettingProxyInspectAgent::create_with_node(
990            context,
991            Rc::new(RefCell::new(rx)),
992            condense_node,
993            response_counts_node,
994        )
995        .await;
996
997        request_processor
998            .send_and_receive(
999                SettingType::Intl,
1000                Request::SetIntlInfo(IntlInfo {
1001                    locales: Some(vec![LocaleId { id: "en-US".to_string() }]),
1002                    temperature_unit: Some(TemperatureUnit::Celsius),
1003                    time_zone_id: Some("UTC".to_string()),
1004                    hour_cycle: None,
1005                }),
1006            )
1007            .await;
1008
1009        // Send one more than the max requests to make sure they get pushed off the end of the
1010        // queue. The requests must have different values to avoid getting grouped together.
1011        for i in 0..MAX_REQUEST_RESPONSE_PAIRS + 1 {
1012            request_processor
1013                .send_and_receive(
1014                    SettingType::Display,
1015                    Request::SetDisplayInfo(SetDisplayInfo {
1016                        manual_brightness_value: Some((i as f32) / 100f32),
1017                        ..SetDisplayInfo::default()
1018                    }),
1019                )
1020                .await;
1021        }
1022
1023        // Ensures we have INSPECT_REQUESTS_COUNT items and that the queue dropped the earliest one
1024        // when hitting the limit.
1025        fn display_subtree_assertion() -> TreeAssertion {
1026            let mut tree_assertion = TreeAssertion::new("Display", false);
1027            let mut request_response_assertion = TreeAssertion::new("requests_and_responses", true);
1028            let mut request_assertion = TreeAssertion::new("SetDisplayInfo", true);
1029
1030            for i in 1..MAX_REQUEST_RESPONSE_PAIRS + 1 {
1031                // We don't need to set clock here since we don't do exact match.
1032                request_assertion
1033                    .add_child_assertion(TreeAssertion::new(&format!("{i:020}"), false));
1034            }
1035            request_response_assertion.add_child_assertion(request_assertion);
1036            tree_assertion.add_child_assertion(request_response_assertion);
1037            tree_assertion
1038        }
1039
1040        assert_data_tree!(inspector, root: contains {
1041            requests_and_responses: {
1042                display_subtree_assertion(),
1043                "Intl": {
1044                    "pending_requests": {},
1045                    "requests_and_responses": {
1046                        "SetIntlInfo": {
1047                            "00000000000000000000": {
1048                                "request": "SetIntlInfo(IntlInfo { \
1049                                    locales: Some([LocaleId { id: \"en-US\" }]), \
1050                                    temperature_unit: Some(Celsius), \
1051                                    time_zone_id: Some(\"UTC\"), \
1052                                    hour_cycle: None \
1053                                })",
1054                                "request_timestamps": "0.000000000",
1055                                "response": "Ok(None)",
1056                                "response_timestamps": "0.000000000"
1057                            }
1058                        }
1059                    }
1060                }
1061            },
1062        });
1063    }
1064}