blob: 29794bee6727cd695f32c7c0b30e1592f29d4219 [file] [log] [blame]
// 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,
},
}
});
}
}