blob: 718b983fd6efd057c6c3ad6241f6578df81280e5 [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, InputDeviceType, InputEvent};
use crate::input_handler::InputHandler;
use async_trait::async_trait;
use fuchsia_inspect::{
self as inspect, health::Reporter, ExponentialHistogramParams, HistogramProperty, Inspector,
NumericProperty, Property,
};
use fuchsia_zircon as zx;
use futures::lock::Mutex;
use futures::FutureExt;
use inspect::Node;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt::Debug;
use std::rc::Rc;
use std::sync::Arc;
const MAX_RECENT_EVENT_LOG_SIZE: usize = 125;
const LATENCY_HISTOGRAM_PROPERTIES: ExponentialHistogramParams<i64> = ExponentialHistogramParams {
floor: 0,
initial_step: 1,
step_multiplier: 10,
// Seven buckets allows us to report
// * < 0 msec (added automatically by Inspect)
// * 0-1 msec
// * 1-10 msec
// * 10-100 msec
// * 100-1000 msec
// * 1-10 sec
// * 10-100 sec
// * 100-1000 sec
// * >1000 sec (added automatically by Inspect)
buckets: 7,
};
#[derive(Debug, Hash, PartialEq, Eq)]
enum EventType {
Keyboard,
LightSensor,
ConsumerControls,
Mouse,
TouchScreen,
Touchpad,
#[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::LightSensor => write!(f, "light_sensor"),
EventType::ConsumerControls => write!(f, "consumer_controls"),
EventType::Mouse => write!(f, "mouse"),
EventType::TouchScreen => write!(f, "touch_screen"),
EventType::Touchpad => write!(f, "touchpad"),
#[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::LightSensor(_) => EventType::LightSensor,
InputDeviceEvent::ConsumerControls(_) => EventType::ConsumerControls,
InputDeviceEvent::Mouse(_) => EventType::Mouse,
InputDeviceEvent::TouchScreen(_) => EventType::TouchScreen,
InputDeviceEvent::Touchpad(_) => EventType::Touchpad,
#[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());
}
}
pub(crate) struct CircularBuffer<T> {
// Size of CircularBuffer
_size: usize,
// VecDeque of recent events with capacity of `size`
_events: VecDeque<T>,
}
pub(crate) trait BufferNode {
fn get_name(&self) -> &'static str;
fn record_inspect(&self, node: &Node);
}
impl<T> CircularBuffer<T>
where
T: BufferNode,
{
pub(crate) fn new(size: usize) -> Self {
let events = VecDeque::with_capacity(size);
CircularBuffer { _size: size, _events: events }
}
pub(crate) fn push(&mut self, event: T) {
if self._events.len() >= self._size {
std::mem::drop(self._events.pop_front());
}
self._events.push_back(event);
}
pub(crate) fn record_all_lazy_inspect(
&self,
inspector: inspect::Inspector,
) -> inspect::Inspector {
self._events.iter().enumerate().for_each(|(i, event)| {
// Include leading zeros so Inspect will display events in correct numerical order.
// Inspect displays nodes in alphabetical order by default.
inspector.root().record_child(format!("{:03}_{}", i, event.get_name()), move |node| {
event.record_inspect(node)
});
});
inspector
}
}
impl BufferNode for InputEvent {
fn get_name(&self) -> &'static str {
self.get_event_type()
}
fn record_inspect(&self, node: &Node) {
InputEvent::record_inspect(self, node);
}
}
/// 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.
pub struct InspectHandler<F> {
/// A function that obtains the current timestamp.
now: RefCell<F>,
/// 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>,
/// Log of recent events in the order they were received.
recent_events_log: Option<Arc<Mutex<CircularBuffer<InputEvent>>>>,
/// Histogram of latency from the binding timestamp for an `InputEvent` until
/// the time the `InputEvent` was observed by this handler. Reported in milliseconds,
/// because values less than 1 msec aren't especially interesting.
pipeline_latency_ms: inspect::IntExponentialHistogramProperty,
// This node records the health status of `InspectHandler`.
health_node: RefCell<fuchsia_inspect::health::Node>,
}
impl<F: FnMut() -> zx::Time + 'static> Debug for InspectHandler<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InspectHandler")
.field("node", &self.node)
.field("events_count", &self.events_count)
.field("last_seen_timestamp_ns", &self.last_seen_timestamp_ns)
.field("last_generated_timestamp_ns", &self.last_generated_timestamp_ns)
.field("events_by_type", &self.events_by_type)
.field("recent_events_log", &self.recent_events_log)
.field("pipeline_latency_ms", &self.pipeline_latency_ms)
.finish()
}
}
#[async_trait(?Send)]
impl<F: FnMut() -> zx::Time + 'static> InputHandler for InspectHandler<F> {
async fn handle_input_event(self: Rc<Self>, input_event: InputEvent) -> Vec<InputEvent> {
let event_time = input_event.event_time;
let now = (self.now.borrow_mut())();
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)
.unwrap_or_else(|| panic!("no event counters for {}", event_type))
.count_event(now, event_time, &input_event.handled);
if let Some(recent_events_log) = &self.recent_events_log {
recent_events_log.lock().await.push(input_event.clone());
}
self.pipeline_latency_ms.insert((now - event_time).into_millis());
vec![input_event]
}
fn set_handler_healthy(self: std::rc::Rc<Self>) {
self.health_node.borrow_mut().set_ok();
}
fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
self.health_node.borrow_mut().set_unhealthy(msg);
}
}
/// Creates a new inspect handler instance.
///
/// `node` is the inspect node that will receive the stats.
pub fn make_inspect_handler(
node: inspect::Node,
supported_input_devices: &HashSet<&InputDeviceType>,
displays_recent_events: bool,
) -> Rc<InspectHandler<fn() -> zx::Time>> {
InspectHandler::new_internal(
node,
zx::Time::get_monotonic,
supported_input_devices,
displays_recent_events,
)
}
impl<F> InspectHandler<F> {
/// Creates a new inspect handler instance, using `now` to supply the current timestamp.
/// Expected to be useful in testing mainly.
fn new_internal(
node: inspect::Node,
now: F,
supported_input_devices: &HashSet<&InputDeviceType>,
displays_recent_events: bool,
) -> 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 recent_events_log = match displays_recent_events {
true => {
let recent_events =
Arc::new(Mutex::new(CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE)));
record_lazy_recent_events(&node, Arc::clone(&recent_events));
Some(recent_events)
}
false => None,
};
let pipeline_latency_ms = node
.create_int_exponential_histogram("pipeline_latency_ms", LATENCY_HISTOGRAM_PROPERTIES);
let mut health_node = fuchsia_inspect::health::Node::new(&node);
health_node.set_starting_up();
let mut events_by_type = HashMap::new();
if supported_input_devices.contains(&InputDeviceType::Keyboard) {
EventCounters::add_new_into(&mut events_by_type, &node, EventType::Keyboard);
}
if supported_input_devices.contains(&InputDeviceType::ConsumerControls) {
EventCounters::add_new_into(&mut events_by_type, &node, EventType::ConsumerControls);
}
if supported_input_devices.contains(&InputDeviceType::LightSensor) {
EventCounters::add_new_into(&mut events_by_type, &node, EventType::LightSensor);
}
if supported_input_devices.contains(&InputDeviceType::Mouse) {
EventCounters::add_new_into(&mut events_by_type, &node, EventType::Mouse);
}
if supported_input_devices.contains(&InputDeviceType::Touch) {
EventCounters::add_new_into(&mut events_by_type, &node, EventType::TouchScreen);
EventCounters::add_new_into(&mut events_by_type, &node, EventType::Touchpad);
}
#[cfg(test)]
EventCounters::add_new_into(&mut events_by_type, &node, EventType::Fake);
Rc::new(Self {
now: RefCell::new(now),
node,
events_count: event_count,
last_seen_timestamp_ns,
last_generated_timestamp_ns,
events_by_type,
recent_events_log,
pipeline_latency_ms,
health_node: RefCell::new(health_node),
})
}
}
fn record_lazy_recent_events(
node: &inspect::Node,
recent_events: Arc<Mutex<CircularBuffer<InputEvent>>>,
) {
node.record_lazy_child("recent_events_log", move || {
let recent_events_clone = Arc::clone(&recent_events);
async move {
let inspector = Inspector::default();
Ok(recent_events_clone.lock().await.record_all_lazy_inspect(inspector))
}
.boxed()
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
input_device::{self, InputDeviceDescriptor},
keyboard_binding::KeyboardDeviceDescriptor,
light_sensor::types::Rgbc,
light_sensor_binding::{LightSensorDeviceDescriptor, LightSensorEvent},
mouse_binding::{
MouseDeviceDescriptor, MouseLocation, MousePhase, PrecisionScroll, RawWheelDelta,
WheelDelta,
},
testing_utilities::{
consumer_controls_device_descriptor, create_consumer_controls_event,
create_fake_handled_input_event, create_fake_input_event, create_keyboard_event,
create_mouse_event, create_touch_contact, create_touch_screen_event,
create_touchpad_event,
},
touch_binding::{TouchScreenDeviceDescriptor, TouchpadDeviceDescriptor},
utils::Position,
};
use diagnostics_assertions::{assert_data_tree, AnyProperty};
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_input_report::InputDeviceMarker;
use fuchsia_async as fasync;
use maplit::{hashmap, hashset};
use test_case::test_case;
fn fixed_now() -> zx::Time {
zx::Time::ZERO + zx::Duration::from_nanos(42)
}
#[fasync::run_singlethreaded(test)]
async fn circular_buffer_no_overflow() {
let mut circular_buffer = CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE);
assert_eq!(circular_buffer._size, MAX_RECENT_EVENT_LOG_SIZE);
let first_event_time = zx::Time::get_monotonic();
circular_buffer.push(create_fake_input_event(first_event_time));
let second_event_time = zx::Time::get_monotonic();
circular_buffer.push(create_fake_input_event(second_event_time));
// Fill up `events` VecDeque
for _i in 2..MAX_RECENT_EVENT_LOG_SIZE {
let curr_event_time = zx::Time::get_monotonic();
circular_buffer.push(create_fake_input_event(curr_event_time));
match circular_buffer._events.back() {
Some(event) => assert_eq!(event.event_time, curr_event_time),
None => assert!(false),
}
}
// Verify first event at the front
match circular_buffer._events.front() {
Some(event) => assert_eq!(event.event_time, first_event_time),
None => assert!(false),
}
// CircularBuffer `events` should be full, pushing another event should remove the first event.
let last_event_time = zx::Time::get_monotonic();
circular_buffer.push(create_fake_input_event(last_event_time));
match circular_buffer._events.front() {
Some(event) => assert_eq!(event.event_time, second_event_time),
None => assert!(false),
}
match circular_buffer._events.back() {
Some(event) => assert_eq!(event.event_time, last_event_time),
None => assert!(false),
}
}
#[fasync::run_singlethreaded(test)]
async fn recent_events_log_records_inspect() {
let inspector = fuchsia_inspect::Inspector::default();
let recent_events_log =
Arc::new(Mutex::new(CircularBuffer::new(MAX_RECENT_EVENT_LOG_SIZE)));
record_lazy_recent_events(inspector.root(), Arc::clone(&recent_events_log));
let keyboard_descriptor = InputDeviceDescriptor::Keyboard(KeyboardDeviceDescriptor {
keys: vec![fidl_fuchsia_input::Key::A, fidl_fuchsia_input::Key::B],
..Default::default()
});
let mouse_descriptor = InputDeviceDescriptor::Mouse(MouseDeviceDescriptor {
device_id: 1u32,
absolute_x_range: None,
absolute_y_range: None,
wheel_v_range: None,
wheel_h_range: None,
buttons: None,
counts_per_mm: 12u32,
});
let touch_screen_descriptor =
InputDeviceDescriptor::TouchScreen(TouchScreenDeviceDescriptor {
device_id: 1,
contacts: vec![],
});
let touchpad_descriptor = InputDeviceDescriptor::Touchpad(TouchpadDeviceDescriptor {
device_id: 1,
contacts: vec![],
});
let pressed_buttons = HashSet::from([1u8, 21u8, 15u8]);
let mut pressed_buttons_vec: Vec<u64> = vec![];
pressed_buttons.iter().for_each(|button| {
pressed_buttons_vec.push(*button as u64);
});
let (light_sensor_proxy, _) =
create_proxy_and_stream::<InputDeviceMarker>().expect("proxy created");
let recent_events = vec![
create_keyboard_event(
fidl_fuchsia_input::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
None,
&keyboard_descriptor,
None,
),
create_consumer_controls_event(
vec![
fidl_fuchsia_input_report::ConsumerControlButton::VolumeUp,
fidl_fuchsia_input_report::ConsumerControlButton::VolumeUp,
fidl_fuchsia_input_report::ConsumerControlButton::Pause,
fidl_fuchsia_input_report::ConsumerControlButton::VolumeDown,
fidl_fuchsia_input_report::ConsumerControlButton::MicMute,
fidl_fuchsia_input_report::ConsumerControlButton::CameraDisable,
fidl_fuchsia_input_report::ConsumerControlButton::FactoryReset,
fidl_fuchsia_input_report::ConsumerControlButton::Reboot,
],
zx::Time::get_monotonic(),
&consumer_controls_device_descriptor(),
),
create_mouse_event(
MouseLocation::Absolute(Position { x: 7.0f32, y: 15.0f32 }),
Some(WheelDelta {
raw_data: RawWheelDelta::Ticks(5i64),
physical_pixel: Some(8.0f32),
}),
Some(WheelDelta {
raw_data: RawWheelDelta::Millimeters(10.0f32),
physical_pixel: Some(8.0f32),
}),
Some(PrecisionScroll::Yes),
MousePhase::Move,
HashSet::from([1u8]),
pressed_buttons.clone(),
zx::Time::get_monotonic(),
&mouse_descriptor,
),
create_touch_screen_event(
hashmap! {
fidl_fuchsia_ui_input::PointerEventPhase::Add
=> vec![create_touch_contact(1u32, Position { x: 10.0, y: 30.0 })],
fidl_fuchsia_ui_input::PointerEventPhase::Move
=> vec![create_touch_contact(1u32, Position { x: 11.0, y: 31.0 })],
},
zx::Time::get_monotonic(),
&touch_screen_descriptor,
),
create_touchpad_event(
vec![
create_touch_contact(1u32, Position { x: 0.0, y: 0.0 }),
create_touch_contact(2u32, Position { x: 10.0, y: 10.0 }),
],
pressed_buttons,
zx::Time::get_monotonic(),
&touchpad_descriptor,
),
InputEvent {
device_event: InputDeviceEvent::LightSensor(LightSensorEvent {
device_proxy: light_sensor_proxy,
rgbc: Rgbc { red: 1, green: 2, blue: 3, clear: 14747 },
}),
device_descriptor: InputDeviceDescriptor::LightSensor(
LightSensorDeviceDescriptor {
vendor_id: 1,
product_id: 2,
device_id: 3,
sensor_layout: Rgbc { red: 1, green: 2, blue: 3, clear: 4 },
},
),
event_time: zx::Time::get_monotonic(),
handled: input_device::Handled::No,
trace_id: None,
},
create_keyboard_event(
fidl_fuchsia_input::Key::B,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
None,
&keyboard_descriptor,
None,
),
];
for event in recent_events.into_iter() {
recent_events_log.lock().await.push(event);
}
assert_data_tree!(inspector, root: {
recent_events_log: {
"000_keyboard_event": {
event_time: AnyProperty,
},
"001_consumer_controls_event": {
event_time: AnyProperty,
pressed_buttons: vec!["volume_up", "volume_up", "pause", "volume_down", "mic_mute", "camera_disable", "factory_reset", "reboot"],
},
"002_mouse_event": {
event_time: AnyProperty,
location_absolute: { x: 7.0f64, y: 15.0f64},
wheel_delta_v: {
ticks: 5i64,
physical_pixel: 8.0f64,
},
wheel_delta_h: {
millimeters: 10.0f64,
physical_pixel: 8.0f64,
},
is_precision_scroll: "yes",
phase: "move",
affected_buttons: vec![1u64],
pressed_buttons: pressed_buttons_vec.clone(),
},
"003_touch_screen_event": {
event_time: AnyProperty,
injector_contacts: {
add: {
"1": {
position_x_mm: 10.0f64,
position_y_mm: 30.0f64,
},
},
change: {
"1": {
position_x_mm: 11.0f64,
position_y_mm: 31.0f64,
},
},
remove: {},
},
},
"004_touchpad_event": {
event_time: AnyProperty,
pressed_buttons: pressed_buttons_vec,
injector_contacts: {
"1": {
position_x_mm: 0.0f64,
position_y_mm: 0.0f64,
},
"2": {
position_x_mm: 10.0f64,
position_y_mm: 10.0f64,
},
},
},
"005_light_sensor_event": {
event_time: AnyProperty,
red: 1u64,
green: 2u64,
blue: 3u64,
clear: 14747u64,
},
"006_keyboard_event": {
event_time: AnyProperty,
},
}
});
}
#[fasync::run_singlethreaded(test)]
async fn verify_inspect_no_recent_events_log() {
let inspector = inspect::Inspector::default();
let root = inspector.root();
let test_node = root.create_child("test_node");
let supported_input_devices: HashSet<&InputDeviceType> = HashSet::from([
&input_device::InputDeviceType::Keyboard,
&input_device::InputDeviceType::ConsumerControls,
&input_device::InputDeviceType::LightSensor,
&input_device::InputDeviceType::Mouse,
&input_device::InputDeviceType::Touch,
]);
let handler = super::InspectHandler::new_internal(
test_node,
fixed_now,
&supported_input_devices,
/* displays_recent_events = */ false,
);
assert_data_tree!(inspector, root: {
test_node: contains {
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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_input_event(zx::Time::from_nanos(43i64)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_input_event(zx::Time::from_nanos(44i64)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_handled_input_event(zx::Time::from_nanos(44)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
}
#[fasync::run_singlethreaded(test)]
async fn verify_inspect_with_recent_events_log() {
let inspector = inspect::Inspector::default();
let root = inspector.root();
let test_node = root.create_child("test_node");
let supported_input_devices: HashSet<&InputDeviceType> = HashSet::from([
&input_device::InputDeviceType::Keyboard,
&input_device::InputDeviceType::ConsumerControls,
&input_device::InputDeviceType::LightSensor,
&input_device::InputDeviceType::Mouse,
&input_device::InputDeviceType::Touch,
]);
let handler = super::InspectHandler::new_internal(
test_node,
fixed_now,
&supported_input_devices,
/* displays_recent_events = */ true,
);
assert_data_tree!(inspector, root: {
test_node: contains {
events_count: 0u64,
last_seen_timestamp_ns: 0i64,
last_generated_timestamp_ns: 0i64,
recent_events_log: {},
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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_input_event(zx::Time::from_nanos(43i64)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
events_count: 1u64,
last_seen_timestamp_ns: 42i64,
last_generated_timestamp_ns: 43i64,
recent_events_log: {
"000_fake_event": {
event_time: 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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_input_event(zx::Time::from_nanos(44i64)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
events_count: 2u64,
last_seen_timestamp_ns: 42i64,
last_generated_timestamp_ns: 44i64,
recent_events_log: {
"000_fake_event": {
event_time: 43i64,
},
"001_fake_event": {
event_time: 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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
handler
.clone()
.handle_input_event(create_fake_handled_input_event(zx::Time::from_nanos(44)))
.await;
assert_data_tree!(inspector, root: {
test_node: contains {
events_count: 3u64,
last_seen_timestamp_ns: 42i64,
last_generated_timestamp_ns: 44i64,
recent_events_log: {
"000_fake_event": {
event_time: 43i64,
},
"001_fake_event": {
event_time: 44i64,
},
"002_fake_event": {
event_time: 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,
},
light_sensor: {
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,
},
touch_screen: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
touchpad: {
events_count: 0u64,
handled_events_count: 0u64,
last_generated_timestamp_ns: 0i64,
last_seen_timestamp_ns: 0i64,
},
}
});
}
#[test_case([i64::MIN]; "min value")]
#[test_case([-1]; "negative value")]
#[test_case([0]; "zero")]
#[test_case([1]; "positive value")]
#[test_case([i64::MAX]; "max value")]
#[test_case([1_000_000, 10_000_000, 100_000_000, 1000_000_000]; "multiple values")]
#[fuchsia::test(allow_stalls = false)]
async fn updates_latency_histogram(
latencies_nsec: impl IntoIterator<Item = i64> + Clone + 'static,
) {
let inspector = inspect::Inspector::default();
let root = inspector.root();
let test_node = root.create_child("test_node");
let mut seen_timestamps = latencies_nsec.clone().into_iter().map(zx::Time::from_nanos);
let now = move || {
seen_timestamps.next().expect("internal error: test has more events than latencies")
};
let handler = super::InspectHandler::new_internal(
test_node,
now,
&hashset! {},
/* displays_recent_events = */ false,
);
for _latency in latencies_nsec.clone() {
handler.clone().handle_input_event(create_fake_input_event(zx::Time::ZERO)).await;
}
let mut histogram_assertion = diagnostics_assertions::HistogramAssertion::exponential(
super::LATENCY_HISTOGRAM_PROPERTIES,
);
histogram_assertion
.insert_values(latencies_nsec.into_iter().map(|nsec| nsec / 1000 / 1000));
assert_data_tree!(inspector, root: {
test_node: contains {
pipeline_latency_ms: histogram_assertion
}
})
}
}