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::clock;
14use fuchsia_async as fasync;
15use fuchsia_inspect::{self as inspect, NumericProperty, component};
16use fuchsia_inspect_derive::{IValue, Inspect};
17use futures::StreamExt;
18use futures::channel::mpsc::UnboundedReceiver;
19#[cfg(test)]
20use futures::channel::mpsc::UnboundedSender;
21use settings_common::inspect::event::{Direction, ResponseType, UsageEvent};
22use settings_common::trace;
23use settings_inspect_utils::joinable_inspect_vecdeque::JoinableInspectVecDeque;
24use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
25use settings_inspect_utils::managed_inspect_queue::ManagedInspectQueue;
26
27/// The maximum number of pending requests to store in inspect per setting. There should generally
28/// be fairly few of these unless a setting is changing rapidly, so a slightly larger size allows us
29/// to avoid dropping requests.
30const MAX_PENDING_REQUESTS: usize = 20;
31
32/// The maximum number of unique request + response pairs to store per request type in each setting.
33const MAX_REQUEST_RESPONSE_PAIRS: usize = 10;
34
35/// The maximum number of request or response timestamps to store per request + response pair.
36const MAX_REQUEST_RESPONSE_TIMESTAMPS: usize = 10;
37
38/// Name of the top-level node under root used to store the contents of requests and responses.
39const REQUEST_RESPONSE_NODE_NAME: &str = "requests_and_responses";
40
41/// Name of the top-level node under root used to store request counts.
42const RESPONSE_COUNTS_NODE_NAME: &str = "response_counts";
43
44#[derive(Default, Inspect)]
45/// Information about response counts to be written to inspect.
46struct SettingTypeResponseCountInfo {
47    /// Map from the name of the ResponseType variant to a ResponseCountInfo that holds the number
48    /// of occurrences of that response.
49    #[inspect(forward)]
50    response_counts_by_type: ManagedInspectMap<ResponseTypeCount>,
51}
52
53#[derive(Default, Inspect)]
54/// Information about the number of responses of a given response type.
55struct ResponseTypeCount {
56    count: inspect::UintProperty,
57    inspect_node: inspect::Node,
58}
59
60/// Inspect information on the requests and responses of one setting.
61#[derive(Inspect)]
62struct SettingTypeRequestResponseInfo {
63    /// Map from request type to a map containing [RequestResponsePairInfo].
64    ///
65    /// The first-level map's keys are the enum variant names from [Request::to_inspect], to allow
66    /// different request types to be recorded separately. The second-level map's keys are the
67    /// concatenation of the debug representation of the request and response and holds up to
68    /// [REQUEST_RESPONSE_COUNT] of the most recently seen unique request + response pairs.
69    #[inspect(rename = "requests_and_responses")]
70    requests_and_responses_by_type: ManagedInspectMap<ManagedInspectMap<RequestResponsePairInfo>>,
71
72    /// Queue of pending requests that have been sent but have not received a response yet.
73    pending_requests: ManagedInspectQueue<PendingRequestInspectInfo>,
74
75    /// Inspect node to which this setting's data is written.
76    inspect_node: inspect::Node,
77
78    /// Incrementing count for all requests of this setting type.
79    ///
80    /// The same counter is used across all request types to easily see the order that requests
81    /// occurred in.
82    #[inspect(skip)]
83    count: u64,
84}
85
86impl SettingTypeRequestResponseInfo {
87    fn new() -> Self {
88        Self {
89            requests_and_responses_by_type: Default::default(),
90            pending_requests: ManagedInspectQueue::<PendingRequestInspectInfo>::new(
91                MAX_PENDING_REQUESTS,
92            ),
93            inspect_node: Default::default(),
94            count: 0,
95        }
96    }
97}
98
99/// Information to be written to inspect about a request that has not yet received a response.
100#[derive(Debug, Default, Inspect)]
101struct PendingRequestInspectInfo {
102    /// Debug string representation of the request.
103    request: IValue<String>,
104
105    /// The request type of the request, from [Request::to_inspect]. Used to bucket by request type
106    /// when recording responses.
107    #[inspect(skip)]
108    request_type: String,
109
110    /// Time this request was sent, in milliseconds of uptime. Uses the system monotonic clock
111    /// (zx_clock_get_monotonic).
112    timestamp: IValue<String>,
113
114    /// Request count within the setting for this request.
115    #[inspect(skip)]
116    count: u64,
117
118    /// Inspect node this request will be recorded at.
119    inspect_node: inspect::Node,
120}
121
122/// Information about a request and response pair to be written to inspect.
123///
124/// Timestamps are recorded upon receiving a response, so [request_timestamp] and
125/// [response_timestamp] will always be the same length and the timestamps at index N of each array
126/// belong to the same request + response round trip.
127#[derive(Default, Inspect)]
128struct RequestResponsePairInfo {
129    /// Debug string representation of the request.
130    request: IValue<String>,
131
132    /// Debug string representation of the response.
133    response: IValue<String>,
134
135    /// Request count of the most recently received request + response.
136    #[inspect(skip)]
137    request_count: u64,
138
139    /// List of timestamps at which this request was seen.
140    ///
141    /// Timestamps are recorded as milliseconds of system uptime. Uses the system monotonic clock
142    /// (zx_clock_get_monotonic).
143    request_timestamps: IValue<JoinableInspectVecDeque>,
144
145    /// List of timestamps at which this response was seen.
146    ///
147    /// Timestamps are recorded as milliseconds of system uptime. Uses the system monotonic clock
148    /// (zx_clock_get_monotonic).
149    response_timestamps: IValue<JoinableInspectVecDeque>,
150
151    /// Inspect node at which this info is stored.
152    inspect_node: inspect::Node,
153}
154
155impl RequestResponsePairInfo {
156    fn new(request: String, response: String, count: u64) -> Self {
157        Self {
158            request: IValue::new(request),
159            response: IValue::new(response),
160            request_count: count,
161            request_timestamps: Default::default(),
162            response_timestamps: Default::default(),
163            inspect_node: Default::default(),
164        }
165    }
166}
167
168/// The SettingProxyInspectAgent is responsible for listening to requests to the setting
169/// handlers and recording the requests and responses to Inspect.
170pub(crate) struct SettingProxyInspectAgent {
171    /// Response type accumulation info.
172    response_counts: ManagedInspectMap<SettingTypeResponseCountInfo>,
173
174    /// Information for each setting on requests and responses.
175    setting_request_response_info: ManagedInspectMap<SettingTypeRequestResponseInfo>,
176
177    usage_rx: Option<UnboundedReceiver<UsageEvent>>,
178
179    #[cfg(test)]
180    done_tx: Option<UnboundedSender<()>>,
181}
182
183impl SettingProxyInspectAgent {
184    pub fn new(rx: UnboundedReceiver<UsageEvent>) -> Self {
185        Self::create_with_node(
186            rx,
187            component::inspector().root().create_child(REQUEST_RESPONSE_NODE_NAME),
188            component::inspector().root().create_child(RESPONSE_COUNTS_NODE_NAME),
189            #[cfg(test)]
190            None,
191        )
192    }
193
194    fn create_with_node(
195        rx: UnboundedReceiver<UsageEvent>,
196        request_response_inspect_node: inspect::Node,
197        response_counts_node: inspect::Node,
198        #[cfg(test)] done_tx: Option<UnboundedSender<()>>,
199    ) -> Self {
200        SettingProxyInspectAgent {
201            response_counts: ManagedInspectMap::<SettingTypeResponseCountInfo>::with_node(
202                response_counts_node,
203            ),
204            setting_request_response_info:
205                ManagedInspectMap::<SettingTypeRequestResponseInfo>::with_node(
206                    request_response_inspect_node,
207                ),
208            usage_rx: Some(rx),
209            #[cfg(test)]
210            done_tx,
211        }
212    }
213
214    pub fn initialize(mut self) {
215        fasync::Task::local(async move {
216            let id = fuchsia_trace::Id::new();
217            trace!(id, c"setting_proxy_inspect_agent");
218            let mut usage_rx = self.usage_rx.take().unwrap();
219            while let Some(usage_event) = usage_rx.next().await {
220                if let Direction::Request(_) = usage_event.direction {
221                    self.process_usage_event(usage_event);
222                } else {
223                    self.process_usage_response_event(usage_event);
224                }
225                #[cfg(test)]
226                if let Some(done_tx) = &self.done_tx {
227                    let _ = done_tx.unbounded_send(());
228                }
229            }
230        })
231        .detach();
232    }
233
234    fn process_usage_event(&mut self, event: UsageEvent) {
235        let timestamp = clock::inspect_format_now();
236
237        // Get or create the info for this setting type.
238        let request_response_info = self
239            .setting_request_response_info
240            .get_or_insert_with(event.setting.to_string(), SettingTypeRequestResponseInfo::new);
241
242        request_response_info.count += 1;
243
244        let Direction::Request(request) = event.direction else {
245            panic!("Call process_usage_event with response!");
246        };
247        let pending_request_info = PendingRequestInspectInfo {
248            request: request.into(),
249            request_type: format!("{:?}", event.request_type),
250            timestamp: timestamp.into(),
251            count: event.id,
252            inspect_node: inspect::Node::default(),
253        };
254
255        let count_key = format!("{:020}", event.id);
256        request_response_info.pending_requests.push(&count_key, pending_request_info);
257    }
258
259    fn process_usage_response_event(&mut self, event: UsageEvent) {
260        let setting_type_str = event.setting.to_string();
261        let timestamp = clock::inspect_format_now();
262        let Direction::Response(response, response_type) = event.direction else {
263            panic!("Called process_usage_response_event with a request!");
264        };
265
266        // Update the response counter.
267        self.increment_response_count(setting_type_str.clone(), response_type);
268
269        // Find the inspect data for this setting. This should always be present as it's created
270        // upon receiving a request, which should happen before the response is recorded.
271        let condensed_setting_type_info = self
272            .setting_request_response_info
273            .map_mut()
274            .get_mut(&setting_type_str)
275            .expect("Missing info for request");
276
277        let pending_requests = &mut condensed_setting_type_info.pending_requests;
278
279        // Find the position of the pending request with the same request count and remove it. This
280        // should generally be the first pending request in the queue if requests are being answered
281        // in order.
282        let position = match pending_requests.iter_mut().position(|info| info.count == event.id) {
283            Some(position) => position,
284            None => {
285                // We may be unable to find a matching request if requests are piling up faster than
286                // responses, as the number of pending requests is limited.
287                return;
288            }
289        };
290        let pending =
291            pending_requests.items_mut().remove(position).expect("Failed to find pending item");
292
293        // Find the info for this particular request type.
294        let request_type_info_map = condensed_setting_type_info
295            .requests_and_responses_by_type
296            .get_or_insert_with(pending.request_type, || {
297                ManagedInspectMap::<RequestResponsePairInfo>::default()
298            });
299
300        // Request and response pairs are keyed by the concatenation of the request and response,
301        // which uniquely identifies them within a setting.
302        let map_key = format!("{:?}{:?}", pending.request, response);
303
304        // Find this request + response pair in the map and remove it, if it's present. While the
305        // map key is the request + response concatenated, the key displayed in inspect is the
306        // newest request count for that pair. We remove the map entry if it exists so that we can
307        // re-insert to update the key displayed in inspect.
308        let removed_info = request_type_info_map.map_mut().remove(&map_key);
309
310        let mut info = removed_info.unwrap_or_else(|| {
311            RequestResponsePairInfo::new(pending.request.into_inner(), response, pending.count)
312        });
313        {
314            // Update the request and response timestamps. We have borrow from the IValues with
315            // as_mut and drop the variables after this scope ends so that the IValues will know to
316            // update the values in inspect.
317            let mut_requests = &mut info.request_timestamps.as_mut().0;
318            let mut_responses = &mut info.response_timestamps.as_mut().0;
319
320            mut_requests.push_back(pending.timestamp.into_inner());
321            mut_responses.push_back(timestamp);
322
323            // If there are too many timestamps, remove earlier ones.
324            if mut_requests.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
325                let _ = mut_requests.pop_front();
326            }
327            if mut_responses.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
328                let _ = mut_responses.pop_front();
329            }
330        }
331
332        // Insert into the map, but display the key in inspect as the request count.
333        let count_key = format!("{:020}", pending.count);
334        let _ = request_type_info_map.insert_with_property_name(map_key, count_key, info);
335
336        // If there are too many entries, find and remove the oldest.
337        let num_request_response_pairs = request_type_info_map.map().len();
338        if num_request_response_pairs > MAX_REQUEST_RESPONSE_PAIRS {
339            // Find the item with the lowest request count, which means it was the oldest request
340            // received.
341            let mut lowest_count: u64 = u64::MAX;
342            let mut lowest_key: Option<String> = None;
343            for (key, inspect_info) in request_type_info_map.map() {
344                if inspect_info.request_count < lowest_count {
345                    lowest_count = inspect_info.request_count;
346                    lowest_key = Some(key.clone());
347                }
348            }
349
350            if let Some(key_to_remove) = lowest_key {
351                let _ = request_type_info_map.map_mut().remove(&key_to_remove);
352            }
353        }
354    }
355
356    fn increment_response_count(&mut self, setting_type_str: String, response_type: ResponseType) {
357        // Get the response count info for the setting type, creating a new info object
358        // if it doesn't exist in the map yet.
359        let response_count_info = self
360            .response_counts
361            .get_or_insert_with(setting_type_str, SettingTypeResponseCountInfo::default);
362
363        // Get the count for the response type, creating a new count if it doesn't exist
364        // in the map yet, then increment the response count
365        let response_count = response_count_info
366            .response_counts_by_type
367            .get_or_insert_with(format!("{response_type:?}"), ResponseTypeCount::default);
368        let _ = response_count.count.add(1u64);
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use diagnostics_assertions::{TreeAssertion, assert_data_tree};
376    use futures::channel::mpsc;
377    use settings_common::inspect::event::RequestType;
378    use zx::MonotonicInstant;
379
380    // Verifies that request + response pairs with the same value and request type are grouped
381    // together.
382    #[fuchsia::test(allow_stalls = false)]
383    async fn test_inspect_grouped_responses() {
384        // Set the clock so that timestamps can be controlled.
385        clock::mock::set(MonotonicInstant::from_nanos(0));
386
387        let inspector = inspect::Inspector::default();
388        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
389        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
390
391        let (tx, rx) = mpsc::unbounded();
392        let (done_tx, mut done_rx) = mpsc::unbounded();
393        let agent = SettingProxyInspectAgent::create_with_node(
394            rx,
395            condense_node,
396            response_counts_node,
397            Some(done_tx),
398        );
399        agent.initialize();
400
401        // Send a request to turn off auto brightness.
402        let _ = tx.unbounded_send(UsageEvent {
403            setting: "Display",
404            request_type: RequestType::Set,
405            direction: Direction::Request(
406                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
407            ),
408            id: 0,
409        });
410        let _ = done_rx.next().await;
411        let _ = tx.unbounded_send(UsageEvent {
412            setting: "Display",
413            request_type: RequestType::Set,
414            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
415            id: 0,
416        });
417        let _ = done_rx.next().await;
418
419        // Increment clock and send a request to turn on auto brightness.
420        clock::mock::set(MonotonicInstant::from_nanos(100));
421        let _ = tx.unbounded_send(UsageEvent {
422            setting: "Display",
423            request_type: RequestType::Set,
424            direction: Direction::Request(
425                "SetDisplayInfo{auto_brightness: Some(true)}".to_string(),
426            ),
427            id: 1,
428        });
429        let _ = done_rx.next().await;
430        let _ = tx.unbounded_send(UsageEvent {
431            setting: "Display",
432            request_type: RequestType::Set,
433            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
434            id: 1,
435        });
436        let _ = done_rx.next().await;
437
438        // Increment clock and send the same request as the first one. The two should be grouped
439        // together.
440        clock::mock::set(MonotonicInstant::from_nanos(200));
441        let _ = tx.unbounded_send(UsageEvent {
442            setting: "Display",
443            request_type: RequestType::Set,
444            direction: Direction::Request(
445                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
446            ),
447            id: 2,
448        });
449        let _ = done_rx.next().await;
450        let _ = tx.unbounded_send(UsageEvent {
451            setting: "Display",
452            request_type: RequestType::Set,
453            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
454            id: 2,
455        });
456        let _ = done_rx.next().await;
457
458        assert_data_tree!(inspector, root: contains {
459            requests_and_responses: {
460                "Display": {
461                    "pending_requests": {},
462                    "requests_and_responses": {
463                        "Set": {
464                            "00000000000000000001": {
465                                "request": "SetDisplayInfo{auto_brightness: Some(true)}",
466                                "request_timestamps": "0.000000100",
467                                "response": "Ok(None)",
468                                "response_timestamps": "0.000000100"
469                            },
470                            "00000000000000000002": {
471                                "request": "SetDisplayInfo{auto_brightness: Some(false)}",
472                                "request_timestamps": "0.000000000,0.000000200",
473                                "response": "Ok(None)",
474                                "response_timestamps": "0.000000000,0.000000200"
475                            }
476                        }
477                    }
478                }
479            },
480        });
481    }
482
483    // Test that multiple requests of different request types for the same setting records the
484    // correct inspect data.
485    #[fuchsia::test(allow_stalls = false)]
486    async fn test_inspect_mixed_request_types() {
487        // Set the clock so that timestamps can be controlled.
488        clock::mock::set(MonotonicInstant::from_nanos(0));
489
490        let inspector = inspect::Inspector::default();
491        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
492        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
493
494        let (tx, rx) = mpsc::unbounded();
495        let (done_tx, mut done_rx) = mpsc::unbounded();
496        let agent = SettingProxyInspectAgent::create_with_node(
497            rx,
498            condense_node,
499            response_counts_node,
500            Some(done_tx),
501        );
502        agent.initialize();
503
504        // Interlace different request types to make sure the counter is correct.
505        let _ = tx.unbounded_send(UsageEvent {
506            setting: "Display",
507            request_type: RequestType::Set,
508            direction: Direction::Request(
509                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
510            ),
511            id: 0,
512        });
513        let _ = done_rx.next().await;
514        let _ = tx.unbounded_send(UsageEvent {
515            setting: "Display",
516            request_type: RequestType::Set,
517            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
518            id: 0,
519        });
520        let _ = done_rx.next().await;
521
522        // Set to a different time so that a response can correctly link to its request.
523        clock::mock::set(MonotonicInstant::from_nanos(100));
524        let _ = tx.unbounded_send(UsageEvent {
525            setting: "Display",
526            request_type: RequestType::Get,
527            direction: Direction::Request("WatchDisplayInfo".to_string()),
528            id: 1,
529        });
530        let _ = done_rx.next().await;
531        let _ = tx.unbounded_send(UsageEvent {
532            setting: "Display",
533            request_type: RequestType::Get,
534            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
535            id: 1,
536        });
537        let _ = done_rx.next().await;
538
539        // Set to a different time so that a response can correctly link to its request.
540        clock::mock::set(MonotonicInstant::from_nanos(200));
541        let _ = tx.unbounded_send(UsageEvent {
542            setting: "Display",
543            request_type: RequestType::Set,
544            direction: Direction::Request(
545                "SetDisplayInfo{auto_brightness: Some(true)}".to_string(),
546            ),
547            id: 2,
548        });
549        let _ = done_rx.next().await;
550        let _ = tx.unbounded_send(UsageEvent {
551            setting: "Display",
552            request_type: RequestType::Set,
553            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
554            id: 2,
555        });
556        let _ = done_rx.next().await;
557
558        clock::mock::set(MonotonicInstant::from_nanos(300));
559        let _ = tx.unbounded_send(UsageEvent {
560            setting: "Display",
561            request_type: RequestType::Get,
562            direction: Direction::Request("WatchDisplayInfo".to_string()),
563            id: 3,
564        });
565        let _ = done_rx.next().await;
566        let _ = tx.unbounded_send(UsageEvent {
567            setting: "Display",
568            request_type: RequestType::Get,
569            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
570            id: 3,
571        });
572        let _ = done_rx.next().await;
573
574        assert_data_tree!(inspector, root: contains {
575            requests_and_responses: {
576                "Display": {
577                    "pending_requests": {},
578                    "requests_and_responses": {
579                        "Get": {
580                            "00000000000000000003": {
581                                "request": "WatchDisplayInfo",
582                                "request_timestamps": "0.000000100,0.000000300",
583                                "response": "Ok(None)",
584                                "response_timestamps": "0.000000100,0.000000300"
585                            }
586                        },
587                        "Set": {
588                            "00000000000000000000": {
589                                "request": "SetDisplayInfo{auto_brightness: Some(false)}",
590                                  "request_timestamps": "0.000000000",
591                                  "response": "Ok(None)",
592                                  "response_timestamps": "0.000000000"
593                            },
594                            "00000000000000000002": {
595                                "request": "SetDisplayInfo{auto_brightness: Some(true)}",
596                                "request_timestamps": "0.000000200",
597                                "response": "Ok(None)",
598                                "response_timestamps": "0.000000200"
599                            }
600                        }
601                    }
602                }
603            },
604            response_counts: {
605                "Display": {
606                    "OkNone": {
607                        count: 4u64,
608                    }
609                },
610            },
611        });
612    }
613
614    #[fuchsia::test(allow_stalls = false)]
615    async fn test_pending_request() {
616        // Set the clock so that timestamps can be controlled.
617        clock::mock::set(MonotonicInstant::from_nanos(0));
618
619        let inspector = inspect::Inspector::default();
620        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
621        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
622
623        let (tx, rx) = mpsc::unbounded();
624        let (done_tx, mut done_rx) = mpsc::unbounded();
625        let agent = SettingProxyInspectAgent::create_with_node(
626            rx,
627            condense_node,
628            response_counts_node,
629            Some(done_tx),
630        );
631        agent.initialize();
632
633        let _ = tx.unbounded_send(UsageEvent {
634            setting: "Display",
635            request_type: RequestType::Set,
636            direction: Direction::Request(
637                "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
638            ),
639            id: 0,
640        });
641        let _ = done_rx.next().await;
642
643        assert_data_tree!(inspector, root: contains {
644            requests_and_responses: {
645                "Display": {
646                    "pending_requests": {
647                        "00000000000000000000": {
648                            "request": "SetDisplayInfo{auto_brightness: Some(false)}",
649                            "timestamp": "0.000000000",
650                        }
651                    },
652                    "requests_and_responses": {}
653                }
654            },
655        });
656    }
657
658    #[fuchsia::test(allow_stalls = false)]
659    async fn test_response_counts_inspect() {
660        // Set the clock so that timestamps can be controlled.
661        clock::mock::set(MonotonicInstant::from_nanos(0));
662
663        let inspector = inspect::Inspector::default();
664        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
665        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
666
667        let (tx, rx) = mpsc::unbounded();
668        let (done_tx, mut done_rx) = mpsc::unbounded();
669        let agent = SettingProxyInspectAgent::create_with_node(
670            rx,
671            condense_node,
672            response_counts_node,
673            Some(done_tx),
674        );
675        agent.initialize();
676        let _ = tx.unbounded_send(UsageEvent {
677            setting: "Display",
678            request_type: RequestType::Set,
679            direction: Direction::Request(
680                "SetDisplayInfo{auto_brightness:Some(false)}".to_string(),
681            ),
682            id: 0,
683        });
684        let _ = done_rx.next().await;
685        let _ = tx.unbounded_send(UsageEvent {
686            setting: "Display",
687            request_type: RequestType::Set,
688            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
689            id: 0,
690        });
691        let _ = done_rx.next().await;
692
693        clock::mock::set(MonotonicInstant::from_nanos(100));
694        let _ = tx.unbounded_send(UsageEvent {
695            setting: "Display",
696            request_type: RequestType::Get,
697            direction: Direction::Request("WatchDisplayInfo".to_string()),
698            id: 1,
699        });
700        let _ = done_rx.next().await;
701        let _ = tx.unbounded_send(UsageEvent {
702            setting: "Display",
703            request_type: RequestType::Get,
704            direction: Direction::Response("Ok(Some())".to_string(), ResponseType::OkNone),
705            id: 1,
706        });
707        let _ = done_rx.next().await;
708
709        clock::mock::set(MonotonicInstant::from_nanos(200));
710        let _ = tx.unbounded_send(UsageEvent {
711            setting: "Display",
712            request_type: RequestType::Set,
713            direction: Direction::Request("SetDisplayInfo{auto_brightness:None}".to_string()),
714            id: 2,
715        });
716        let _ = done_rx.next().await;
717        let _ = tx.unbounded_send(UsageEvent {
718            setting: "Display",
719            request_type: RequestType::Set,
720            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
721            id: 2,
722        });
723        let _ = done_rx.next().await;
724
725        clock::mock::set(MonotonicInstant::from_nanos(300));
726        let _ = tx.unbounded_send(UsageEvent {
727            setting: "Display",
728            request_type: RequestType::Get,
729            direction: Direction::Request("WatchDisplayInfo".to_string()),
730            id: 3,
731        });
732        let _ = done_rx.next().await;
733        let _ = tx.unbounded_send(UsageEvent {
734            setting: "Display",
735            request_type: RequestType::Get,
736            direction: Direction::Response("Ok(Some())".to_string(), ResponseType::OkNone),
737            id: 3,
738        });
739        let _ = done_rx.next().await;
740
741        assert_data_tree!(inspector, root: contains {
742            response_counts: {
743                "Display": {
744                    "OkNone": {
745                        count: 4u64,
746                    },
747                },
748            },
749        });
750    }
751
752    // Verifies that old requests are dropped after MAX_REQUEST_RESPONSE_PAIRS are received for a
753    // given request + response pair.
754    #[fuchsia::test(allow_stalls = false)]
755    async fn inspect_queue_test() {
756        // Set the clock so that timestamps will always be 0.
757        clock::mock::set(MonotonicInstant::from_nanos(0));
758        let inspector = inspect::Inspector::default();
759        let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
760        let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
761
762        let (tx, rx) = mpsc::unbounded();
763        let (done_tx, mut done_rx) = mpsc::unbounded();
764        let agent = SettingProxyInspectAgent::create_with_node(
765            rx,
766            condense_node,
767            response_counts_node,
768            Some(done_tx),
769        );
770        agent.initialize();
771
772        let _ = tx.unbounded_send(UsageEvent {
773            setting: "Intl",
774            request_type: RequestType::Set,
775            direction: Direction::Request(
776                "SetIntlInfo { \
777                    locales: Some([LocaleId { id: \"en-US\" }]), \
778                    temperature_unit: Some(Celsius), \
779                    time_zone_id: Some(\"UTC\"), \
780                    hour_cycle: None \
781                }"
782                .to_string(),
783            ),
784            id: 0,
785        });
786        let _ = done_rx.next().await;
787        let _ = tx.unbounded_send(UsageEvent {
788            setting: "Intl",
789            request_type: RequestType::Set,
790            direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
791            id: 0,
792        });
793        let _ = done_rx.next().await;
794
795        // Send one more than the max requests to make sure they get pushed off the end of the
796        // queue. The requests must have different values to avoid getting grouped together.
797        for i in 0..MAX_REQUEST_RESPONSE_PAIRS + 1 {
798            let _ = tx.unbounded_send(UsageEvent {
799                setting: "Display",
800                request_type: RequestType::Set,
801                direction: Direction::Request(format!(
802                    "SetDisplayInfo{{manual_brightness_value: Some({})}}",
803                    (i as f32) / 100f32
804                )),
805                id: i as u64,
806            });
807            let _ = done_rx.next().await;
808            let _ = tx.unbounded_send(UsageEvent {
809                setting: "Display",
810                request_type: RequestType::Set,
811                direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
812                id: i as u64,
813            });
814            let _ = done_rx.next().await;
815        }
816
817        // Ensures we have INSPECT_REQUESTS_COUNT items and that the queue dropped the earliest one
818        // when hitting the limit.
819        fn display_subtree_assertion() -> TreeAssertion {
820            let mut tree_assertion = TreeAssertion::new("Display", false);
821            let mut request_response_assertion = TreeAssertion::new("requests_and_responses", true);
822            let mut request_assertion = TreeAssertion::new("Set", true);
823
824            for i in 1..MAX_REQUEST_RESPONSE_PAIRS + 1 {
825                // We don't need to set clock here since we don't do exact match.
826                request_assertion
827                    .add_child_assertion(TreeAssertion::new(&format!("{i:020}"), false));
828            }
829            request_response_assertion.add_child_assertion(request_assertion);
830            tree_assertion.add_child_assertion(request_response_assertion);
831            tree_assertion
832        }
833
834        assert_data_tree!(inspector, root: contains {
835            requests_and_responses: {
836                display_subtree_assertion(),
837                "Intl": {
838                    "pending_requests": {},
839                    "requests_and_responses": {
840                        "Set": {
841                            "00000000000000000000": {
842                                "request": "SetIntlInfo { \
843                                    locales: Some([LocaleId { id: \"en-US\" }]), \
844                                    temperature_unit: Some(Celsius), \
845                                    time_zone_id: Some(\"UTC\"), \
846                                    hour_cycle: None \
847                                }",
848                                "request_timestamps": "0.000000000",
849                                "response": "Ok(None)",
850                                "response_timestamps": "0.000000000"
851                            }
852                        }
853                    }
854                }
855            },
856        });
857    }
858}