| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::input_device::{Handled, InputDeviceEvent, InputEvent}; |
| use crate::input_handler::InputHandler; |
| use async_trait::async_trait; |
| use fuchsia_inspect::{self as inspect, NumericProperty, Property}; |
| use fuchsia_zircon as zx; |
| use std::collections::HashMap; |
| use std::rc::Rc; |
| |
| #[derive(Debug, Hash, PartialEq, Eq)] |
| enum EventType { |
| Keyboard, |
| ConsumerControls, |
| Mouse, |
| Touch, |
| MouseConfig, |
| #[cfg(test)] |
| Fake, |
| } |
| |
| impl std::fmt::Display for EventType { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match &*self { |
| EventType::Keyboard => write!(f, "keyboard"), |
| EventType::ConsumerControls => write!(f, "consumer_controls"), |
| EventType::Mouse => write!(f, "mouse"), |
| EventType::Touch => write!(f, "touch"), |
| EventType::MouseConfig => write!(f, "mouse_config"), |
| #[cfg(test)] |
| EventType::Fake => write!(f, "fake"), |
| } |
| } |
| } |
| |
| impl EventType { |
| /// Creates an `EventType` based on an [InputDeviceEvent]. |
| pub fn for_device_event(event: &InputDeviceEvent) -> Self { |
| match event { |
| InputDeviceEvent::Keyboard(_) => EventType::Keyboard, |
| InputDeviceEvent::ConsumerControls(_) => EventType::ConsumerControls, |
| InputDeviceEvent::Mouse(_) => EventType::Mouse, |
| InputDeviceEvent::Touch(_) => EventType::Touch, |
| InputDeviceEvent::MouseConfig(_) => EventType::MouseConfig, |
| #[cfg(test)] |
| InputDeviceEvent::Fake => EventType::Fake, |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct EventCounters { |
| /// A node that contains the counters below. |
| _node: inspect::Node, |
| /// The number of total events that this handler has seen so far. |
| events_count: inspect::UintProperty, |
| /// The number of total handled events that this handler has seen so far. |
| handled_events_count: inspect::UintProperty, |
| /// The timestamp (in nanoseconds) when the last event was seen by this |
| /// handler (not when the event itself was generated). 0 if unset. |
| last_seen_timestamp_ns: inspect::IntProperty, |
| /// The event time at which the last recorded event was generated. |
| /// 0 if unset. |
| last_generated_timestamp_ns: inspect::IntProperty, |
| } |
| |
| impl EventCounters { |
| fn add_new_into( |
| map: &mut HashMap<EventType, EventCounters>, |
| root: &inspect::Node, |
| event_type: EventType, |
| ) { |
| let node = root.create_child(format!("{}", event_type)); |
| let events_count = node.create_uint("events_count", 0); |
| let handled_events_count = node.create_uint("handled_events_count", 0); |
| let last_seen_timestamp_ns = node.create_int("last_seen_timestamp_ns", 0); |
| let last_generated_timestamp_ns = node.create_int("last_generated_timestamp_ns", 0); |
| let new_counters = EventCounters { |
| _node: node, |
| events_count, |
| handled_events_count, |
| last_seen_timestamp_ns, |
| last_generated_timestamp_ns, |
| }; |
| map.insert(event_type, new_counters); |
| } |
| |
| pub fn count_event(&self, time: zx::Time, event_time: zx::Time, handled: &Handled) { |
| self.events_count.add(1); |
| if *handled == Handled::Yes { |
| self.handled_events_count.add(1); |
| } |
| self.last_seen_timestamp_ns.set(time.into_nanos()); |
| self.last_generated_timestamp_ns.set(event_time.into_nanos()); |
| } |
| } |
| |
| /// A [InputHandler] that records various metrics about the flow of events. |
| /// All events are passed through unmodified. Some properties of those events |
| /// may be exposed in the metrics. No PII information should ever be exposed |
| /// this way. |
| #[derive(Debug)] |
| pub struct InspectHandler { |
| /// A function that obtains the current timestamp. |
| now: fn() -> zx::Time, |
| /// A node that contains the statistics about this particular handler. |
| _node: inspect::Node, |
| /// The number of total events that this handler has seen so far. |
| events_count: inspect::UintProperty, |
| /// The timestamp (in nanoseconds) when the last event was seen by this |
| /// handler (not when the event itself was generated). 0 if unset. |
| last_seen_timestamp_ns: inspect::IntProperty, |
| /// The event time at which the last recorded event was generated. |
| /// 0 if unset. |
| last_generated_timestamp_ns: inspect::IntProperty, |
| |
| /// An inventory of event counters by type. |
| events_by_type: HashMap<EventType, EventCounters>, |
| } |
| |
| #[async_trait(?Send)] |
| impl InputHandler for InspectHandler { |
| async fn handle_input_event(self: Rc<Self>, input_event: InputEvent) -> Vec<InputEvent> { |
| let event_time = input_event.event_time; |
| let now = (self.now)(); |
| self.events_count.add(1); |
| self.last_seen_timestamp_ns.set(now.into_nanos()); |
| self.last_generated_timestamp_ns.set(event_time.into_nanos()); |
| let event_type = EventType::for_device_event(&input_event.device_event); |
| self.events_by_type.get(&event_type).expect("all event types are tracked").count_event( |
| now, |
| event_time, |
| &input_event.handled, |
| ); |
| vec![input_event] |
| } |
| } |
| |
| impl InspectHandler { |
| /// Creates a new inspect handler instance. |
| /// |
| /// `node` is the inspect node that will receive the stats. |
| pub fn new(node: inspect::Node) -> Rc<Self> { |
| Self::new_with_now(node, zx::Time::get_monotonic) |
| } |
| |
| /// Creates a new inspect handler instance, using `now` to supply the current timestamp. |
| /// Expected to be useful in testing mainly. |
| fn new_with_now(node: inspect::Node, now: fn() -> zx::Time) -> Rc<Self> { |
| let event_count = node.create_uint("events_count", 0); |
| let last_seen_timestamp_ns = node.create_int("last_seen_timestamp_ns", 0); |
| let last_generated_timestamp_ns = node.create_int("last_generated_timestamp_ns", 0); |
| |
| let mut events_by_type = HashMap::new(); |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::Keyboard); |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::ConsumerControls); |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::Mouse); |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::Touch); |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::MouseConfig); |
| #[cfg(test)] |
| EventCounters::add_new_into(&mut events_by_type, &node, EventType::Fake); |
| |
| Rc::new(Self { |
| now, |
| _node: node, |
| events_count: event_count, |
| last_seen_timestamp_ns, |
| last_generated_timestamp_ns, |
| events_by_type, |
| }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::testing_utilities; |
| use fuchsia_async as fasync; |
| use fuchsia_inspect::assert_data_tree; |
| |
| fn fixed_now() -> zx::Time { |
| zx::Time::ZERO + zx::Duration::from_nanos(42) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn verify_inspect() { |
| let inspector = inspect::Inspector::new(); |
| let root = inspector.root(); |
| let test_node = root.create_child("test_node"); |
| |
| let handler = super::InspectHandler::new_with_now(test_node, fixed_now); |
| assert_data_tree!(inspector, root: { |
| test_node: { |
| events_count: 0u64, |
| last_seen_timestamp_ns: 0i64, |
| last_generated_timestamp_ns: 0i64, |
| consumer_controls: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| fake: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| keyboard: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse_config: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| touch: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| } |
| }); |
| |
| handler |
| .clone() |
| .handle_input_event(testing_utilities::create_fake_input_event(zx::Time::from_nanos( |
| 43i64, |
| ))) |
| .await; |
| assert_data_tree!(inspector, root: { |
| test_node: { |
| events_count: 1u64, |
| last_seen_timestamp_ns: 42i64, |
| last_generated_timestamp_ns: 43i64, |
| consumer_controls: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| fake: { |
| events_count: 1u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 43i64, |
| last_seen_timestamp_ns: 42i64, |
| }, |
| keyboard: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse_config: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| touch: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| } |
| }); |
| |
| handler |
| .clone() |
| .handle_input_event(testing_utilities::create_fake_input_event(zx::Time::from_nanos( |
| 44i64, |
| ))) |
| .await; |
| assert_data_tree!(inspector, root: { |
| test_node: { |
| events_count: 2u64, |
| last_seen_timestamp_ns: 42i64, |
| last_generated_timestamp_ns: 44i64, |
| consumer_controls: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| fake: { |
| events_count: 2u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 44i64, |
| last_seen_timestamp_ns: 42i64, |
| }, |
| keyboard: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse_config: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| touch: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| } |
| }); |
| |
| handler |
| .clone() |
| .handle_input_event(testing_utilities::create_fake_handled_input_event( |
| zx::Time::from_nanos(44), |
| )) |
| .await; |
| assert_data_tree!(inspector, root: { |
| test_node: { |
| events_count: 3u64, |
| last_seen_timestamp_ns: 42i64, |
| last_generated_timestamp_ns: 44i64, |
| consumer_controls: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| fake: { |
| events_count: 3u64, |
| handled_events_count: 1u64, |
| last_generated_timestamp_ns: 44i64, |
| last_seen_timestamp_ns: 42i64, |
| }, |
| keyboard: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| mouse_config: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| touch: { |
| events_count: 0u64, |
| handled_events_count: 0u64, |
| last_generated_timestamp_ns: 0i64, |
| last_seen_timestamp_ns: 0i64, |
| }, |
| } |
| }); |
| } |
| } |