input_pipeline/
inspect_handler.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
5use crate::input_device::{Handled, InputDeviceEvent, InputDeviceType, InputEvent, InputEventType};
6use crate::input_handler::{Handler, InputHandler};
7use async_trait::async_trait;
8use fuchsia_inspect::health::Reporter;
9use fuchsia_inspect::{
10    self as inspect, ExponentialHistogramParams, HistogramProperty, Inspector, NumericProperty,
11    Property,
12};
13
14use futures::FutureExt;
15use futures::lock::Mutex;
16use inspect::Node;
17use std::cell::RefCell;
18use std::collections::{HashMap, HashSet, VecDeque};
19use std::fmt::Debug;
20use std::rc::Rc;
21use std::sync::Arc;
22
23const MAX_RECENT_EVENT_LOG_SIZE: usize = 125;
24const LATENCY_HISTOGRAM_PROPERTIES: ExponentialHistogramParams<i64> = ExponentialHistogramParams {
25    floor: 0,
26    initial_step: 1,
27    step_multiplier: 10,
28    // Seven buckets allows us to report
29    // *      < 0 msec (added automatically by Inspect)
30    // *      0-1 msec
31    // *     1-10 msec
32    // *   10-100 msec
33    // * 100-1000 msec
34    // *     1-10 sec
35    // *   10-100 sec
36    // * 100-1000 sec
37    // *    >1000 sec (added automatically by Inspect)
38    buckets: 7,
39};
40
41#[derive(Debug, Hash, PartialEq, Eq)]
42enum EventType {
43    Keyboard,
44    LightSensor,
45    ConsumerControls,
46    Mouse,
47    TouchScreen,
48    Touchpad,
49    #[cfg(test)]
50    Fake,
51}
52
53impl std::fmt::Display for EventType {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match &*self {
56            EventType::Keyboard => write!(f, "keyboard"),
57            EventType::LightSensor => write!(f, "light_sensor"),
58            EventType::ConsumerControls => write!(f, "consumer_controls"),
59            EventType::Mouse => write!(f, "mouse"),
60            EventType::TouchScreen => write!(f, "touch_screen"),
61            EventType::Touchpad => write!(f, "touchpad"),
62            #[cfg(test)]
63            EventType::Fake => write!(f, "fake"),
64        }
65    }
66}
67
68impl EventType {
69    /// Creates an `EventType` based on an [InputDeviceEvent].
70    pub fn for_device_event(event: &InputDeviceEvent) -> Self {
71        match event {
72            InputDeviceEvent::Keyboard(_) => EventType::Keyboard,
73            InputDeviceEvent::LightSensor(_) => EventType::LightSensor,
74            InputDeviceEvent::ConsumerControls(_) => EventType::ConsumerControls,
75            InputDeviceEvent::Mouse(_) => EventType::Mouse,
76            InputDeviceEvent::TouchScreen(_) => EventType::TouchScreen,
77            InputDeviceEvent::Touchpad(_) => EventType::Touchpad,
78            #[cfg(test)]
79            InputDeviceEvent::Fake => EventType::Fake,
80        }
81    }
82}
83
84#[derive(Debug)]
85struct EventCounters {
86    /// A node that contains the counters below.
87    _node: inspect::Node,
88    /// The number of total events that this handler has seen so far.
89    events_count: inspect::UintProperty,
90    /// The number of total handled events that this handler has seen so far.
91    handled_events_count: inspect::UintProperty,
92    /// The timestamp (in nanoseconds) when the last event was seen by this
93    /// handler (not when the event itself was generated). 0 if unset.
94    last_seen_timestamp_ns: inspect::IntProperty,
95    /// The event time at which the last recorded event was generated.
96    /// 0 if unset.
97    last_generated_timestamp_ns: inspect::IntProperty,
98}
99
100impl EventCounters {
101    fn add_new_into(
102        map: &mut HashMap<EventType, EventCounters>,
103        root: &inspect::Node,
104        event_type: EventType,
105    ) {
106        let node = root.create_child(format!("{}", event_type));
107        let events_count = node.create_uint("events_count", 0);
108        let handled_events_count = node.create_uint("handled_events_count", 0);
109        let last_seen_timestamp_ns = node.create_int("last_seen_timestamp_ns", 0);
110        let last_generated_timestamp_ns = node.create_int("last_generated_timestamp_ns", 0);
111        let new_counters = EventCounters {
112            _node: node,
113            events_count,
114            handled_events_count,
115            last_seen_timestamp_ns,
116            last_generated_timestamp_ns,
117        };
118        map.insert(event_type, new_counters);
119    }
120
121    pub fn count_event(
122        &self,
123        time: zx::MonotonicInstant,
124        event_time: zx::MonotonicInstant,
125        handled: &Handled,
126    ) {
127        self.events_count.add(1);
128        if *handled == Handled::Yes {
129            self.handled_events_count.add(1);
130        }
131        self.last_seen_timestamp_ns.set(time.into_nanos());
132        self.last_generated_timestamp_ns.set(event_time.into_nanos());
133    }
134}
135
136pub(crate) struct CircularBuffer<T> {
137    // Size of CircularBuffer
138    _size: usize,
139    // VecDeque of recent events with capacity of `size`
140    _events: VecDeque<T>,
141}
142
143pub(crate) trait BufferNode {
144    fn get_name(&self) -> &'static str;
145    fn record_inspect(&self, node: &Node);
146}
147
148impl<T> CircularBuffer<T>
149where
150    T: BufferNode,
151{
152    pub(crate) fn new(size: usize) -> Self {
153        let events = VecDeque::with_capacity(size);
154        CircularBuffer { _size: size, _events: events }
155    }
156
157    pub(crate) fn push(&mut self, event: T) {
158        if self._events.len() >= self._size {
159            std::mem::drop(self._events.pop_front());
160        }
161        self._events.push_back(event);
162    }
163
164    pub(crate) fn record_all_lazy_inspect(
165        &self,
166        inspector: inspect::Inspector,
167    ) -> inspect::Inspector {
168        self._events.iter().enumerate().for_each(|(i, event)| {
169            // Include leading zeros so Inspect will display events in correct numerical order.
170            // Inspect displays nodes in alphabetical order by default.
171            inspector.root().record_child(format!("{:03}_{}", i, event.get_name()), move |node| {
172                event.record_inspect(node)
173            });
174        });
175        inspector
176    }
177}
178
179impl BufferNode for InputEvent {
180    fn get_name(&self) -> &'static str {
181        self.get_event_type()
182    }
183
184    fn record_inspect(&self, node: &Node) {
185        InputEvent::record_inspect(self, node);
186    }
187}
188
189/// A [InputHandler] that records various metrics about the flow of events.
190/// All events are passed through unmodified.  Some properties of those events
191/// may be exposed in the metrics.  No PII information should ever be exposed
192/// this way.
193pub struct InspectHandler<F> {
194    /// A function that obtains the current timestamp.
195    now: RefCell<F>,
196    /// A node that contains the statistics about this particular handler.
197    node: inspect::Node,
198    /// The number of total events that this handler has seen so far.
199    events_count: inspect::UintProperty,
200    /// The timestamp (in nanoseconds) when the last event was seen by this
201    /// handler (not when the event itself was generated). 0 if unset.
202    last_seen_timestamp_ns: inspect::IntProperty,
203    /// The event time at which the last recorded event was generated.
204    /// 0 if unset.
205    last_generated_timestamp_ns: inspect::IntProperty,
206    /// An inventory of event counters by type.
207    events_by_type: HashMap<EventType, EventCounters>,
208    /// Log of recent events in the order they were received.
209    recent_events_log: Option<Arc<Mutex<CircularBuffer<InputEvent>>>>,
210    /// Histogram of latency from the binding timestamp for an `InputEvent` until
211    /// the time the `InputEvent` was observed by this handler. Reported in milliseconds,
212    /// because values less than 1 msec aren't especially interesting.
213    pipeline_latency_ms: inspect::IntExponentialHistogramProperty,
214    // This node records the health status of `InspectHandler`.
215    health_node: RefCell<fuchsia_inspect::health::Node>,
216}
217
218impl<F: FnMut() -> zx::MonotonicInstant + 'static> Debug for InspectHandler<F> {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        f.debug_struct("InspectHandler")
221            .field("node", &self.node)
222            .field("events_count", &self.events_count)
223            .field("last_seen_timestamp_ns", &self.last_seen_timestamp_ns)
224            .field("last_generated_timestamp_ns", &self.last_generated_timestamp_ns)
225            .field("events_by_type", &self.events_by_type)
226            .field("recent_events_log", &self.recent_events_log)
227            .field("pipeline_latency_ms", &self.pipeline_latency_ms)
228            .finish()
229    }
230}
231
232impl<F: FnMut() -> zx::MonotonicInstant + 'static> Handler for InspectHandler<F> {
233    fn set_handler_healthy(self: std::rc::Rc<Self>) {
234        self.health_node.borrow_mut().set_ok();
235    }
236
237    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
238        self.health_node.borrow_mut().set_unhealthy(msg);
239    }
240
241    fn get_name(&self) -> &'static str {
242        "InspectHandler"
243    }
244
245    fn interest(&self) -> Vec<InputEventType> {
246        vec![
247            InputEventType::Keyboard,
248            InputEventType::LightSensor,
249            InputEventType::ConsumerControls,
250            InputEventType::Mouse,
251            InputEventType::TouchScreen,
252            InputEventType::Touchpad,
253            #[cfg(test)]
254            InputEventType::Fake,
255        ]
256    }
257}
258
259#[async_trait(?Send)]
260impl<F: FnMut() -> zx::MonotonicInstant + 'static> InputHandler for InspectHandler<F> {
261    async fn handle_input_event(self: Rc<Self>, input_event: InputEvent) -> Vec<InputEvent> {
262        fuchsia_trace::duration!("input", "inspect_handler");
263        let tracing_id = input_event.trace_id.unwrap_or_else(|| 0.into());
264        fuchsia_trace::flow_step!("input", "event_in_input_pipeline", tracing_id);
265
266        let event_time = input_event.event_time;
267        let now = (self.now.borrow_mut())();
268        self.events_count.add(1);
269        self.last_seen_timestamp_ns.set(now.into_nanos());
270        self.last_generated_timestamp_ns.set(event_time.into_nanos());
271        let event_type = EventType::for_device_event(&input_event.device_event);
272        self.events_by_type
273            .get(&event_type)
274            .unwrap_or_else(|| panic!("no event counters for {}", event_type))
275            .count_event(now, event_time, &input_event.handled);
276        if let Some(recent_events_log) = &self.recent_events_log {
277            recent_events_log.lock().await.push(input_event.clone());
278        }
279        self.pipeline_latency_ms.insert((now - event_time).into_millis());
280        vec![input_event]
281    }
282}
283
284/// Creates a new inspect handler instance.
285///
286/// `node` is the inspect node that will receive the stats.
287pub fn make_inspect_handler(
288    node: inspect::Node,
289    supported_input_devices: &HashSet<&InputDeviceType>,
290    displays_recent_events: bool,
291) -> Rc<InspectHandler<fn() -> zx::MonotonicInstant>> {
292    InspectHandler::new_internal(
293        node,
294        zx::MonotonicInstant::get,
295        supported_input_devices,
296        displays_recent_events,
297    )
298}
299
300impl<F> InspectHandler<F> {
301    /// Creates a new inspect handler instance, using `now` to supply the current timestamp.
302    /// Expected to be useful in testing mainly.
303    fn new_internal(
304        node: inspect::Node,
305        now: F,
306        supported_input_devices: &HashSet<&InputDeviceType>,
307        displays_recent_events: bool,
308    ) -> Rc<Self> {
309        let event_count = node.create_uint("events_count", 0);
310        let last_seen_timestamp_ns = node.create_int("last_seen_timestamp_ns", 0);
311        let last_generated_timestamp_ns = node.create_int("last_generated_timestamp_ns", 0);
312
313        let recent_events_log = match displays_recent_events {
314            true => {
315                let recent_events =
316                    Arc::new(Mutex::new(CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE)));
317                record_lazy_recent_events(&node, Arc::clone(&recent_events));
318                Some(recent_events)
319            }
320            false => None,
321        };
322
323        let pipeline_latency_ms = node
324            .create_int_exponential_histogram("pipeline_latency_ms", LATENCY_HISTOGRAM_PROPERTIES);
325
326        let mut health_node = fuchsia_inspect::health::Node::new(&node);
327        health_node.set_starting_up();
328
329        let mut events_by_type = HashMap::new();
330        if supported_input_devices.contains(&InputDeviceType::Keyboard) {
331            EventCounters::add_new_into(&mut events_by_type, &node, EventType::Keyboard);
332        }
333        if supported_input_devices.contains(&InputDeviceType::ConsumerControls) {
334            EventCounters::add_new_into(&mut events_by_type, &node, EventType::ConsumerControls);
335        }
336        if supported_input_devices.contains(&InputDeviceType::LightSensor) {
337            EventCounters::add_new_into(&mut events_by_type, &node, EventType::LightSensor);
338        }
339        if supported_input_devices.contains(&InputDeviceType::Mouse) {
340            EventCounters::add_new_into(&mut events_by_type, &node, EventType::Mouse);
341        }
342        if supported_input_devices.contains(&InputDeviceType::Touch) {
343            EventCounters::add_new_into(&mut events_by_type, &node, EventType::TouchScreen);
344            EventCounters::add_new_into(&mut events_by_type, &node, EventType::Touchpad);
345        }
346        #[cfg(test)]
347        EventCounters::add_new_into(&mut events_by_type, &node, EventType::Fake);
348
349        Rc::new(Self {
350            now: RefCell::new(now),
351            node,
352            events_count: event_count,
353            last_seen_timestamp_ns,
354            last_generated_timestamp_ns,
355            events_by_type,
356            recent_events_log,
357            pipeline_latency_ms,
358            health_node: RefCell::new(health_node),
359        })
360    }
361}
362
363fn record_lazy_recent_events(
364    node: &inspect::Node,
365    recent_events: Arc<Mutex<CircularBuffer<InputEvent>>>,
366) {
367    node.record_lazy_child("recent_events_log", move || {
368        let recent_events_clone = Arc::clone(&recent_events);
369        async move {
370            let inspector = Inspector::default();
371            Ok(recent_events_clone.lock().await.record_all_lazy_inspect(inspector))
372        }
373        .boxed()
374    });
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::input_device::{self, InputDeviceDescriptor};
381    use crate::keyboard_binding::KeyboardDeviceDescriptor;
382    use crate::light_sensor::types::Rgbc;
383    use crate::light_sensor_binding::{LightSensorDeviceDescriptor, LightSensorEvent};
384    use crate::mouse_binding::{
385        MouseDeviceDescriptor, MouseLocation, MousePhase, PrecisionScroll, RawWheelDelta,
386        WheelDelta,
387    };
388    use crate::testing_utilities::{
389        consumer_controls_device_descriptor, create_consumer_controls_event,
390        create_fake_handled_input_event, create_fake_input_event, create_keyboard_event,
391        create_mouse_event, create_touch_contact, create_touch_screen_event, create_touchpad_event,
392    };
393    use crate::touch_binding::{TouchScreenDeviceDescriptor, TouchpadDeviceDescriptor};
394    use crate::utils::Position;
395    use diagnostics_assertions::{AnyProperty, assert_data_tree};
396    use fidl::endpoints::create_proxy_and_stream;
397    use fidl_fuchsia_input_report::InputDeviceMarker;
398    use fuchsia_async as fasync;
399    use maplit::{hashmap, hashset};
400    use test_case::test_case;
401
402    fn fixed_now() -> zx::MonotonicInstant {
403        zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_nanos(42)
404    }
405
406    #[fasync::run_singlethreaded(test)]
407    async fn circular_buffer_no_overflow() {
408        let mut circular_buffer = CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE);
409        assert_eq!(circular_buffer._size, MAX_RECENT_EVENT_LOG_SIZE);
410
411        let first_event_time = zx::MonotonicInstant::get();
412        circular_buffer.push(create_fake_input_event(first_event_time));
413        let second_event_time = zx::MonotonicInstant::get();
414        circular_buffer.push(create_fake_input_event(second_event_time));
415
416        // Fill up `events` VecDeque
417        for _i in 2..MAX_RECENT_EVENT_LOG_SIZE {
418            let curr_event_time = zx::MonotonicInstant::get();
419            circular_buffer.push(create_fake_input_event(curr_event_time));
420            match circular_buffer._events.back() {
421                Some(event) => assert_eq!(event.event_time, curr_event_time),
422                None => assert!(false),
423            }
424        }
425
426        // Verify first event at the front
427        match circular_buffer._events.front() {
428            Some(event) => assert_eq!(event.event_time, first_event_time),
429            None => assert!(false),
430        }
431
432        // CircularBuffer `events` should be full, pushing another event should remove the first event.
433        let last_event_time = zx::MonotonicInstant::get();
434        circular_buffer.push(create_fake_input_event(last_event_time));
435        match circular_buffer._events.front() {
436            Some(event) => assert_eq!(event.event_time, second_event_time),
437            None => assert!(false),
438        }
439        match circular_buffer._events.back() {
440            Some(event) => assert_eq!(event.event_time, last_event_time),
441            None => assert!(false),
442        }
443    }
444
445    #[fasync::run_singlethreaded(test)]
446    async fn recent_events_log_records_inspect() {
447        let inspector = fuchsia_inspect::Inspector::default();
448
449        let recent_events_log =
450            Arc::new(Mutex::new(CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE)));
451        record_lazy_recent_events(inspector.root(), Arc::clone(&recent_events_log));
452
453        let keyboard_descriptor = InputDeviceDescriptor::Keyboard(KeyboardDeviceDescriptor {
454            keys: vec![fidl_fuchsia_input::Key::A, fidl_fuchsia_input::Key::B],
455            ..Default::default()
456        });
457        let mouse_descriptor = InputDeviceDescriptor::Mouse(MouseDeviceDescriptor {
458            device_id: 1u32,
459            absolute_x_range: None,
460            absolute_y_range: None,
461            wheel_v_range: None,
462            wheel_h_range: None,
463            buttons: None,
464            counts_per_mm: 12u32,
465        });
466        let touch_screen_descriptor =
467            InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
468                device_id: 1,
469                contacts: vec![],
470            });
471        let touchpad_descriptor = InputDeviceDescriptor::Touchpad(TouchpadDeviceDescriptor {
472            device_id: 1,
473            contacts: vec![],
474        });
475
476        let pressed_buttons = HashSet::from([1u8, 21u8, 15u8]);
477        let mut pressed_buttons_vec: Vec<u64> = vec![];
478        pressed_buttons.iter().for_each(|button| {
479            pressed_buttons_vec.push(*button as u64);
480        });
481
482        let (light_sensor_proxy, _) = create_proxy_and_stream::<InputDeviceMarker>();
483
484        let recent_events = vec![
485            create_keyboard_event(
486                fidl_fuchsia_input::Key::A,
487                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
488                None,
489                &keyboard_descriptor,
490                None,
491            ),
492            create_consumer_controls_event(
493                vec![
494                    fidl_fuchsia_input_report::ConsumerControlButton::VolumeUp,
495                    fidl_fuchsia_input_report::ConsumerControlButton::VolumeUp,
496                    fidl_fuchsia_input_report::ConsumerControlButton::Pause,
497                    fidl_fuchsia_input_report::ConsumerControlButton::VolumeDown,
498                    fidl_fuchsia_input_report::ConsumerControlButton::MicMute,
499                    fidl_fuchsia_input_report::ConsumerControlButton::CameraDisable,
500                    fidl_fuchsia_input_report::ConsumerControlButton::FactoryReset,
501                    fidl_fuchsia_input_report::ConsumerControlButton::Reboot,
502                ],
503                zx::MonotonicInstant::get(),
504                &consumer_controls_device_descriptor(),
505            ),
506            create_mouse_event(
507                MouseLocation::Absolute(Position { x: 7.0f32, y: 15.0f32 }),
508                Some(WheelDelta {
509                    raw_data: RawWheelDelta::Ticks(5i64),
510                    physical_pixel: Some(8.0f32),
511                }),
512                Some(WheelDelta {
513                    raw_data: RawWheelDelta::Millimeters(10.0f32),
514                    physical_pixel: Some(8.0f32),
515                }),
516                Some(PrecisionScroll::Yes),
517                MousePhase::Move,
518                HashSet::from([1u8]),
519                pressed_buttons.clone(),
520                zx::MonotonicInstant::get(),
521                &mouse_descriptor,
522            ),
523            create_touch_screen_event(
524                hashmap! {
525                    fidl_fuchsia_ui_input::PointerEventPhase::Add
526                        => vec![create_touch_contact(1u32, Position { x: 10.0, y: 30.0 })],
527                    fidl_fuchsia_ui_input::PointerEventPhase::Move
528                        => vec![create_touch_contact(1u32, Position { x: 11.0, y: 31.0 })],
529                },
530                zx::MonotonicInstant::get(),
531                &touch_screen_descriptor,
532            ),
533            create_touchpad_event(
534                vec![
535                    create_touch_contact(1u32, Position { x: 0.0, y: 0.0 }),
536                    create_touch_contact(2u32, Position { x: 10.0, y: 10.0 }),
537                ],
538                HashSet::new(),
539                zx::MonotonicInstant::get(),
540                &touchpad_descriptor,
541            ),
542            InputEvent {
543                device_event: InputDeviceEvent::LightSensor(LightSensorEvent {
544                    device_proxy: light_sensor_proxy,
545                    rgbc: Rgbc { red: 1, green: 2, blue: 3, clear: 14747 },
546                }),
547                device_descriptor: InputDeviceDescriptor::LightSensor(
548                    LightSensorDeviceDescriptor {
549                        vendor_id: 1,
550                        product_id: 2,
551                        device_id: 3,
552                        sensor_layout: Rgbc { red: 1, green: 2, blue: 3, clear: 4 },
553                    },
554                ),
555                event_time: zx::MonotonicInstant::get(),
556                handled: input_device::Handled::No,
557                trace_id: None,
558            },
559            create_keyboard_event(
560                fidl_fuchsia_input::Key::B,
561                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
562                None,
563                &keyboard_descriptor,
564                None,
565            ),
566        ];
567
568        for event in recent_events.into_iter() {
569            recent_events_log.lock().await.push(event);
570        }
571
572        assert_data_tree!(inspector, root: {
573            recent_events_log: {
574                "000_keyboard_event": {
575                    event_time: AnyProperty,
576                },
577                "001_consumer_controls_event": {
578                    event_time: AnyProperty,
579                    pressed_buttons: vec!["volume_up", "volume_up", "pause", "volume_down", "mic_mute", "camera_disable", "factory_reset", "reboot"],
580                },
581                "002_mouse_event": {
582                    event_time: AnyProperty,
583                    location_absolute: { x: 7.0f64, y: 15.0f64},
584                    wheel_delta_v: {
585                        ticks: 5i64,
586                        physical_pixel: 8.0f64,
587                    },
588                    wheel_delta_h: {
589                        millimeters: 10.0f64,
590                        physical_pixel: 8.0f64,
591                    },
592                    is_precision_scroll: "yes",
593                    phase: "move",
594                    affected_buttons: vec![1u64],
595                    pressed_buttons: pressed_buttons_vec.clone(),
596                },
597                "003_touch_screen_event": {
598                    event_time: AnyProperty,
599                    injector_contacts: {
600                        add: {
601                            "1": {
602                                position_x_mm: 10.0f64,
603                                position_y_mm: 30.0f64,
604                            },
605                        },
606                        change: {
607                            "1": {
608                                position_x_mm: 11.0f64,
609                                position_y_mm: 31.0f64,
610                            },
611                        },
612                        remove: {},
613                    },
614                    pressed_buttons: Vec::<String>::new(),
615                },
616                "004_touchpad_event": {
617                    event_time: AnyProperty,
618                    pressed_buttons: Vec::<u64>::new(),
619                    injector_contacts: {
620                        "1": {
621                            position_x_mm: 0.0f64,
622                            position_y_mm: 0.0f64,
623                        },
624                        "2": {
625                            position_x_mm: 10.0f64,
626                            position_y_mm: 10.0f64,
627                        },
628                    },
629                },
630                "005_light_sensor_event": {
631                    event_time: AnyProperty,
632                    red: 1u64,
633                    green: 2u64,
634                    blue: 3u64,
635                    clear: 14747u64,
636                },
637                "006_keyboard_event": {
638                    event_time: AnyProperty,
639                },
640            }
641        });
642    }
643
644    #[fasync::run_singlethreaded(test)]
645    async fn verify_inspect_no_recent_events_log() {
646        let inspector = inspect::Inspector::default();
647        let root = inspector.root();
648        let test_node = root.create_child("test_node");
649        let supported_input_devices: HashSet<&InputDeviceType> = HashSet::from([
650            &input_device::InputDeviceType::Keyboard,
651            &input_device::InputDeviceType::ConsumerControls,
652            &input_device::InputDeviceType::LightSensor,
653            &input_device::InputDeviceType::Mouse,
654            &input_device::InputDeviceType::Touch,
655        ]);
656
657        let handler = super::InspectHandler::new_internal(
658            test_node,
659            fixed_now,
660            &supported_input_devices,
661            /* displays_recent_events = */ false,
662        );
663        assert_data_tree!(inspector, root: {
664            test_node: contains {
665                events_count: 0u64,
666                last_seen_timestamp_ns: 0i64,
667                last_generated_timestamp_ns: 0i64,
668                consumer_controls: {
669                     events_count: 0u64,
670                     handled_events_count: 0u64,
671                     last_generated_timestamp_ns: 0i64,
672                     last_seen_timestamp_ns: 0i64,
673                },
674                fake: {
675                     events_count: 0u64,
676                     handled_events_count: 0u64,
677                     last_generated_timestamp_ns: 0i64,
678                     last_seen_timestamp_ns: 0i64,
679                },
680                keyboard: {
681                     events_count: 0u64,
682                     handled_events_count: 0u64,
683                     last_generated_timestamp_ns: 0i64,
684                     last_seen_timestamp_ns: 0i64,
685                },
686                light_sensor: {
687                     events_count: 0u64,
688                     handled_events_count: 0u64,
689                     last_generated_timestamp_ns: 0i64,
690                     last_seen_timestamp_ns: 0i64,
691                },
692                mouse: {
693                     events_count: 0u64,
694                     handled_events_count: 0u64,
695                     last_generated_timestamp_ns: 0i64,
696                     last_seen_timestamp_ns: 0i64,
697                },
698                touch_screen: {
699                     events_count: 0u64,
700                     handled_events_count: 0u64,
701                     last_generated_timestamp_ns: 0i64,
702                     last_seen_timestamp_ns: 0i64,
703                },
704                touchpad: {
705                    events_count: 0u64,
706                    handled_events_count: 0u64,
707                    last_generated_timestamp_ns: 0i64,
708                    last_seen_timestamp_ns: 0i64,
709               },
710           }
711        });
712
713        handler
714            .clone()
715            .handle_input_event(create_fake_input_event(zx::MonotonicInstant::from_nanos(43i64)))
716            .await;
717        assert_data_tree!(inspector, root: {
718            test_node: contains {
719                events_count: 1u64,
720                last_seen_timestamp_ns: 42i64,
721                last_generated_timestamp_ns: 43i64,
722                consumer_controls: {
723                     events_count: 0u64,
724                     handled_events_count: 0u64,
725                     last_generated_timestamp_ns: 0i64,
726                     last_seen_timestamp_ns: 0i64,
727                },
728                fake: {
729                     events_count: 1u64,
730                     handled_events_count: 0u64,
731                     last_generated_timestamp_ns: 43i64,
732                     last_seen_timestamp_ns: 42i64,
733                },
734                keyboard: {
735                     events_count: 0u64,
736                     handled_events_count: 0u64,
737                     last_generated_timestamp_ns: 0i64,
738                     last_seen_timestamp_ns: 0i64,
739                },
740                light_sensor: {
741                     events_count: 0u64,
742                     handled_events_count: 0u64,
743                     last_generated_timestamp_ns: 0i64,
744                     last_seen_timestamp_ns: 0i64,
745                },
746                mouse: {
747                     events_count: 0u64,
748                     handled_events_count: 0u64,
749                     last_generated_timestamp_ns: 0i64,
750                     last_seen_timestamp_ns: 0i64,
751                },
752                touch_screen: {
753                     events_count: 0u64,
754                     handled_events_count: 0u64,
755                     last_generated_timestamp_ns: 0i64,
756                     last_seen_timestamp_ns: 0i64,
757                },
758                touchpad: {
759                    events_count: 0u64,
760                    handled_events_count: 0u64,
761                    last_generated_timestamp_ns: 0i64,
762                    last_seen_timestamp_ns: 0i64,
763               },
764            }
765        });
766
767        handler
768            .clone()
769            .handle_input_event(create_fake_input_event(zx::MonotonicInstant::from_nanos(44i64)))
770            .await;
771        assert_data_tree!(inspector, root: {
772            test_node: contains {
773                events_count: 2u64,
774                last_seen_timestamp_ns: 42i64,
775                last_generated_timestamp_ns: 44i64,
776                consumer_controls: {
777                     events_count: 0u64,
778                     handled_events_count: 0u64,
779                     last_generated_timestamp_ns: 0i64,
780                     last_seen_timestamp_ns: 0i64,
781                },
782                fake: {
783                     events_count: 2u64,
784                     handled_events_count: 0u64,
785                     last_generated_timestamp_ns: 44i64,
786                     last_seen_timestamp_ns: 42i64,
787                },
788                keyboard: {
789                     events_count: 0u64,
790                     handled_events_count: 0u64,
791                     last_generated_timestamp_ns: 0i64,
792                     last_seen_timestamp_ns: 0i64,
793                },
794                light_sensor: {
795                     events_count: 0u64,
796                     handled_events_count: 0u64,
797                     last_generated_timestamp_ns: 0i64,
798                     last_seen_timestamp_ns: 0i64,
799                },
800                mouse: {
801                     events_count: 0u64,
802                     handled_events_count: 0u64,
803                     last_generated_timestamp_ns: 0i64,
804                     last_seen_timestamp_ns: 0i64,
805                },
806                touch_screen: {
807                     events_count: 0u64,
808                     handled_events_count: 0u64,
809                     last_generated_timestamp_ns: 0i64,
810                     last_seen_timestamp_ns: 0i64,
811                },
812                touchpad: {
813                    events_count: 0u64,
814                    handled_events_count: 0u64,
815                    last_generated_timestamp_ns: 0i64,
816                    last_seen_timestamp_ns: 0i64,
817               },
818            }
819        });
820
821        handler
822            .clone()
823            .handle_input_event(create_fake_handled_input_event(zx::MonotonicInstant::from_nanos(
824                44,
825            )))
826            .await;
827        assert_data_tree!(inspector, root: {
828            test_node: contains {
829                events_count: 3u64,
830                last_seen_timestamp_ns: 42i64,
831                last_generated_timestamp_ns: 44i64,
832                consumer_controls: {
833                     events_count: 0u64,
834                     handled_events_count: 0u64,
835                     last_generated_timestamp_ns: 0i64,
836                     last_seen_timestamp_ns: 0i64,
837                },
838                fake: {
839                     events_count: 3u64,
840                     handled_events_count: 1u64,
841                     last_generated_timestamp_ns: 44i64,
842                     last_seen_timestamp_ns: 42i64,
843                },
844                keyboard: {
845                     events_count: 0u64,
846                     handled_events_count: 0u64,
847                     last_generated_timestamp_ns: 0i64,
848                     last_seen_timestamp_ns: 0i64,
849                },
850                light_sensor: {
851                     events_count: 0u64,
852                     handled_events_count: 0u64,
853                     last_generated_timestamp_ns: 0i64,
854                     last_seen_timestamp_ns: 0i64,
855                },
856                mouse: {
857                     events_count: 0u64,
858                     handled_events_count: 0u64,
859                     last_generated_timestamp_ns: 0i64,
860                     last_seen_timestamp_ns: 0i64,
861                },
862                touch_screen: {
863                     events_count: 0u64,
864                     handled_events_count: 0u64,
865                     last_generated_timestamp_ns: 0i64,
866                     last_seen_timestamp_ns: 0i64,
867                },
868                touchpad: {
869                    events_count: 0u64,
870                    handled_events_count: 0u64,
871                    last_generated_timestamp_ns: 0i64,
872                    last_seen_timestamp_ns: 0i64,
873               },
874            }
875        });
876    }
877
878    #[fasync::run_singlethreaded(test)]
879    async fn verify_inspect_with_recent_events_log() {
880        let inspector = inspect::Inspector::default();
881        let root = inspector.root();
882        let test_node = root.create_child("test_node");
883        let supported_input_devices: HashSet<&InputDeviceType> = HashSet::from([
884            &input_device::InputDeviceType::Keyboard,
885            &input_device::InputDeviceType::ConsumerControls,
886            &input_device::InputDeviceType::LightSensor,
887            &input_device::InputDeviceType::Mouse,
888            &input_device::InputDeviceType::Touch,
889        ]);
890
891        let handler = super::InspectHandler::new_internal(
892            test_node,
893            fixed_now,
894            &supported_input_devices,
895            /* displays_recent_events = */ true,
896        );
897        assert_data_tree!(inspector, root: {
898            test_node: contains {
899                events_count: 0u64,
900                last_seen_timestamp_ns: 0i64,
901                last_generated_timestamp_ns: 0i64,
902                recent_events_log: {},
903                consumer_controls: {
904                     events_count: 0u64,
905                     handled_events_count: 0u64,
906                     last_generated_timestamp_ns: 0i64,
907                     last_seen_timestamp_ns: 0i64,
908                },
909                fake: {
910                     events_count: 0u64,
911                     handled_events_count: 0u64,
912                     last_generated_timestamp_ns: 0i64,
913                     last_seen_timestamp_ns: 0i64,
914                },
915                keyboard: {
916                     events_count: 0u64,
917                     handled_events_count: 0u64,
918                     last_generated_timestamp_ns: 0i64,
919                     last_seen_timestamp_ns: 0i64,
920                },
921                light_sensor: {
922                     events_count: 0u64,
923                     handled_events_count: 0u64,
924                     last_generated_timestamp_ns: 0i64,
925                     last_seen_timestamp_ns: 0i64,
926                },
927                mouse: {
928                     events_count: 0u64,
929                     handled_events_count: 0u64,
930                     last_generated_timestamp_ns: 0i64,
931                     last_seen_timestamp_ns: 0i64,
932                },
933                touch_screen: {
934                     events_count: 0u64,
935                     handled_events_count: 0u64,
936                     last_generated_timestamp_ns: 0i64,
937                     last_seen_timestamp_ns: 0i64,
938                },
939                touchpad: {
940                    events_count: 0u64,
941                    handled_events_count: 0u64,
942                    last_generated_timestamp_ns: 0i64,
943                    last_seen_timestamp_ns: 0i64,
944               },
945           }
946        });
947
948        handler
949            .clone()
950            .handle_input_event(create_fake_input_event(zx::MonotonicInstant::from_nanos(43i64)))
951            .await;
952        assert_data_tree!(inspector, root: {
953            test_node: contains {
954                events_count: 1u64,
955                last_seen_timestamp_ns: 42i64,
956                last_generated_timestamp_ns: 43i64,
957                recent_events_log: {
958                    "000_fake_event": {
959                        event_time: 43i64,
960                    },
961                },
962                consumer_controls: {
963                     events_count: 0u64,
964                     handled_events_count: 0u64,
965                     last_generated_timestamp_ns: 0i64,
966                     last_seen_timestamp_ns: 0i64,
967                },
968                fake: {
969                     events_count: 1u64,
970                     handled_events_count: 0u64,
971                     last_generated_timestamp_ns: 43i64,
972                     last_seen_timestamp_ns: 42i64,
973                },
974                keyboard: {
975                     events_count: 0u64,
976                     handled_events_count: 0u64,
977                     last_generated_timestamp_ns: 0i64,
978                     last_seen_timestamp_ns: 0i64,
979                },
980                light_sensor: {
981                     events_count: 0u64,
982                     handled_events_count: 0u64,
983                     last_generated_timestamp_ns: 0i64,
984                     last_seen_timestamp_ns: 0i64,
985                },
986                mouse: {
987                     events_count: 0u64,
988                     handled_events_count: 0u64,
989                     last_generated_timestamp_ns: 0i64,
990                     last_seen_timestamp_ns: 0i64,
991                },
992                touch_screen: {
993                     events_count: 0u64,
994                     handled_events_count: 0u64,
995                     last_generated_timestamp_ns: 0i64,
996                     last_seen_timestamp_ns: 0i64,
997                },
998                touchpad: {
999                    events_count: 0u64,
1000                    handled_events_count: 0u64,
1001                    last_generated_timestamp_ns: 0i64,
1002                    last_seen_timestamp_ns: 0i64,
1003               },
1004            }
1005        });
1006
1007        handler
1008            .clone()
1009            .handle_input_event(create_fake_input_event(zx::MonotonicInstant::from_nanos(44i64)))
1010            .await;
1011        assert_data_tree!(inspector, root: {
1012            test_node: contains {
1013                events_count: 2u64,
1014                last_seen_timestamp_ns: 42i64,
1015                last_generated_timestamp_ns: 44i64,
1016                recent_events_log: {
1017                    "000_fake_event": {
1018                        event_time: 43i64,
1019                    },
1020                    "001_fake_event": {
1021                        event_time: 44i64,
1022                    },
1023                },
1024                consumer_controls: {
1025                     events_count: 0u64,
1026                     handled_events_count: 0u64,
1027                     last_generated_timestamp_ns: 0i64,
1028                     last_seen_timestamp_ns: 0i64,
1029                },
1030                fake: {
1031                     events_count: 2u64,
1032                     handled_events_count: 0u64,
1033                     last_generated_timestamp_ns: 44i64,
1034                     last_seen_timestamp_ns: 42i64,
1035                },
1036                keyboard: {
1037                     events_count: 0u64,
1038                     handled_events_count: 0u64,
1039                     last_generated_timestamp_ns: 0i64,
1040                     last_seen_timestamp_ns: 0i64,
1041                },
1042                light_sensor: {
1043                     events_count: 0u64,
1044                     handled_events_count: 0u64,
1045                     last_generated_timestamp_ns: 0i64,
1046                     last_seen_timestamp_ns: 0i64,
1047                },
1048                mouse: {
1049                     events_count: 0u64,
1050                     handled_events_count: 0u64,
1051                     last_generated_timestamp_ns: 0i64,
1052                     last_seen_timestamp_ns: 0i64,
1053                },
1054                touch_screen: {
1055                     events_count: 0u64,
1056                     handled_events_count: 0u64,
1057                     last_generated_timestamp_ns: 0i64,
1058                     last_seen_timestamp_ns: 0i64,
1059                },
1060                touchpad: {
1061                    events_count: 0u64,
1062                    handled_events_count: 0u64,
1063                    last_generated_timestamp_ns: 0i64,
1064                    last_seen_timestamp_ns: 0i64,
1065               },
1066            }
1067        });
1068
1069        handler
1070            .clone()
1071            .handle_input_event(create_fake_handled_input_event(zx::MonotonicInstant::from_nanos(
1072                44,
1073            )))
1074            .await;
1075        assert_data_tree!(inspector, root: {
1076            test_node: contains {
1077                events_count: 3u64,
1078                last_seen_timestamp_ns: 42i64,
1079                last_generated_timestamp_ns: 44i64,
1080                recent_events_log: {
1081                    "000_fake_event": {
1082                        event_time: 43i64,
1083                    },
1084                    "001_fake_event": {
1085                        event_time: 44i64,
1086                    },
1087                    "002_fake_event": {
1088                        event_time: 44i64,
1089                    },
1090                },
1091                consumer_controls: {
1092                     events_count: 0u64,
1093                     handled_events_count: 0u64,
1094                     last_generated_timestamp_ns: 0i64,
1095                     last_seen_timestamp_ns: 0i64,
1096                },
1097                fake: {
1098                     events_count: 3u64,
1099                     handled_events_count: 1u64,
1100                     last_generated_timestamp_ns: 44i64,
1101                     last_seen_timestamp_ns: 42i64,
1102                },
1103                keyboard: {
1104                     events_count: 0u64,
1105                     handled_events_count: 0u64,
1106                     last_generated_timestamp_ns: 0i64,
1107                     last_seen_timestamp_ns: 0i64,
1108                },
1109                light_sensor: {
1110                     events_count: 0u64,
1111                     handled_events_count: 0u64,
1112                     last_generated_timestamp_ns: 0i64,
1113                     last_seen_timestamp_ns: 0i64,
1114                },
1115                mouse: {
1116                     events_count: 0u64,
1117                     handled_events_count: 0u64,
1118                     last_generated_timestamp_ns: 0i64,
1119                     last_seen_timestamp_ns: 0i64,
1120                },
1121                touch_screen: {
1122                     events_count: 0u64,
1123                     handled_events_count: 0u64,
1124                     last_generated_timestamp_ns: 0i64,
1125                     last_seen_timestamp_ns: 0i64,
1126                },
1127                touchpad: {
1128                    events_count: 0u64,
1129                    handled_events_count: 0u64,
1130                    last_generated_timestamp_ns: 0i64,
1131                    last_seen_timestamp_ns: 0i64,
1132               },
1133            }
1134        });
1135    }
1136
1137    #[test_case([i64::MIN]; "min value")]
1138    #[test_case([-1]; "negative value")]
1139    #[test_case([0]; "zero")]
1140    #[test_case([1]; "positive value")]
1141    #[test_case([i64::MAX]; "max value")]
1142    #[test_case([1_000_000, 10_000_000, 100_000_000, 1000_000_000]; "multiple values")]
1143    #[fuchsia::test(allow_stalls = false)]
1144    async fn updates_latency_histogram(
1145        latencies_nsec: impl IntoIterator<Item = i64> + Clone + 'static,
1146    ) {
1147        let inspector = inspect::Inspector::default();
1148        let root = inspector.root();
1149        let test_node = root.create_child("test_node");
1150
1151        let mut seen_timestamps =
1152            latencies_nsec.clone().into_iter().map(zx::MonotonicInstant::from_nanos);
1153        let now = move || {
1154            seen_timestamps.next().expect("internal error: test has more events than latencies")
1155        };
1156        let handler = super::InspectHandler::new_internal(
1157            test_node,
1158            now,
1159            &hashset! {},
1160            /* displays_recent_events = */ false,
1161        );
1162        for _latency in latencies_nsec.clone() {
1163            handler
1164                .clone()
1165                .handle_input_event(create_fake_input_event(zx::MonotonicInstant::ZERO))
1166                .await;
1167        }
1168
1169        let mut histogram_assertion = diagnostics_assertions::HistogramAssertion::exponential(
1170            super::LATENCY_HISTOGRAM_PROPERTIES,
1171        );
1172        histogram_assertion
1173            .insert_values(latencies_nsec.into_iter().map(|nsec| nsec / 1000 / 1000));
1174        assert_data_tree!(inspector, root: {
1175            test_node: contains {
1176                pipeline_latency_ms: histogram_assertion
1177            }
1178        })
1179    }
1180}