From 67db5f1885f7ccf7c9121ba6a1af5b1e227abe78 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Fri, 19 Dec 2025 20:49:57 +0000 Subject: [PATCH] First microbenchmark conducted --- README.md | 2 ++ docs/TESTS.md | 30 +++++++++++++++++++++++ vuinputd-tests/src/bin/test-keyboard.rs | 8 +++--- vuinputd-tests/src/test_log.rs | 4 +-- vuinputd-tests/tests/integration_tests.rs | 17 +++++++++---- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6cf9a8a..c7f4b92 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ sequenceDiagram Kernel->>Game: 10. send input data via eventX device ``` +> **Performance note:** +> While `vuinputd` adds an extra userspace round trip via CUSE, the measured overhead is in the range of **tens of microseconds per event** in a simple integration test. This is several orders of magnitude smaller than typical sources of input latency such as frame rendering, compositor delays, scheduling jitter, or network latency. In practice, the additional cost is negligible for interactive and latency-sensitive applications, including gaming. More detailed benchmarks can be found in `TESTS.md`. --- diff --git a/docs/TESTS.md b/docs/TESTS.md index 9b403c7..142c634 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -7,6 +7,36 @@ Install bubblewrap: Run with `cargo test -p vuinputd-tests --features "requires-privileges requires-uinput requires-bwrap"`. + + +## Performance tests + +Using CUSE introduces an additional round trip between kernel and userspace, which inevitably adds overhead compared to direct uinput access. To estimate the order of magnitude of this overhead, the `vuinputd-tests` include a simple integration test that emits two input events: once using direct uinput access and once via `vuinputd` v0.3. + +The test measures the elapsed time between emitting an event and receiving it again, using `CLOCK_MONOTONIC`. The measured latencies were: + +* **First call:** 16 µs (direct uinput) vs. 90 µs (vuinputd) +* **Second call:** 7 µs (direct uinput) vs. 68 µs (vuinputd) + +As expected, `vuinputd` introduces a clearly measurable performance penalty due to the additional userspace round trip. However, even in this best-case microbenchmark, the absolute overhead remains well below 0.1 ms per event. + +In practical terms, this level of overhead is negligible for real-world usage. For latency-sensitive applications such as gaming, tens of microseconds are several orders of magnitude smaller than typical sources of latency such as frame rendering time, compositor delays, scheduling jitter, or network latency. Even a single rendered frame at 60 Hz already accounts for roughly 16.6 ms, making the additional cost introduced by `vuinputd` effectively unobservable to the user. + +It is important to note that this benchmark is intentionally minimal and primarily intended to provide a rough sense of scale. It does not model realistic workloads, higher event rates, or concurrent inputs. More comprehensive benchmarks are required to assess behavior under load and contention. Nevertheless, these results demonstrate that the architectural approach taken by `vuinputd` is sound and does not introduce prohibitive latency by design. +As long as more realistic benchmarks confirm similar behavior under load, `vuinputd` can be considered suitable even for interactive and latency-sensitive use cases. + +Detailed results: + +`integration_tests.rs#test_keyboard_in_container_with_uinput`: +``` +{"events":[{"tv_sec":3133476,"tv_nsec":947503794,"duration_usec":16,"type_":1,"code":57,"value":1,"send_and_receive_match":true},{"tv_sec":3133476,"tv_nsec":947520555,"duration_usec":7,"type_":1,"code":57,"value":0,"send_and_receive_match":true}]} +``` + +`integration_tests.rs#test_keyboard_in_container_with_vuinput`: +``` +Event log: {"events":[{"tv_sec":3133303,"tv_nsec":796108454,"duration_usec":90,"type_":1,"code":57,"value":1,"send_and_receive_match":true},{"tv_sec":3133303,"tv_nsec":796198973,"duration_usec":68,"type_":1,"code":57,"value":0,"send_and_receive_match":true}]} +``` + ## Manual end-to-end tests | vuinputd | host | input type | app that creates device | app that reads device | working | Notes | diff --git a/vuinputd-tests/src/bin/test-keyboard.rs b/vuinputd-tests/src/bin/test-keyboard.rs index ed039e2..d4112de 100644 --- a/vuinputd-tests/src/bin/test-keyboard.rs +++ b/vuinputd-tests/src/bin/test-keyboard.rs @@ -280,7 +280,7 @@ unsafe fn set_standard_keyboard_keys(fd: i32) -> Result<(), std::io::Error> { #[command(author, version, about)] struct Args { /// Use IPC - #[arg(long, default_value_t = false)] + #[arg(long)] ipc: bool, /// Device path (with /dev/) @@ -335,7 +335,7 @@ fn emit_read_and_log( let input_event_recv = read_event(&read_from).unwrap(); let _syn_recv = read_event(&read_from).unwrap(); let (time_recv_sec, time_recv_nsec) = monotonic_time(); - let duration_nsec = + let duration_usec = (time_recv_sec - time_sent_sec) * 1_000_000 + (time_recv_nsec - time_sent_nsec) / 1000; let send_and_receive_match = input_event_recv.type_ == ev_type && input_event_recv.code == code @@ -343,8 +343,8 @@ fn emit_read_and_log( Ok(LoggedInputEvent { tv_sec: time_sent_sec, - tv_usec: time_sent_nsec, - duration_nsec: duration_nsec, + tv_nsec: time_sent_nsec, + duration_usec: duration_usec, type_: ev_type, code: code, value: val, diff --git a/vuinputd-tests/src/test_log.rs b/vuinputd-tests/src/test_log.rs index de02bea..80e9fc2 100644 --- a/vuinputd-tests/src/test_log.rs +++ b/vuinputd-tests/src/test_log.rs @@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize}; pub struct LoggedInputEvent { pub tv_sec: i64, - pub tv_usec: i64, + pub tv_nsec: i64, - pub duration_nsec: i64, + pub duration_usec: i64, pub type_: u16, diff --git a/vuinputd-tests/tests/integration_tests.rs b/vuinputd-tests/tests/integration_tests.rs index ed39e08..cef9ce6 100644 --- a/vuinputd-tests/tests/integration_tests.rs +++ b/vuinputd-tests/tests/integration_tests.rs @@ -99,14 +99,18 @@ fn test_keyboard_on_host() { fn test_keyboard_in_container_with_uinput() { let test_keyboard = env!("CARGO_BIN_EXE_test-keyboard"); - let out = bwrap::BwrapBuilder::new() + let (builder, _ipc) = bwrap::BwrapBuilder::new() .unshare_net() .ro_bind("/", "/") .tmpfs("/tmp") .dev_bind("/dev/uinput", "/dev/uinput") .dev_bind("/dev/input", "/dev/input") .die_with_parent() - .command(test_keyboard, &[]) + .with_ipc() + .expect("failed to create IPC"); + + let out = builder + .command(test_keyboard, &["--ipc"]) .run() .unwrap_or_else(|e| panic!("failed to run bwrap!: {e}")); @@ -122,14 +126,13 @@ fn test_keyboard_in_container_with_uinput() { feature = "requires-uinput", feature = "requires-bwrap" ))] -//#[ignore] #[test] fn test_keyboard_in_container_with_vuinput() { run_vuinputd::ensure_vuinputd_running(); let test_keyboard = env!("CARGO_BIN_EXE_test-keyboard"); - let out = bwrap::BwrapBuilder::new() + let (builder, _ipc) = bwrap::BwrapBuilder::new() .unshare_net() .ro_bind("/", "/") .tmpfs("/tmp") @@ -139,7 +142,11 @@ fn test_keyboard_in_container_with_vuinput() { .tmpfs("/run") .dev_bind("/dev/vuinput-test", "/dev/uinput") .die_with_parent() - .command(test_keyboard, &[]) + .with_ipc() + .expect("failed to create IPC"); + + let out = builder + .command(test_keyboard, &["--ipc"]) .run() .unwrap_or_else(|e| panic!("failed to run bwrap!: {e}"));